我是靠谱客的博主 苹果烧鹅,最近开发中收集的这篇文章主要介绍springboot源码研究actuator,自定义actuator路径,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

我们知道,当我们在springboot项目中引入了actuator模块之后,可以通过暴露的端口来获取系统相关信息:

   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

如获取bean相关信息:
在这里插入图片描述

那么这里是怎么样的逻辑呢 ?
深入源码研究,我们发现,这里所有的起点一个注解上:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Endpoint {
	/**
	 * The id of the endpoint (must follow {@link EndpointId} rules).
	 * @return the id
	 * @see EndpointId
	 */
	String id() default "";

	/**
	 * If the endpoint should be enabled or disabled by default.
	 * @return {@code true} if the endpoint is enabled by default
	 */
	boolean enableByDefault() default true;

}

在acturator中,有如下几个注解是基于Endpoint注解的:

  • WebEndpoint
  • ServletEndpoint
  • ControllerEndpoint 、 RestControllerEndpoint
  • @EndpointExtension 将Endpoint拓展到特定技术下使用,通常不直接使用该注解,而使用它的组合注解,例如:@EndpointWebExtension拓展到Web技术下(例:SpringMVC Spring WebFlux) @EndpointJmxExtension

我们在访问被Endpoint注解修饰的类的时候并不是直接通过Endpoint注解,而是通过其封装的ExposableEndpoint来进行相关细节的封装和访问,其类结构层次如下:
在这里插入图片描述
而Endpoint的封装则在EndpointDiscoverer中进行:

@Override
	public final Collection<E> getEndpoints() {
		if (this.endpoints == null) {
			this.endpoints = discoverEndpoints();
		}
		return this.endpoints;
	}

	private Collection<E> discoverEndpoints() {
		Collection<EndpointBean> endpointBeans = createEndpointBeans();
		addExtensionBeans(endpointBeans);
		return convertToEndpoints(endpointBeans);
	}

	private Collection<EndpointBean> createEndpointBeans() {
		Map<EndpointId, EndpointBean> byId = new LinkedHashMap<>();
		String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.applicationContext,
				Endpoint.class);
		for (String beanName : beanNames) {
			if (!ScopedProxyUtils.isScopedTarget(beanName)) {
				EndpointBean endpointBean = createEndpointBean(beanName);
				EndpointBean previous = byId.putIfAbsent(endpointBean.getId(), endpointBean);
				Assert.state(previous == null, () -> "Found two endpoints with the id '" + endpointBean.getId() + "': '"
						+ endpointBean.getBeanName() + "' and '" + previous.getBeanName() + "'");
			}
		}
		return byId.values();
	}

	private EndpointBean createEndpointBean(String beanName) {
		Object bean = this.applicationContext.getBean(beanName);
		return new EndpointBean(beanName, bean);
	}

	private void addExtensionBeans(Collection<EndpointBean> endpointBeans) {
		Map<EndpointId, EndpointBean> byId = endpointBeans.stream()
				.collect(Collectors.toMap(EndpointBean::getId, Function.identity()));
		String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.applicationContext,
				EndpointExtension.class);
		for (String beanName : beanNames) {
			ExtensionBean extensionBean = createExtensionBean(beanName);
			EndpointBean endpointBean = byId.get(extensionBean.getEndpointId());
			Assert.state(endpointBean != null, () -> ("Invalid extension '" + extensionBean.getBeanName()
					+ "': no endpoint found with id '" + extensionBean.getEndpointId() + "'"));
			addExtensionBean(endpointBean, extensionBean);
		}
	}

	private ExtensionBean createExtensionBean(String beanName) {
		Object bean = this.applicationContext.getBean(beanName);
		return new ExtensionBean(beanName, bean);
	}

	private void addExtensionBean(EndpointBean endpointBean, ExtensionBean extensionBean) {
		if (isExtensionExposed(endpointBean, extensionBean)) {
			Assert.state(isEndpointExposed(endpointBean) || isEndpointFiltered(endpointBean),
					() -> "Endpoint bean '" + endpointBean.getBeanName() + "' cannot support the extension bean '"
							+ extensionBean.getBeanName() + "'");
			endpointBean.addExtension(extensionBean);
		}
	}

	private Collection<E> convertToEndpoints(Collection<EndpointBean> endpointBeans) {
		Set<E> endpoints = new LinkedHashSet<>();
		for (EndpointBean endpointBean : endpointBeans) {
			if (isEndpointExposed(endpointBean)) {
				endpoints.add(convertToEndpoint(endpointBean));
			}
		}
		return Collections.unmodifiableSet(endpoints);
	}

	private E convertToEndpoint(EndpointBean endpointBean) {
		MultiValueMap<OperationKey, O> indexed = new LinkedMultiValueMap<>();
		EndpointId id = endpointBean.getId();
		addOperations(indexed, id, endpointBean.getBean(), false);
		if (endpointBean.getExtensions().size() > 1) {
			String extensionBeans = endpointBean.getExtensions().stream().map(ExtensionBean::getBeanName)
					.collect(Collectors.joining(", "));
			throw new IllegalStateException("Found multiple extensions for the endpoint bean "
					+ endpointBean.getBeanName() + " (" + extensionBeans + ")");
		}
		for (ExtensionBean extensionBean : endpointBean.getExtensions()) {
			addOperations(indexed, id, extensionBean.getBean(), true);
		}
		assertNoDuplicateOperations(endpointBean, indexed);
		List<O> operations = indexed.values().stream().map(this::getLast).filter(Objects::nonNull)
				.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
		return createEndpoint(endpointBean.getBean(), id, endpointBean.isEnabledByDefault(), operations);
	}

	private void addOperations(MultiValueMap<OperationKey, O> indexed, EndpointId id, Object target,
			boolean replaceLast) {
		Set<OperationKey> replacedLast = new HashSet<>();
		Collection<O> operations = this.operationsFactory.createOperations(id, target);
		for (O operation : operations) {
			OperationKey key = createOperationKey(operation);
			O last = getLast(indexed.get(key));
			if (replaceLast && replacedLast.add(key) && last != null) {
				indexed.get(key).remove(last);
			}
			indexed.add(key, operation);
		}
	}

