本文接着《Springboot Actuator之七:actuator 中原生endpoint源碼解析1》,前面主要分析了原生endpoint的做用。html
如今着重瞭解actuator的執行原理。spring
在前面一篇文章中,咱們已經瞭解endpoint的暴露方式有http(spring MVC)協議,jmx協議。json
總體實現思路是將端點(Endpoint)適配委託給MVC層策略端點(MvcEndpoint),再經過端點MVC適配器(EndpointMvcAdapter)將端點暴露爲HTTP請求方式的MVC端點,最後分別使用端點自動配置(EndpointAutoConfiguration)和MVC方式暴露端點的配置(EndpointWebMvcManagementContextConfiguration)來注入端點組件和端點處理程序映射組件、MVC端點註冊表組件、MVC端點組件。安全
其中,端點處理程序映射(EndpointHandlerMapping)經過Spring MVC方式來暴露MVC端點。最後,本文以「shutdown端點示例」收尾。服務器
如今就按照總體實現思路來剖析HTTP端點的實現原理。mvc
其抽象實現基類 AbstractEndpoint<T>app
/** * 實現類容許使用@RequestMapping和完整的Spring MVC機制, * 但不能在類型級別使用@Controller或@RequestMapping,由於這將致使路徑的雙重映射, * 一次經過常規MVC處理程序映射,一次經過{@link EndpointHandlerMapping}。 * * @author Dave Syer * @see NamedMvcEndpoint */ // 核心接口 在端點之上的MVC層策略 public interface MvcEndpoint { /** * 禁用端點的響應實體 */ ResponseEntity<Map<String, String>> DISABLED_RESPONSE = new ResponseEntity<>( Collections.singletonMap("message", "This endpoint is disabled"), HttpStatus.NOT_FOUND); // 核心方法 返回端點的MVC路徑 String getPath(); /** * 返回端點是否暴露敏感信息。 */ boolean isSensitive(); // 核心方法 返回端點暴露的類型/null @SuppressWarnings("rawtypes") Class<? extends Endpoint> getEndpointType(); }
/** * 名稱提供了引用端點的一致方式。 * * @author Madhura Bhave * @since 1.5.0 */ // 包括邏輯名稱的MVC端點 public interface NamedMvcEndpoint extends MvcEndpoint { /** * 返回端點的邏輯名稱。 */ String getName(); } }
/** * 暴露端點({@link Endpoint})爲MVC端點({@link MvcEndpoint})的適配器。 */ // 端點MVC適配器 public class EndpointMvcAdapter extends AbstractEndpointMvcAdapter<Endpoint<?>> { /** * Create a new {@link EndpointMvcAdapter}. * @param delegate the underlying {@link Endpoint} to adapt. (用於適配的底層端點) */ public EndpointMvcAdapter(Endpoint<?> delegate) { super(delegate); // 委託代理 } // 核心實現 以HTTP GET方式調用 @Override @ActuatorGetMapping @ResponseBody public Object invoke() { return super.invoke(); // 向上調用,鏈式模式 } }
@ActuatorGetMapping註解源碼:cors
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @RequestMapping(method = RequestMethod.GET, produces = { ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) @interface ActuatorGetMapping { /** * Alias for {@link RequestMapping#value}. * @return the value */ @AliasFor(annotation = RequestMapping.class) String[] value() default {}; }
其抽象實現基類 AbstractEndpointMvcAdapter<E extends Endpoint<?>>ide
/** * MVC端點({@link MvcEndpoint})實現的抽象基類。 * * @param <E> * The delegate endpoint (代理的端點) * @author Dave Syer * @since 1.3.0 */ public abstract class AbstractEndpointMvcAdapter<E extends Endpoint<?>> implements NamedMvcEndpoint { /** * 被代理的底層端點(端點子類) */ private final E delegate; /** * 端點URL路徑 */ private String path; public AbstractEndpointMvcAdapter(E delegate) { Assert.notNull(delegate, "Delegate must not be null"); this.delegate = delegate; } // 核心實現 調用底層端點,並返回調用結果 protected Object invoke() { if (!this.delegate.isEnabled()) { // 端點被禁用 // Shouldn't happen - shouldn't be registered when delegate's disabled return getDisabledResponse(); } return this.delegate.invoke(); // 調用端點 } public E getDelegate() { return this.delegate; } @Override public String getName() { return this.delegate.getId(); // name = id } @Override public String getPath() { return (this.path != null ? this.path : "/" + this.delegate.getId()); // "/id" } public void setPath(String path) { while (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } if (!path.startsWith("/")) { path = "/" + path; } this.path = path; } @Override @SuppressWarnings("rawtypes") public Class<? extends Endpoint> getEndpointType() { return this.delegate.getClass(); } }
四、端點組件自動配置spring-boot
基於Spring Boot的自動配置機制(Auto-configuration),其自動配置文件位於spring-boot-actuator資源目錄下的META-INF/spring.factories文件:
# 啓用自動配置 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ ... org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration,\ ... # 管理上下文配置 org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration=\ org.springframework.boot.actuate.autoconfigure.EndpointWebMvcManagementContextConfiguration,\ ...
4.一、公共管理的端點自動配置(EndpointAutoConfiguration)
4.二、全局的端點屬性(EndpointProperties)
4.三、外部化配置的註解(ConfigurationProperties)
/** * 若是要綁定和驗證一些外部屬性(例如來自.properties文件),請將其添加到@Configuration類中的類定義或@Bean方法。 */ // 外部化配置的註解 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ConfigurationProperties { // 屬性的名稱前綴 @AliasFor("value") String prefix() default ""; }
五、MVC方式暴露端點的配置(EndpointWebMvcManagementContextConfiguration)
// 核心類 經過MVC方式來暴露端點的配置 @ManagementContextConfiguration @EnableConfigurationProperties({ HealthMvcEndpointProperties.class, EndpointCorsProperties.class }) public class EndpointWebMvcManagementContextConfiguration { private final HealthMvcEndpointProperties healthMvcEndpointProperties; /** * 管理服務器的屬性 */ private final ManagementServerProperties managementServerProperties; private final EndpointCorsProperties corsProperties; /** * 端點處理程序的映射定製程序 */ private final List<EndpointHandlerMappingCustomizer> mappingCustomizers; // 核心方法 注入端點處理程序映射組件 @Bean @ConditionalOnMissingBean // 組件未注入 public EndpointHandlerMapping endpointHandlerMapping() { // 註冊的MVC端點集合 Set<MvcEndpoint> endpoints = mvcEndpoints().getEndpoints(); CorsConfiguration corsConfiguration = getCorsConfiguration(this.corsProperties); // 端點處理程序映射 EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints, corsConfiguration); // 管理端點的上下文路徑前綴 mapping.setPrefix(this.managementServerProperties.getContextPath()); // MVC端點安全處理程序攔截器 MvcEndpointSecurityInterceptor securityInterceptor = new MvcEndpointSecurityInterceptor( this.managementServerProperties.getSecurity().isEnabled(), this.managementServerProperties.getSecurity().getRoles()); mapping.setSecurityInterceptor(securityInterceptor); for (EndpointHandlerMappingCustomizer customizer : this.mappingCustomizers) { customizer.customize(mapping); } return mapping; } // 核心方法 注入MVC端點註冊表組件 @Bean @ConditionalOnMissingBean // 組件未注入 public MvcEndpoints mvcEndpoints() { return new MvcEndpoints(); } @Bean @ConditionalOnBean(EnvironmentEndpoint.class) @ConditionalOnEnabledEndpoint("env") public EnvironmentMvcEndpoint environmentMvcEndpoint(EnvironmentEndpoint delegate) { return new EnvironmentMvcEndpoint(delegate); } @Bean @ConditionalOnBean(HealthEndpoint.class) @ConditionalOnEnabledEndpoint("health") public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate) { HealthMvcEndpoint healthMvcEndpoint = new HealthMvcEndpoint(delegate, this.managementServerProperties.getSecurity().isEnabled()); if (this.healthMvcEndpointProperties.getMapping() != null) { healthMvcEndpoint.addStatusMapping(this.healthMvcEndpointProperties.getMapping()); } return healthMvcEndpoint; } // 注入關閉應用程序的MVC端點組件 @Bean @ConditionalOnBean(ShutdownEndpoint.class) // 組件已實例化 @ConditionalOnEnabledEndpoint(value = "shutdown", enabledByDefault = false) // 端點已啓用 public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) { return new ShutdownMvcEndpoint(delegate); } }
5.一、MVC端點註冊表(MvcEndpoints)
一、MvcEndpoints實現了ApplicationContextAware,取ApplicationContext;
二、MvcEndpoints實現了InitializingBean ,在容器啓動後調用afterPropertiesSet()方法適配那些通用的endpoint;
* 全部MVC端點組件的註冊表,以及一組用於包裝還沒有公開的MVC端點的現有端點實例的通用工廠。 */ // 核心類 MVC端點註冊表 public class MvcEndpoints implements ApplicationContextAware, InitializingBean { /** * 應用上下文 */ private ApplicationContext applicationContext; /** * MVC端點集合 */ private final Set<MvcEndpoint> endpoints = new HashSet<>(); /** * MVC端點類型集合 */ private Set<Class<?>> customTypes; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Override public void afterPropertiesSet() throws Exception { //一、從applicationContext中檢索出來已經實例化的MVC端點列表 Collection<MvcEndpoint> existing = BeanFactoryUtils .beansOfTypeIncludingAncestors(this.applicationContext, MvcEndpoint.class) // MVC端點 .values(); this.endpoints.addAll(existing); this.customTypes = findEndpointClasses(existing); //二、從applicationContext中檢索出來已經實例化的代理端點列表 @SuppressWarnings("rawtypes") Collection<Endpoint> delegates = BeanFactoryUtils .beansOfTypeIncludingAncestors(this.applicationContext, Endpoint.class) // 端點 .values(); for (Endpoint<?> endpoint : delegates) { //三、判斷是不是通用的endpoint if (isGenericEndpoint(endpoint.getClass()) && endpoint.isEnabled()) { EndpointMvcAdapter adapter = new EndpointMvcAdapter(endpoint); // 端點MVC適配器 // 端點路徑 String path = determinePath(endpoint, this.applicationContext.getEnvironment()); if (path != null) { adapter.setPath(path); } this.endpoints.add(adapter); } } } private Set<Class<?>> findEndpointClasses(Collection<MvcEndpoint> existing) { Set<Class<?>> types = new HashSet<>(); for (MvcEndpoint endpoint : existing) { Class<?> type = endpoint.getEndpointType(); // 端點類型 if (type != null) { types.add(type); } } return types; } // 核心方法 返回註冊的MVC端點集合 public Set<MvcEndpoint> getEndpoints() { return this.endpoints; } /** * 返回指定類型的MVC端點集合。 */ @SuppressWarnings("unchecked") public <E extends MvcEndpoint> Set<E> getEndpoints(Class<E> type) { Set<E> result = new HashSet<>(this.endpoints.size()); for (MvcEndpoint candidate : this.endpoints) { if (type.isInstance(candidate)) { result.add((E) candidate); } } return Collections.unmodifiableSet(result); // 不可修改的集合 } // 判斷是不是通用的端點 private boolean isGenericEndpoint(Class<?> type) { return //第一步中掃描的已經實例化的MVC端點中不存在 !this.customTypes.contains(type) //肯定此類對象表示的類或接口是否與由指定的類參數表示的類或接口相同,或者是該類或接口的超類或超接口。 && !MvcEndpoint.class.isAssignableFrom(type); } private String determinePath(Endpoint<?> endpoint, Environment environment) { // 配置屬性 ConfigurationProperties configurationProperties = AnnotationUtils.findAnnotation(endpoint.getClass(), ConfigurationProperties.class); if (configurationProperties != null) { return environment.getProperty(configurationProperties.prefix() + ".path"); } return null; } }
5.二、端點處理程序映射(EndpointHandlerMapping)
/** * handlerMapping經過endpoint.getid()將端點映射到URL。@RequestMapping的語義應該與普通的@Controller相同, * 可是端點不該該被註釋爲@Controller,不然它們將被正常的MVC機制映射。 <p> * <p> * 映射的目標之一是支持做爲HTTP端點工做的端點, 可是當沒有HTTP服務器(類路徑上沒有Spring MVC)時,仍然能夠提供有用的服務接口。 * 注意:具備方法簽名的任何端點將在非Servlet環境下中斷。 */ // 核心類 經過端點的邏輯標識將端點映射到URL的處理程序映射 public class EndpointHandlerMapping extends AbstractEndpointHandlerMapping<MvcEndpoint> { /** * Create a new {@link EndpointHandlerMapping} instance. All {@link Endpoint}s * will be detected from the {@link ApplicationContext}. The endpoints will * accepts CORS requests based on the given {@code corsConfiguration}. * * @param endpoints * the endpoints (MVC端點列表) * @param corsConfiguration * the CORS configuration for the endpoints * @since 1.3.0 */ public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints, CorsConfiguration corsConfiguration) { super(endpoints, corsConfiguration); } }
其抽象實現基類 AbstractEndpointHandlerMapping<E extends MvcEndpoint>
package com.dxz.inject; /** * @RequestMapping的語義應該與普通的@Controller相同, 可是端點不該該被註釋爲@Controller,不然它們將被正常的MVC機制映射。 * 映射的目標之一是支持做爲HTTP端點工做的端點,可是當沒有HTTP服務器(類路徑上沒有Spring MVC)時,仍然能夠提供有用的服務接口。 * 注意:具備方法簽名的任何端點將在非Servlet環境下中斷。 * * @param <E> * The endpoint type (端點類型) */ // 核心類 經過端點的邏輯標識將端點映射到URL的處理程序映射的抽象基類 public abstract class AbstractEndpointHandlerMapping<E extends MvcEndpoint> extends RequestMappingHandlerMapping { /** * MVC端點集合 */ private final Set<E> endpoints; /** * 安全處理程序攔截器 */ private HandlerInterceptor securityInterceptor; /** * CORS配置 */ private final CorsConfiguration corsConfiguration; /** * 端點的映射路徑前綴 */ private String prefix = ""; private boolean disabled = false; /** * <p> * 將從應用上下文檢測到全部端點。 */ public AbstractEndpointHandlerMapping(Collection<? extends E> endpoints, CorsConfiguration corsConfiguration) { this.endpoints = new HashSet<>(endpoints); postProcessEndpoints(this.endpoints); this.corsConfiguration = corsConfiguration; // By default the static resource handler mapping is LOWEST_PRECEDENCE - 1 // and the RequestMappingHandlerMapping is 0 (we ideally want to be before both) // 默認狀況下,靜態資源處理程序映射的順序是 LOWEST_PRECEDENCE - 1 setOrder(-100); setUseSuffixPatternMatch(false); } /** * Post process the endpoint setting before they are used. Subclasses can add or * modify the endpoints as necessary. * <p> * 在使用以前,後處理端點設置。 * * @param endpoints * the endpoints to post process */ protected void postProcessEndpoints(Set<E> endpoints) { } @Override public void afterPropertiesSet() { super.afterPropertiesSet(); if (!this.disabled) { // 端點處理程序被禁用 for (MvcEndpoint endpoint : this.endpoints) { detectHandlerMethods(endpoint); } } } // 核心實現 註冊端點處理程序方法及其惟一映射 @Override @Deprecated protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) { if (mapping == null) { return; } String[] patterns = getPatterns(handler, mapping); if (!ObjectUtils.isEmpty(patterns)) { super.registerHandlerMethod(handler, method, withNewPatterns(mapping, patterns)); } } private String[] getPatterns(Object handler, RequestMappingInfo mapping) { if (handler instanceof String) { // 組件名稱 handler = getApplicationContext().getBean((String) handler); } Assert.state(handler instanceof MvcEndpoint, "Only MvcEndpoints are supported"); String path = getPath((MvcEndpoint) handler); // MVC端點路徑 return (path == null ? null : getEndpointPatterns(path, mapping)); } protected String getPath(MvcEndpoint endpoint) { return endpoint.getPath(); } // 核心實現 返回端點的路徑列表 private String[] getEndpointPatterns(String path, RequestMappingInfo mapping) { // 路徑模式前綴 String patternPrefix = StringUtils.hasText(this.prefix) ? this.prefix + path : path; // 默認的路徑集合 Set<String> defaultPatterns = mapping.getPatternsCondition().getPatterns(); if (defaultPatterns.isEmpty()) { // 端點路徑 return new String[] { patternPrefix, patternPrefix + ".json" }; } List<String> patterns = new ArrayList<>(defaultPatterns); for (int i = 0; i < patterns.size(); i++) { patterns.set(i, patternPrefix + patterns.get(i)); // 端點請求路徑 } return patterns.toArray(new String[patterns.size()]); } // 新的端點路徑 private RequestMappingInfo withNewPatterns(RequestMappingInfo mapping, String[] patternStrings) { // 模式請求條件 PatternsRequestCondition patterns = new PatternsRequestCondition(patternStrings, null, null, useSuffixPatternMatch(), useTrailingSlashMatch(), null); return new RequestMappingInfo(patterns, mapping.getMethodsCondition(), mapping.getParamsCondition(), mapping.getHeadersCondition(), mapping.getConsumesCondition(), mapping.getProducesCondition(), mapping.getCustomCondition()); } // 核心實現 獲取處理程序執行鏈 @Override protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) { HandlerExecutionChain chain = super.getHandlerExecutionChain(handler, request); if (this.securityInterceptor == null || CorsUtils.isCorsRequest(request)) { return chain; } return addSecurityInterceptor(chain); } private HandlerExecutionChain addSecurityInterceptor(HandlerExecutionChain chain) { // 處理程序攔截器 List<HandlerInterceptor> interceptors = new ArrayList<>(); if (chain.getInterceptors() != null) { interceptors.addAll(Arrays.asList(chain.getInterceptors())); } // 添加安全處理程序攔截器 interceptors.add(this.securityInterceptor); return new HandlerExecutionChain(chain.getHandler(), interceptors.toArray(new HandlerInterceptor[interceptors.size()])); } // 獲取端點的路徑 public String getPath(String endpoint) { return this.prefix + endpoint; } // 返回MVC端點集合 public Set<E> getEndpoints() { return Collections.unmodifiableSet(this.endpoints); // 不可修改的集合 } }
5.三、組件存在條件(OnBeanCondition)
5.3.一、未注入組件條件(ConditionalOnMissingBean)
5.3.二、組件條件(ConditionalOnBean)
六、shutdown端點示例
6.一、關閉應用程序的端點(ShutdownEndpoint)
6.二、關閉應用程序的MVC端點(ShutdownMvcEndpoint)
/** * 暴露關閉應用上下文端點({@link ShutdownEndpoint})爲MVC端點({@link MvcEndpoint})的適配器。 */ // 關閉應用程序的MVC端點 @ConfigurationProperties(prefix = "endpoints.shutdown") // 屬性前綴配置 public class ShutdownMvcEndpoint extends EndpointMvcAdapter { public ShutdownMvcEndpoint(ShutdownEndpoint delegate) { super(delegate); // 委託代理 } // 核心實現 以HTTP POST方式調用 @PostMapping(produces = { ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE, MediaType.APPLICATION_JSON_VALUE }) @ResponseBody @Override public Object invoke() { if (!getDelegate().isEnabled()) { // 端點被禁用 return getDisabledResponse(); } return super.invoke(); // 向上調用,鏈式模式 } }
原文:https://blog.csdn.net/shupili141005/article/details/61476546