建议先预习Spring Boot Actuator,请戳《初识篇》
WEB Endpoints,以HTTP协议对外暴露提供访问接口。
META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.actuate.autoconfigure.endpoint.EndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointAutoConfiguration
公共配置,对于Web EndPoints与JMX EndPoints共用的配置。
@Configuration(proxyBeanMethods = false)
public class EndpointAutoConfiguration {
// 负责方法参数的转换的参数值映射器
@Bean
@ConditionalOnMissingBean
public ParameterValueMapper endpointOperationParameterMapper(
// 自动注入被@EndpointConverter标注的Converter与GenericConverter类型的bean
// Spring Boot Actuator默认没有提供,可自定义添加
@EndpointConverter ObjectProvider> converters,
@EndpointConverter ObjectProvider genericConverters) {
ConversionService conversionService = createConversionService(
converters.orderedStream().collect(Collectors.toList()),
genericConverters.orderedStream().collect(Collectors.toList()));
return new ConversionServiceParameterValueMapper(conversionService);
}
// 参数转换器
private ConversionService createConversionService(List> converters,
List genericConverters) {
// 默认为空,满足if条件
if (genericConverters.isEmpty() && converters.isEmpty()) {
// 添加Spring Boot默认提供的转换器
// ApplicationConversionService.getSharedInstance()也会调用new ApplicationConversionService();来添加默认提供的转换器
return ApplicationConversionService.getSharedInstance();
}
// 如果有自定义的转换器,在自定义的基础上,加上Spring Boot默认提供的转化器
ApplicationConversionService conversionService = new ApplicationConversionService();
converters.forEach(conversionService::addConverter);
genericConverters.forEach(conversionService::addConverter);
return conversionService;
}
// 缓存切面,第一次访问后,会将结果缓存起来。下一次再获取直接从缓存中取
// 可以设置缓存失效时间
@Bean
@ConditionalOnMissingBean
public CachingOperationInvokerAdvisor endpointCachingOperationInvokerAdvisor(Environment environment) {
return new CachingOperationInvokerAdvisor(new EndpointIdTimeToLivePropertyFunction(environment));
}
}
举例:自定义转换器
@Configuration
public class CustomConversionConfiguration{
/**
* 自定义Converter
* Converter接口是org.springframework.core.convert.converter.Converter
*
* 将Integer类型的值转换成String类型
*
*/
@Bean
// 加上注解标注给EndPoint提供服务
@EndpointConverter
public Converter customConverter() {
return new CustomConverter();
}
static class CustomConverter implements Converter{
@Override
public String convert(Integer source) {
if (source == null) {
return null;
}
return source.toString();
}
}
/**
*
* 自定义GenericConverter
* GenericConverter接口是:org.springframework.core.convert.converter.GenericConverter
*
* @return
*/
@Bean
// 加上注解标注给EndPoint提供服务
@EndpointConverter
public GenericConverter customGenericConverter() {
return new GenericConverter() {
@Override
public Set getConvertibleTypes() {
Set convertiblePairSet = new HashSet<>(2);
// 将Integer类型的值转换成String类型的值
convertiblePairSet.add(new ConvertiblePair(Integer.class, String.class));
return convertiblePairSet;
}
@Override
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
// 实现自定义逻辑
return null;
}
};
}
}
用于Web EndPoints构建的相关配置。
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@AutoConfigureAfter(EndpointAutoConfiguration.class)
// WebEndpointProperties:management.endpoints.web前缀的配置
@EnableConfigurationProperties(WebEndpointProperties.class)
public class WebEndpointAutoConfiguration {
private final ApplicationContext applicationContext;
private final WebEndpointProperties properties;
// 通过构造器注入ApplicationContext与WebEndpointProperties
public WebEndpointAutoConfiguration(ApplicationContext applicationContext, WebEndpointProperties properties) {
this.applicationContext = applicationContext;
this.properties = properties;
}
// 访问路径映射
// 默认EndPoint的id就是访问路径,可通过配置management.endpoints.web.pathMapping更改访问路径。
@Bean
public PathMapper webEndpointPathMapper() {
// 默认this.properties.getPathMapping()是空集合
return new MappingWebEndpointPathMapper(this.properties.getPathMapping());
}
// 默认的接口响应Media类型
@Bean
@ConditionalOnMissingBean
public EndpointMediaTypes endpointMediaTypes() {
// application/vnd.spring-boot.actuator.v3+json
// application/vnd.spring-boot.actuator.v2+json
// application/json
return EndpointMediaTypes.DEFAULT;
}
// EndPoint http服务暴露过滤器
// 根据配置:management.endpoints.web.exposure.include 设置暴露服务
// 根据配置:management.endpoints.web.exposure.exclude 设置不暴露服务
@Bean
public ExposeExcludePropertyEndpointFilter webExposeExcludePropertyEndpointFilter() {
WebEndpointProperties.Exposure exposure = this.properties.getExposure();
// 默认暴露 info、health
return new ExposeExcludePropertyEndpointFilter<>(ExposableWebEndpoint.class, exposure.getInclude(),
exposure.getExclude(), "info", "health");
}
// Controller EndPoint服务暴露过滤
@Bean
public ExposeExcludePropertyEndpointFilter controllerExposeExcludePropertyEndpointFilter() {
WebEndpointProperties.Exposure exposure = this.properties.getExposure();
return new ExposeExcludePropertyEndpointFilter<>(ExposableControllerEndpoint.class, exposure.getInclude(),
exposure.getExclude());
}
// 创建 Web EndPoints
// 根据@EndPoint、@WebEndPoint注解标注的类
@Bean
@ConditionalOnMissingBean(WebEndpointsSupplier.class)
public WebEndpointDiscoverer webEndpointDiscoverer(ParameterValueMapper parameterValueMapper,
EndpointMediaTypes endpointMediaTypes, ObjectProvider endpointPathMappers,
ObjectProvider invokerAdvisors,
ObjectProvider> filters) {
return new WebEndpointDiscoverer(this.applicationContext, parameterValueMapper, endpointMediaTypes,
endpointPathMappers.orderedStream().collect(Collectors.toList()),
invokerAdvisors.orderedStream().collect(Collectors.toList()),
filters.orderedStream().collect(Collectors.toList()));
}
// 创建 Controller EndPoint
// 根据@ControllerEndPoint标注的类
@Bean
@ConditionalOnMissingBean(ControllerEndpointsSupplier.class)
public ControllerEndpointDiscoverer controllerEndpointDiscoverer(ObjectProvider endpointPathMappers,
ObjectProvider>> filters) {
return new ControllerEndpointDiscoverer(this.applicationContext,
endpointPathMappers.orderedStream().collect(Collectors.toList()),
filters.getIfAvailable(Collections::emptyList));
}
// web应用类型为servlet,创建Servlet EndPoint
// 根据ServletEndPoint标注的类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
static class WebEndpointServletConfiguration {
@Bean
@ConditionalOnMissingBean(ServletEndpointsSupplier.class)
ServletEndpointDiscoverer servletEndpointDiscoverer(ApplicationContext applicationContext,
ObjectProvider endpointPathMappers,
ObjectProvider> filters) {
return new ServletEndpointDiscoverer(applicationContext,
endpointPathMappers.orderedStream().collect(Collectors.toList()),
filters.orderedStream().collect(Collectors.toList()));
}
}
// 根据WebEndpointDiscoverer、ControllerEndpointDiscoverer、ServletEndpointDiscoverer 记录各个EndPoint的id与访问路径关系
@Bean
@ConditionalOnMissingBean
public PathMappedEndpoints pathMappedEndpoints(Collection> endpointSuppliers) {
return new PathMappedEndpoints(this.properties.getBasePath(), endpointSuppliers);
}
}
四种ManagementContextConfiguration分别对应四种类型的WEB应用:Servlet、Spring WebFlux、Spring WebMVC、Jersey。
org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration=\
org.springframework.boot.actuate.autoconfigure.endpoint.web.ServletEndpointManagementContextConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.web.reactive.WebFluxEndpointManagementContextConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration,\
org.springframework.boot.actuate.autoconfigure.endpoint.web.jersey.JerseyWebEndpointManagementContextConfiguration
org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration配置的类结合注解@ManagementContextConfiguration使用时会在ManagementContextAutoConfiguration自动配置时被创建,在ManagementContextAutoConfiguration中会触发@EnableManagementContext功能,关键在于ManagementContextConfigurationImportSelector的作用:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ManagementContextConfigurationImportSelector.class)
@interface EnableManagementContext {
/**
* The management context type that should be enabled.
* @return the management context type
*/
ManagementContextType value();
}
对Spring MVC类型的WEB应用对外提供HTTP访问接口。
@ManagementContextConfiguration(proxyBeanMethods = false)
// Spring MVC环境
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@ConditionalOnBean({ DispatcherServlet.class, WebEndpointsSupplier.class })
// CorsEndpointProperties:management.endpoints.web.cors前缀的配置,与跨域有关
@EnableConfigurationProperties(CorsEndpointProperties.class)
public class WebMvcEndpointManagementContextConfiguration {
// 将WEB EndPoints提供HTTP 访问接口
// 通过参数注入WebEndpointAutoConfiguration中创建的各种类型的Bean
@Bean
@ConditionalOnMissingBean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties, Environment environment) {
List> allEndpoints = new ArrayList<>();
// 获取WEB EndPoints
Collection webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = StringUtils.hasText(basePath)
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT);
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
shouldRegisterLinksMapping);
}
// 将Controller转换成EndPoints访问映射
@Bean
@ConditionalOnMissingBean
public ControllerEndpointHandlerMapping controllerEndpointHandlerMapping(
ControllerEndpointsSupplier controllerEndpointsSupplier, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties) {
EndpointMapping endpointMapping = new EndpointMapping(webEndpointProperties.getBasePath());
return new ControllerEndpointHandlerMapping(endpointMapping, controllerEndpointsSupplier.getEndpoints(),
corsProperties.toCorsConfiguration());
}
}
对于方法webEndpointServletHandlerMapping,会通过参数自动注入在配置类WebEndpointAutoConfiguration中创建的Bean:WebEndpointDiscoverer、ServletEndpointDiscoverer、ControllerEndpointDiscoverer、EndpointMediaTypes。
对Spring WebFlux类型的WEB应用对外提供HTTP访问接口。
对Jersey类型的WEB应用对外提供HTTP访问接口。
对Servlet类型的WEB应用对外提供HTTP访问接口。
以在Spring MVC环境为例,即WebMvcEndpointManagementContextConfiguration。
在webEndpointServletHandlerMapping方法中负责创建WEB EndPoints以及HTTP服务暴露。
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties, Environment environment) {
List> allEndpoints = new ArrayList<>();
// 创建
// 获取WEB EndPoints
Collection webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = StringUtils.hasText(basePath)
|| ManagementPortType.get(environment).equals(ManagementPortType.DIFFERENT);
// 暴露
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
shouldRegisterLinksMapping);
}
1、webEndpointsSupplier#getEndpoints:
从getEndPoints方法开始,调用webEndpointsSupplier.getEndpoints(),即调用WebEndpointDiscoverer的getEndpoints方法。getEndpoints方法是继承自抽象类EndpointDiscoverer:
public final Collection getEndpoints() {
// this.endpoints为:private volatile Collection endpoints;
if (this.endpoints == null) {
this.endpoints = discoverEndpoints();
}
return this.endpoints;
}
当初始化时,第一次调用getEndpoints方法,this.endpoints是为null的,需要调用discoverEndpoints方法进行初始化。
private Collection discoverEndpoints() {
// 创建EndpointBean
Collection endpointBeans = createEndpointBeans();
// 对EndpointBean添加扩展,@EndpointExtension的功能
addExtensionBeans(endpointBeans);
// 将EndpointBean转换成EndPoint
return convertToEndpoints(endpointBeans);
}
(1)createEndpointBeans
创建EndpointBean,首先获取Spring容器中被@Endpoint、@WebEndpoint、@JmxEndpoint、@ServletEndpoint、@ControllerEndpoint等标注的bean的名称,再根据bean的名称创建EndpointBean:
private Collection createEndpointBeans() {
Map byId = new LinkedHashMap<>();
// 去Spring容器中获取@Endpoint注解标注的bean名称
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.applicationContext,
Endpoint.class);
for (String beanName : beanNames) {
if (!ScopedProxyUtils.isScopedTarget(beanName)) {
// 根据bean的名称依次创建EndpointBean
EndpointBean endpointBean = createEndpointBean(beanName);
// 校验id是否唯一,有重复报错
EndpointBean previous = byId.putIfAbsent(endpointBean.getId(), endpointBean);
Assert.state(previous == null, () -> "Found two endpoints with the id '" + endpointBean.getId() + "': '"
+ endpointBean.getBeanName() + "' and '" + previous.getBeanName() + "'");
}
}
// 返回EndpointBean集合
return byId.values();
}
createEndpointBean,根据bean的名称创建EndPointBean:
private EndpointBean createEndpointBean(String beanName) {
Object bean = this.applicationContext.getBean(beanName);
// 创建EndpointBean对象
return new EndpointBean(this.applicationContext.getEnvironment(), beanName, bean);
}
EndpointBean(Environment environment, String beanName, Object bean) {
MergedAnnotation annotation = MergedAnnotations
.from(bean.getClass(), SearchStrategy.TYPE_HIERARCHY).get(Endpoint.class);
// @Endpoint注解中的属性id值
String id = annotation.getString("id");
Assert.state(StringUtils.hasText(id),
() -> "No @Endpoint id attribute specified for " + bean.getClass().getName());
// bean的名称
this.beanName = beanName;
this.bean = bean;
// EndpointId
this.id = EndpointId.of(environment, id);
// 默认是否可用,@Endpoint注解中的属性enableByDefault值
this.enabledByDefault = annotation.getBoolean("enableByDefault");
// 过滤器,比如@ServletEndpoint上标注了@FilteredEndpoint(ServletEndpointFilter.class)
this.filter = getFilter(this.bean.getClass());
}
(2)addExtensionBeans
对EndpointBean,添加扩展。是@EndpointExtension的功能的处理:
private void addExtensionBeans(Collection endpointBeans) {
Map byId = endpointBeans.stream()
.collect(Collectors.toMap(EndpointBean::getId, Function.identity()));
// 去Spring容器中,获取所有被@EndpointExtension注解标注的bean
String[] beanNames = BeanFactoryUtils.beanNamesForAnnotationIncludingAncestors(this.applicationContext,
EndpointExtension.class);
for (String beanName : beanNames) {
// 创建ExtensionBean
ExtensionBean extensionBean = createExtensionBean(beanName);
// 根据属性endpoint配置的值获取对应的Endpoint
EndpointBean endpointBean = byId.get(extensionBean.getEndpointId());
Assert.state(endpointBean != null, () -> ("Invalid extension '" + extensionBean.getBeanName()
+ "': no endpoint found with id '" + extensionBean.getEndpointId() + "'"));
addExtensionBean(endpointBean, extensionBean);
}
}
创建ExtensionBean:
private ExtensionBean createExtensionBean(String beanName) {
Object bean = this.applicationContext.getBean(beanName);
return new ExtensionBean(this.applicationContext.getEnvironment(), beanName, bean);
}
添加扩展:
private void addExtensionBean(EndpointBean endpointBean, ExtensionBean extensionBean) {
// 判断,根据过滤器条件和EndpointBean是否对外暴露
if (isExtensionExposed(endpointBean, extensionBean)) {
Assert.state(isEndpointExposed(endpointBean) || isEndpointFiltered(endpointBean),
() -> "Endpoint bean '" + endpointBean.getBeanName() + "' cannot support the extension bean '"
+ extensionBean.getBeanName() + "'");
// extensionBean添加到endpointBean中
endpointBean.addExtension(extensionBean);
}
}
(3)convertToEndpoints
将EndpointBean转换成Endpoint。
private Collection convertToEndpoints(Collection endpointBeans) {
Set 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对应一个Operation注解方法
MultiValueMap indexed = new LinkedMultiValueMap<>();
EndpointId id = endpointBean.getId();
// 解析@ReadOperation、@WriteOperation、@DeleteOperation,并记录
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 + ")");
}
// 继续扩展ExtensionBean中的@ReadOperation、@WriteOperation、@DeleteOperation,并记录
for (ExtensionBean extensionBean : endpointBean.getExtensions()) {
addOperations(indexed, id, extensionBean.getBean(), true);
}
// 判断在一个Endpoint中是否存在多个相同的@*Operation注解
// 即同一个注解只能在一个Endpoint用一次
assertNoDuplicateOperations(endpointBean, indexed);
List 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);
}
createEndpoint是抽象方法,具体由子类实现,创建什么类型的Endpoint,由子类决定:
protected abstract E createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault,
Collection operations);
WebEndpointDiscoverer实现createEndpoint方法,创建DiscoveredWebEndpoint对象:
@Override
protected ExposableWebEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault,
Collection operations) {
String rootPath = PathMapper.getRootPath(this.endpointPathMappers, id);
// 创建DiscoveredWebEndpoint
return new DiscoveredWebEndpoint(this, endpointBean, id, rootPath, enabledByDefault, operations);
}
2、servletEndpointsSupplier#getEndpoints:
即调用ServletEndpointDiscoverer,ServletEndpointDiscoverer与webEndpointsSupplier有共同的父类EndpointDiscoverer,前面所有的方法调用都相同,就是最后createEndpoint实现不同,创建DiscoveredServletEndpoint对象:
@Override
protected ExposableServletEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault,
Collection operations) {
String rootPath = PathMapper.getRootPath(this.endpointPathMappers, id);
return new DiscoveredServletEndpoint(this, endpointBean, id, rootPath, enabledByDefault);
}
3、controllerEndpointsSupplier#getEndpoints:
调用的是ControllerEndpointDiscoverer,创建DiscoveredControllerEndpoint对象:
@Override
protected ExposableControllerEndpoint createEndpoint(Object endpointBean, EndpointId id, boolean enabledByDefault,
Collection operations) {
String rootPath = PathMapper.getRootPath(this.endpointPathMappers, id);
return new DiscoveredControllerEndpoint(this, endpointBean, id, rootPath, enabledByDefault);
}
补充:如何判断重复Operation方法:
在《初识篇》的末尾测试了在同一个Endpoint多次使用同一个Operation注解的情况,下面来具体看看对于重复的Operation是如何判断的。
在上面的代码中,convertToEndpoint方法中有解析Endpoint中Operation注解的过程,解析后的结果放进了MultiValueMap中:
MultiValueMap indexed = new LinkedMultiValueMap<>();
MultiValueMap是一个键可以对应多个值的map,一个OperationKey就是对应一个Operation注解方法。当一个Operation注解方法对应了相同的OperationKey就表示重复了。所以需要查看对于OperationKey是如何判断是否是同一个对象的。判断是MultiValueMap的功能,通过key的equals与hashCode方法,查看OperationKey的equals和hashCode方法:
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
return this.key.equals(((OperationKey) obj).key);
}
@Override
public int hashCode() {
return this.key.hashCode();
}
从equals方法可以看出,对于不同的Operation对象,如何它们的key相等就表示相等了,于是下面就要对key的比较了,key是在调用构造器的时候被赋值的:
public OperationKey(Object key, Supplier description) {
Assert.notNull(key, "Key must not be null");
Assert.notNull(description, "Description must not be null");
this.key = key;
this.description = description;
}
在JmxEndpointDiscoverer调用,key是Operation的name属性值(operation#getName),也就是方法名,是String类型的,通过String的equals方法比较两个对象:
@Override
protected OperationKey createOperationKey(JmxOperation operation) {
return new OperationKey(operation.getName(), () -> "MBean call '" + operation.getName() + "'");
}
在WebEndpointDiscoverer调用,key是requestPredicate属性值,是WebOperationRequestPredicate类型的,通过WebOperationRequestPredicate的equals方法比较两个对象:
@Override
protected OperationKey createOperationKey(WebOperation operation) {
return new OperationKey(operation.getRequestPredicate(),
() -> "web request predicate " + operation.getRequestPredicate());
}
查看WebOperationRequestPredicate的equals方法,需要consumes,httpMethod(Operation注解类型),id(注解@Endpoint的id属性值),produces的值全都相等,两个对象才是相等的。在同一个WEB Endpoint中,同一种Operation注解方法的httpMethod和id都是相同的:
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
WebOperationRequestPredicate other = (WebOperationRequestPredicate) obj;
boolean result = true;
result = result && this.consumes.equals(other.consumes);
result = result && this.httpMethod == other.httpMethod;
result = result && this.canonicalPath.equals(other.canonicalPath);
result = result && this.produces.equals(other.produces);
return result;
}
综上所述,对于JMX Endpoint,在同一个Endpoint中,重复的Operation的判断条件就是方法名是否相同。对于WEB Endpoint,在同一个Endpoint中,重复的Operation的判断条件就是consumes,httpMethod,id,produces的值全都相等。
WebMvcEndpointHandlerMapping
new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
shouldRegisterLinksMapping);
类图:
WebMvcEndpointHandlerMapping继承抽象类AbstractWebMvcEndpointHandlerMapping,间接实现了InitializingBean。Spring初始化Bean的时候,会调用InitializingBean的afterPropertiesSet。所以入口就从afterPropertiesSet的方法实现找起,在抽象类AbstractHandlerMethodMapping中实现了afterPropertiesSet方法:
@Override
public void afterPropertiesSet() {
// 调用 initHandlerMethods
initHandlerMethods();
}
(1)此时调用的initHandlerMethods方法并不是AbstractHandlerMethodMapping中自身的这个方法,因为在AbstractWebMvcEndpointHandlerMapping中重写了此方法:
// AbstractWebMvcEndpointHandlerMapping#initHandlerMethods
@Override
protected void initHandlerMethods() {
// 遍历webEndpoints集合
for (ExposableWebEndpoint endpoint : this.endpoints) {
for (WebOperation operation : endpoint.getOperations()) {
// 创建和注册请求接口
registerMappingForOperation(endpoint, operation);
}
}
// 注册根目录接口,即/actuator
if (this.shouldRegisterLinksMapping) {
registerLinksMapping();
}
}
this.endpoints是在创建WebMvcEndpointHandlerMapping对象的时候,传进来的参数webEndpoints:
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping, Collection endpoints,
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
EndpointLinksResolver linksResolver, boolean shouldRegisterLinksMapping) {
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, shouldRegisterLinksMapping);
this.linksResolver = linksResolver;
setOrder(-100);
}
(2)registerMappingForOperation
// AbstractWebMvcEndpointHandlerMapping#registerMappingForOperation
private void registerMappingForOperation(ExposableWebEndpoint endpoint, WebOperation operation) {
// 获取请求方式、consumers、produces等值
WebOperationRequestPredicate predicate = operation.getRequestPredicate();
// 请求路径,即@Endpoint注解的id值
String path = predicate.getPath();
String matchAllRemainingPathSegmentsVariable = predicate.getMatchAllRemainingPathSegmentsVariable();
if (matchAllRemainingPathSegmentsVariable != null) {
path = path.replace("{*" + matchAllRemainingPathSegmentsVariable + "}", "**");
}
// 创建ServletWebOperation,可以理解成Spring MVC中的HandlerAdapter
ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint, operation,
new ServletWebOperationAdapter(operation));
// createRequestMappingInfo创建HanlderMapping接口路径映射信息
registerMapping(createRequestMappingInfo(predicate, path), new OperationHandler(servletWebOperation),
this.handleMethod);
}
(3)registerMapping
public void registerMapping(T mapping, Object handler, Method method) {
if (logger.isTraceEnabled()) {
logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
}
// 往Spring容器中注册接口信息
this.mappingRegistry.register(mapping, handler, method);
}
接下来就是Spring MVC的功能了。