可以看到,这里主要就是扫描当前bean中有没有被Endpoint修饰的类,另外查看有没没有EndpointExtension修饰的类,但是其必须有对应的被Endpoint修饰的类,这里实际上创建的就是EndpointBean
在创建EndpointBean 的时候,还有一步重要的操作是创建Operation,其主要逻辑就是,扫描被Endpoint修饰的类的方法,如果方法上有

private static final Map<OperationType, Class<? extends Annotation>> OPERATION_TYPES;

	static {
		Map<OperationType, Class<? extends Annotation>> operationTypes = new EnumMap<>(OperationType.class);
		operationTypes.put(OperationType.READ, ReadOperation.class);
		operationTypes.put(OperationType.WRITE, WriteOperation.class);
		operationTypes.put(OperationType.DELETE, DeleteOperation.class);
		OPERATION_TYPES = Collections.unmodifiableMap(operationTypes);
	}

注解,则会创建对应的Operation,而最后实际创建则再其对应的子类中实现:
在这里插入图片描述
我们这里以WebEndpointDiscoverer为例进行说明:

protected WebOperation createOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod,
			OperationInvoker invoker) {
		String rootPath = PathMapper.getRootPath(this.endpointPathMappers, endpointId);
		WebOperationRequestPredicate requestPredicate = this.requestPredicateFactory.getRequestPredicate(endpointId,
				rootPath, operationMethod);
		return new DiscoveredWebOperation(endpointId, operationMethod, invoker, requestPredicate);
	}
	//PathMapper.java
static String getRootPath(List<PathMapper> pathMappers, EndpointId endpointId) {
		Assert.notNull(endpointId, "EndpointId must not be null");
		if (pathMappers != null) {
			for (PathMapper mapper : pathMappers) {
				String path = mapper.getRootPath(endpointId);
				if (StringUtils.hasText(path)) {
					return path;
				}
			}
		}
		return endpointId.toString();
	}

WebEndpointDiscoverer则是在WebEndpointAutoConfiguration进行初始化的:

