概述
我们知道,当我们在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.READ
且String.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的该根路径,有ReadOperation
有Selector
相当于是根路径下的一个子路径
经过上述的处理,我们就得到了一个DiscoveredWebOperation
,然后回到我们上面说的加入HandlerMapping:
而在ServletWebOperationAdapter
中this.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路径所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复