@Configuration
@ConditionalOnWebApplication
@AutoConfigureAfter(EndpointAutoConfiguration.class)
@EnableConfigurationProperties(WebEndpointProperties.class)
public class WebEndpointAutoConfiguration {

	private static final List<String> MEDIA_TYPES = Arrays.asList(ActuatorMediaType.V2_JSON, "application/json");

	private final ApplicationContext applicationContext;

	private final WebEndpointProperties properties;

	public WebEndpointAutoConfiguration(ApplicationContext applicationContext, WebEndpointProperties properties) {
		this.applicationContext = applicationContext;
		this.properties = properties;
	}

	@Bean
	public PathMapper webEndpointPathMapper() {
		return new MappingWebEndpointPathMapper(this.properties.getPathMapping());
	}
	@Bean
	@ConditionalOnMissingBean(WebEndpointsSupplier.class)
	public WebEndpointDiscoverer webEndpointDiscoverer(ParameterValueMapper parameterValueMapper,
			EndpointMediaTypes endpointMediaTypes, ObjectProvider<PathMapper> endpointPathMappers,
			ObjectProvider<OperationInvokerAdvisor> invokerAdvisors,
			ObjectProvider<EndpointFilter<ExposableWebEndpoint>> filters) {
		return new WebEndpointDiscoverer(this.applicationContext, parameterValueMapper, endpointMediaTypes,
				endpointPathMappers.orderedStream().collect(Collectors.toList()),
				invokerAdvisors.orderedStream().collect(Collectors.toList()),
				filters.orderedStream().collect(Collectors.toList()));
	}
	......
}

WebEndpointProperties则对应我们在项目中的配置:

management:
  endpoints:
    web:
      exposure:
        include: "*"  #需要开放才能通过接口请求刷新

可以看到,如果默认情况下我们不配置任何相关路径,那么Endpoint的默认路径就是其endpointId, 如果我们想要更改某个Endpoint的路径,则按照WebEndpointProperties ->pathMapping ,按照 endpointId和需要的路径更改即可,

@ConfigurationProperties(prefix = "management.endpoints.web")
public class WebEndpointProperties {

	private final Exposure exposure = new Exposure();

	/**
	 * Base path for Web endpoints. Relative to server.servlet.context-path or
	 * management.server.servlet.context-path if management.server.port is configured.
	 */
	private String basePath = "/actuator";

	/**
	 * Mapping between endpoint IDs and the path that should expose them.
	 * `这里记录的endpintID和其对应的路径,如果没有在这里指定,则默认采取路由路径是endponitId`
	 */
	private final Map<String, String> pathMapping = new LinkedHashMap<>();

	public Exposure getExposure() {
		return this.exposure;
	}

	public String getBasePath() {
		return this.basePath;
	}

	public void setBasePath(String basePath) {
		Assert.isTrue(basePath.isEmpty() || basePath.startsWith("/"), "Base path must start with '/' or be empty");
		this.basePath = cleanBasePath(basePath);
	}

	private String cleanBasePath(String basePath) {
		if (StringUtils.hasText(basePath) && basePath.endsWith("/")) {
			return basePath.substring(0, basePath.length() - 1);
		}
		return basePath;
	}

	public Map<String, String> getPathMapping() {
		return this.pathMapping;
	}
	public static class Exposure {

		/**
		 * Endpoint IDs that should be included or '*' for all.
		 */
		private Set<String> include = new LinkedHashSet<>();

		/**
		 * Endpoint IDs that should be excluded or '*' for all.
		 */
		private Set<String> exclude = new LinkedHashSet<>();
		...........
	}

}

例如调整配置如下:

management:
  endpoints:
    web:
      exposure:
        include: "*"  #需要开放才能通过接口请求刷新
      path-mapping:
        beans: beansPath

在这里插入图片描述

可以看到,我们可以自己自定义actuator的端点的路由信息。

到这里,我们就把一个Endpoint的信息解析并封装,那么是怎么暴露请求的呢 ?
还是以WebEndpoint为例说明。
在这里插入图片描述
将Endpoint暴露对应的请求在WebMvcEndpointHandlerMapping中,类结构层次如下:
在这里插入图片描述
我们看到其父类AbstractHandlerMethodMapping实现了InitializingBean接口,其重写afterPropertiesSet方法如下:

	public void afterPropertiesSet() {
		initHandlerMethods();
	}

AbstractHandlerMethodMapping中:

//AbstractHandlerMethodMapping.java
protected void initHandlerMethods() {
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				processCandidateBean(beanName);
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

而在子类AbstractWebMvcEndpointHandlerMapping重写了该方法如下:

// AbstractWebMvcEndpointHandlerMapping.java
protected void initHandlerMethods() {
		for (ExposableWebEndpoint endpoint : this.endpoints) {
			for (WebOperation operation : endpoint.getOperations()) {
				registerMappingForOperation(endpoint, operation);
			}
		}
		if (StringUtils.hasText(this.endpointMapping.getPath())) {
			registerLinksMapping();
		}
	}

AbstractHandlerMethodMapping则是SpringMVC的核心,springmvc使用范例,底层实现原理,源代码研究,springmvc父子容器

// AbstractWebMvcEndpointHandlerMapping.java
private void registerMappingForOperation(ExposableWebEndpoint endpoint, WebOperation operation) {
		ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint, operation,
				new ServletWebOperationAdapter(operation));
		registerMapping(createRequestMappingInfo(operation), new OperationHandler(servletWebOperation),
				this.handleMethod);
	}
protected ServletWebOperation wrapServletWebOperation(ExposableWebEndpoint endpoint, WebOperation operation,
			ServletWebOperation servletWebOperation) {
		return servletWebOperation;
	}
private RequestMappingInfo createRequestMappingInfo(WebOperation operation) {
		WebOperationRequestPredicate predicate = operation.getRequestPredicate();
		PatternsRequestCondition patterns = patternsRequestConditionForPattern(predicate.getPath());
		RequestMethodsRequestCondition methods = new RequestMethodsRequestCondition(
				RequestMethod.valueOf(predicate.getHttpMethod().name()));
		ConsumesRequestCondition consumes = new ConsumesRequestCondition(
				StringUtils.toStringArray(predicate.getConsumes()));
		ProducesRequestCondition produces = new ProducesRequestCondition(
				StringUtils.toStringArray(predicate.getProduces()));
		return new RequestMappingInfo(null, patterns, methods, null, null, consumes, produces, null);
	}	
	public void registerMapping(T mapping, Object handler, Method method) {
		if (logger.isTraceEnabled()) {
			logger.trace("Register "" + mapping + "" to " + method.toGenericString());
		}
		this.mappingRegistry.register(mapping, handler, method);
	}	

WebMvcEndpointChildContextConfiguration中,实例化DispatcherServlet:

@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
	public DispatcherServlet dispatcherServlet() {
		DispatcherServlet dispatcherServlet = new DispatcherServlet();
		// Ensure the parent configuration does not leak down to us
		dispatcherServlet.setDetectAllHandlerAdapters(false);
		dispatcherServlet.setDetectAllHandlerExceptionResolvers(false);
		dispatcherServlet.setDetectAllHandlerMappings(false);
		dispatcherServlet.setDetectAllViewResolvers(false);
		return dispatcherServlet;
	}

请注意,这里的设置,我们以加载HandlerMappint为例来说明:

// DispatcherServlet.java
private void initHandlerMappings(ApplicationContext context) {
		this.handlerMappings = null;
		if (this.detectAllHandlerMappings) {
			Map<String, HandlerMapping> matchingBeans =
					BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
			if (!matchingBeans.isEmpty()) {
				this.handlerMappings = new ArrayList<>(matchingBeans.values());
				AnnotationAwareOrderComparator.sort(this.handlerMappings);
			}
		}
		else {
			try {
				HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
				this.handlerMappings = Collections.singletonList(hm);
			}
			catch (NoSuchBeanDefinitionException ex) {
			}
		}
		if (this.handlerMappings == null) {
			this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
			if (logger.isTraceEnabled()) {
				logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
						"': using default strategies from DispatcherServlet.properties");
			}
		}
	}

这样,我们加载HandlerMappint的时候,不会从BeanFactory加载所有HandlerMapping而是先加载固定名称的HandlerMapping的bean,加载不到,在加载默认的,在WebMvcEndpointChildContextConfiguration提供了默认的HandlerMapping:

// WebMvcEndpointChildContextConfiguration.java
@Bean(name = DispatcherServlet.HANDLER_MAPPING_BEAN_NAME)
	public CompositeHandlerMapping compositeHandlerMapping() {
		return new CompositeHandlerMapping();
	}

即上述的CompositeHandlerMapping :

class CompositeHandlerMapping implements HandlerMapping {
	@Autowired
	private ListableBeanFactory beanFactory;
	private List<HandlerMapping> mappings;
	@Override
	public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.mappings == null) {
			this.mappings = extractMappings();
		}
		for (HandlerMapping mapping : this.mappings) {
			HandlerExecutionChain handler = mapping.getHandler(request);
			if (handler != null) {
				return handler;
			}
		}
		return null;
	}
	private List<HandlerMapping> extractMappings() {
		List<HandlerMapping> list = new ArrayList<>();
		list.addAll(this.beanFactory.getBeansOfType(HandlerMapping.class).values());
		list.remove(this);
		AnnotationAwareOrderComparator.sort(list);
		return list;
	}
}

可以看到,这个类很简单,采用组合模式,将所有的HandlerMapping 都组合到当前类中,这样就将上述的各种xxxEndpoingHandlerMapping加入到SpringMVC的处理链中去了。

接下来我们分析,actuator是怎么将Endpont转换为对对应的HandlerMapping的?
EndpointDiscoverer中:

private E convertToEndpoint(EndpointBean endpointBean) {
		MultiValueMap<OperationKey, O> indexed = new LinkedMultiValueMap<>();
		EndpointId id = endpointBean.getId();
		addOperations(indexed, id, endpointBean.getBean(), false);
		if (endpointBean.getExtensions().size() > 1) {
			String extensionBeans = endpointBean.getExtensions().stream().map(ExtensionBean::getBeanName)
					.collect(Collectors.joining(", "));
			throw new IllegalStateException("Found multiple extensions for the endpoint bean "
					+ endpointBean.getBeanName() + " (" + extensionBeans + ")");
		}
		for (ExtensionBean extensionBean : endpointBean.getExtensions()) {
			addOperations(indexed, id, extensionBean.getBean(), true);
		}
		assertNoDuplicateOperations(endpointBean, indexed);
		List<O> operations = indexed.values().stream().map(this::getLast).filter(Objects::nonNull)
				.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
		return createEndpoint(endpointBean.getBean(), id, endpointBean.isEnabledByDefault(), operations);
	}
		private void addOperations(MultiValueMap<OperationKey, O> indexed, EndpointId id, Object target,
			boolean replaceLast) {
		Set<OperationKey> replacedLast = new HashSet<>();
		Collection<O> operations = this.operationsFactory.createOperations(id, target);
		for (O operation : operations) {
			OperationKey key = createOperationKey(operation);
			O last = getLast(indexed.get(key));
			if (replaceLast && replacedLast.add(key) && last != null) {
				indexed.get(key).remove(last);
			}
			indexed.add(key, operation);
		}
	}
	public Collection<O> createOperations(EndpointId id, Object target) {
		return MethodIntrospector
				.selectMethods(target.getClass(), (MetadataLookup<O>) (method) -> createOperation(id, target, method))
				.values();
	}
	private O createOperation(EndpointId endpointId, Object target, Method method) {
		return OPERATION_TYPES.entrySet().stream()
				.map((entry) -> createOperation(endpointId, target, method, entry.getKey(), entry.getValue()))
				.filter(Objects::nonNull).findFirst().orElse(null);
	}
		private O createOperation(EndpointId endpointId, Object target, Method method, OperationType operationType,
			Class<? extends Annotation> annotationType) {
		AnnotationAttributes annotationAttributes = AnnotatedElementUtils.getMergedAnnotationAttributes(method,
				annotationType);
		if (annotationAttributes == null) {
			return null;
		}
		DiscoveredOperationMethod operationMethod = new DiscoveredOperationMethod(method, operationType,
				annotationAttributes);
		OperationInvoker invoker = new ReflectiveOperationInvoker(target, operationMethod, this.parameterValueMapper);
		invoker = applyAdvisors(endpointId, operationMethod, invoker);
		return createOperation(endpointId, operationMethod, invoker);
	}

	private OperationInvoker applyAdvisors(EndpointId endpointId, OperationMethod operationMethod,
			OperationInvoker invoker) {
		if (this.invokerAdvisors != null) {
			for (OperationInvokerAdvisor advisor : this.invokerAdvisors) {
				invoker = advisor.apply(endpointId, operationMethod.getOperationType(), operationMethod.getParameters(),
						invoker);
			}
		}
		return invoker;
	}
//WebEndpointDiscoverer.java
@Override
protected WebOperation createOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod,
			OperationInvoker invoker) {
		String rootPath = PathMapper.getRootPath(this.endpointPathMappers, endpointId);
		WebOperationRequestPredicate requestPredicate = this.requestPredicateFactory.getRequestPredicate(endpointId,
				rootPath, operationMethod);
		return new DiscoveredWebOperation(endpointId, operationMethod, invoker, requestPredicate);
	}

这里简单说下流程,其实主要还是解析Endpoint注解的类上 的ReadOperation WriteOperation DeleteOperation 这三个注解的方法,
然后applyAdvisors这个方法,实际上是对OperationInvoker 一个封装,目前查看源码只有一个CachingOperationInvokerAdvisor:

ublic class CachingOperationInvokerAdvisor implements OperationInvokerAdvisor {

	private final Function<EndpointId, Long> endpointIdTimeToLive;

	public CachingOperationInvokerAdvisor(Function<EndpointId, Long> endpointIdTimeToLive) {
		this.endpointIdTimeToLive = endpointIdTimeToLive;
	}

	@Override
	public OperationInvoker apply(EndpointId endpointId, OperationType operationType, OperationParameters parameters,
			OperationInvoker invoker) {
		if (operationType == OperationType.READ && !hasMandatoryParameter(parameters)) {
			Long timeToLive = this.endpointIdTimeToLive.apply(endpointId);
			if (timeToLive != null && timeToLive > 0) {
				return new CachingOperationInvoker(invoker, timeToLive);
			}
		}
		return invoker;
	}

	private boolean hasMandatoryParameter(OperationParameters parameters) {
		for (OperationParameter parameter : parameters) {
			if (parameter.isMandatory() && !SecurityContext.class.isAssignableFrom(parameter.getType())) {
				return true;
			}
		}
		return false;
	}

}

EndpointAutoConfiguration里面进行实例化:

@Bean
	@ConditionalOnMissingBean
	public CachingOperationInvokerAdvisor endpointCachingOperationInvokerAdvisor(Environment environment) {
		return new CachingOperationInvokerAdvisor(new EndpointIdTimeToLivePropertyFunction(environment));
	}
	class EndpointIdTimeToLivePropertyFunction implements Function<EndpointId, Long> {

	private static final Bindable<Duration> DURATION = Bindable.of(Duration.class);

	private final Environment environment;

	/**
	 * Create a new instance with the {@link PropertyResolver} to use.
	 * @param environment the environment
	 */
	EndpointIdTimeToLivePropertyFunction(Environment environment) {
		this.environment = environment;
	}

	@Override
	public Long apply(EndpointId endpointId) {
		String name = String.format("management.endpoint.%s.cache.time-to-live", endpointId.toLowerCaseString());
		BindResult<Duration> duration = Binder.get(this.environment).bind(name, DURATION);
		return duration.map(Duration::toMillis).orElse(null);
	}

}

实际上CachingOperationInvokerAdvisor 就是对OperationInvoker 的封装,就是对于OperationType.READ的请求进行缓存,如果方式上OperationType.READString.format("management.endpoint.%s.cache.time-to-live", endpointId.toLowerCaseString());且有改配置,那么就会将这个方法进行封装,封装为CachingOperationInvoker,这样执行方法的时候会将其结果缓存,指定的时间后失效。
最后返回的实际上是一个DiscoveredWebOperation:

protected WebOperation createOperation(EndpointId endpointId, DiscoveredOperationMethod operationMethod,
			OperationInvoker invoker) {
		String rootPath = PathMapper.getRootPath(this.endpointPathMappers, endpointId);
		WebOperationRequestPredicate requestPredicate = this.requestPredicateFactory.getRequestPredicate(endpointId,
				rootPath, operationMethod);
		return new DiscoveredWebOperation(endpointId, operationMethod, invoker, requestPredicate);
	}

这里需要关注这个方法逻辑:

WebOperationRequestPredicate requestPredicate = this.requestPredicateFactory.getRequestPredicate(endpointId,
				rootPath, operationMethod);
public WebOperationRequestPredicate getRequestPredicate(EndpointId endpointId, String rootPath,
			DiscoveredOperationMethod operationMethod) {
		Method method = operationMethod.getMethod();
		String path = getPath(rootPath, method);
		WebEndpointHttpMethod httpMethod = determineHttpMethod(operationMethod.getOperationType());
		Collection<String> consumes = getConsumes(httpMethod, method);
		Collection<String> produces = getProduces(operationMethod, method);
		return new WebOperationRequestPredicate(path, httpMethod, consumes, produces);
	}
private WebEndpointHttpMethod determineHttpMethod(OperationType operationType) {
		if (operationType == OperationType.WRITE) {
			return WebEndpointHttpMethod.POST;
		}
		if (operationType == OperationType.DELETE) {
			return WebEndpointHttpMethod.DELETE;
		}
		return WebEndpointHttpMethod.GET;
	}
	private Collection<String> getConsumes(WebEndpointHttpMethod httpMethod, Method method) {
		if (WebEndpointHttpMethod.POST == httpMethod && consumesRequestBody(method)) {
			return this.endpointMediaTypes.getConsumed();
		}
		return Collections.emptyList();
	}
private boolean consumesRequestBody(Method method) {
		return Stream.of(method.getParameters())
				.anyMatch((parameter) -> parameter.getAnnotation(Selector.class) == null);
	}
	private Collection<String> getProduces(DiscoveredOperationMethod operationMethod, Method method) {
		if (!operationMethod.getProducesMediaTypes().isEmpty()) {
			return operationMethod.getProducesMediaTypes();
		}
		if (Void.class.equals(method.getReturnType()) || void.class.equals(method.getReturnType())) {
			return Collections.emptyList();
		}
		if (producesResource(method)) {
			return Collections.singletonList("application/octet-stream");
		}
		return this.endpointMediaTypes.getProduced();
	}

	private boolean producesResource(Method method) {
		if (Resource.class.equals(method.getReturnType())) {
			return true;
		}
		if (WebEndpointResponse.class.isAssignableFrom(method.getReturnType())) {
			ResolvableType returnType = ResolvableType.forMethodReturnType(method);
			if (ResolvableType.forClass(Resource.class).isAssignableFrom(returnType.getGeneric(0))) {
				return true;
			}
		}
		return false;
	}
	private String getPath(String rootPath, Method method) {
		return rootPath + Stream.of(method.getParameters()).filter(this::hasSelector).map(this::slashName)
				.collect(Collectors.joining());
	}

	private boolean hasSelector(Parameter parameter) {
		return parameter.getAnnotation(Selector.class) != null;
	}

	private String slashName(Parameter parameter) {
		return "/{" + parameter.getName() + "}";
	}

这里会根据Operation的类型,决定http的请求类型,默认是GET,如果是WRTIE,则是POST,如果是DELETE,则是DELETE,同时会生成对应的请求路径,如果方法上有Selector注解,那么生成的请求路径相当于是一个动态的路径,例如MetricsEndpoint:

@Endpoint(id = "metrics")
public class MetricsEndpoint {

	private final MeterRegistry registry;

	public MetricsEndpoint(MeterRegistry registry) {
		this.registry = registry;
	}

	@ReadOperation
	public ListNamesResponse listNames() {
		Set<String> names = new LinkedHashSet<>();
		collectNames(names, this.registry);
		return new ListNamesResponse(names);
	}
	@ReadOperation
	public MetricResponse metric(@Selector String requiredMetricName, @Nullable List<String> tag) {
		List<Tag> tags = parseTags(tag);
		Collection<Meter> meters = findFirstMatchingMeters(this.registry, requiredMetricName, tags);
		if (meters.isEmpty()) {
			return null;
		}
		Map<Statistic, Double> samples = getSamples(meters);
		Map<String, Set<String>> availableTags = getAvailableTags(meters);
		tags.forEach((t) -> availableTags.remove(t.getKey()));
		Meter.Id meterId = meters.iterator().next().getId();
		return new MetricResponse(requiredMetricName, meterId.getDescription(), meterId.getBaseUnit(),
				asList(samples, Sample::new), asList(availableTags, AvailableTag::new));
	}

默认的请求:
在这里插入图片描述
然后看对应Selector方法,我们选取其中一个请求如下:
在这里插入图片描述

从这里也能够看出来,如果一个Endpoint,某一个方法上有了一个ReadOperation注解,那么默认的EndpointId对应的请求路径就会映射到该方法上,其他方法如果也用这个注解,那么必须加上Selector注解,相当于有ReadOperation没有Selector注解是对应的HTTP.GET的该根路径,有ReadOperationSelector相当于是根路径下的一个子路径

经过上述的处理,我们就得到了一个DiscoveredWebOperation,然后回到我们上面说的加入HandlerMapping:
在这里插入图片描述
在这里插入图片描述
而在ServletWebOperationAdapterthis.handleMethod:

private final Method handleMethod = ReflectionUtils.findMethod(OperationHandler.class, "handle",
			HttpServletRequest.class, Map.class);

其通过反射获取OperationHandler上的OperationHandler方法:

@ResponseBody
		public Object handle(HttpServletRequest request, @RequestBody(required = false) Map<String, String> body) {
			return this.operation.handle(request, body);
		}
// ServletWebOperationAdapter.java
public Object handle(HttpServletRequest request, @RequestBody(required = false) Map<String, String> body) {
			Map<String, Object> arguments = getArguments(request, body);
			try {
				return handleResult(
						this.operation.invoke(new InvocationContext(new ServletSecurityContext(request), arguments)),
						HttpMethod.valueOf(request.getMethod()));
			}
			catch (InvalidEndpointRequestException ex) {
				throw new BadOperationRequestException(ex.getReason());
			}
		}
		private Object handleResult(Object result, HttpMethod httpMethod) {
			if (result == null) {
				return new ResponseEntity<>(
						(httpMethod != HttpMethod.GET) ? HttpStatus.NO_CONTENT : HttpStatus.NOT_FOUND);
			}
			if (!(result instanceof WebEndpointResponse)) {
				return result;
			}
			WebEndpointResponse<?> response = (WebEndpointResponse<?>) result;
			return new ResponseEntity<Object>(response.getBody(), HttpStatus.valueOf(response.getStatus()));
		}

可以看到,注入到springMVC中的路由,最终请求是通过Operation.invoke来执行,

//AbstractDiscoveredOperation.java
public Object invoke(InvocationContext context) {
		return this.invoker.invoke(context);
	}
//ReflectiveOperationInvoker.java
public Object invoke(InvocationContext context) {
		validateRequiredParameters(context);
		Method method = this.operationMethod.getMethod();
		Object[] resolvedArguments = resolveArguments(context);
		ReflectionUtils.makeAccessible(method);
		return ReflectionUtils.invokeMethod(method, this.target, resolvedArguments);
	}
	

最终通过反射执行Endpoint注解上方法来达到。
这就是整个atuator的执行逻辑流程,稍微写的有点啰嗦,回头有时间在整理下。

最后

以上就是苹果烧鹅为你收集整理的springboot源码研究actuator,自定义actuator路径的全部内容,希望文章能够帮你解决springboot源码研究actuator,自定义actuator路径所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(36)

评论列表共有 0 条评论

立即
投稿
返回
顶部