spring boot 中有關endpoint的實現,細心的朋友能夠發現,在org.springframework.boot.actuate.endpoint.mvc 包下也有一系列的xxxEndpoint,這又是爲何呢?spring
緣由是: 咱們不少狀況下,都是訪問接口的方式獲取應用的監控,以前的分析是其實現的底層,要想實現經過接口訪問,還須要對其進行包裝一番,org.springframework.boot.actuate.endpoint.mvc 包下的實現就是乾的這種事,下面,先看下springboot的actuator包的mvc下定義的類:express
總體實現思路是將端點(Endpoint)適配委託給MVC層策略端點(MvcEndpoint),再經過端點MVC適配器(EndpointMvcAdapter)將端點暴露爲HTTP請求方式的MVC端點,最後分別使用端點自動配置(EndpointAutoConfiguration)和MVC方式暴露端點的配置(EndpointWebMvcManagementContextConfiguration)來注入端點組件和端點處理程序映射組件、MVC端點註冊表組件、MVC端點組件。json
其中,端點處理程序映射(EndpointHandlerMapping)經過Spring MVC方式來暴露MVC端點。最後,本文以「shutdown端點示例」收尾。安全
如今就按照總體實現思路來剖析HTTP端點的實現原理。 springboot
首先找到AbstractEndpoint,服務器
/** * An endpoint that can be used to expose useful information to operations. Usually * exposed via Spring MVC but could also be exposed using some other technique. Consider * extending {@link AbstractEndpoint} if you are developing your own endpoint. * <p>一個端點能夠用於暴露操做的實用信息。 * * @param <T> the endpoint data type (端點數據類型) * @see AbstractEndpoint */ // 核心接口 端點接口 public interface Endpoint<T> { /** * 端點的邏輯標識(字母、數字和下劃線('_')) */ String getId(); /** * 端點是否啓用 */ boolean isEnabled(); /** * 端點是否輸出敏感數據(安全提示) */ boolean isSensitive(); // 核心接口 調用端點,並返回調用結果 T invoke(); }
其抽象實現基類 AbstractEndpoint<T>mvc
/** * Abstract base for {@link Endpoint} implementations. * * @param <T> the endpoint data type (端點數據類型) */ // 核心類 端點實現的抽象基類 public abstract class AbstractEndpoint<T> implements Endpoint<T>, EnvironmentAware { private Environment environment; /** * Endpoint identifier. With HTTP monitoring the identifier of the endpoint is mapped * to a URL (e.g. 'foo' is mapped to '/foo'). * 端點標識符 */ private String id; /** * Mark if the endpoint exposes sensitive information. */ private Boolean sensitive; /** * 是否啓動端點 */ private Boolean enabled; public AbstractEndpoint(String id, boolean sensitive, boolean enabled) { setId(id); this.sensitiveDefault = sensitive; this.enabled = enabled; } @Override public void setEnvironment(Environment environment) { this.environment = environment; } public void setId(String id) { Assert.notNull(id, "Id must not be null"); Assert.isTrue(ID_PATTERN.matcher(id).matches(), "Id must only contains letters, numbers and '_'"); this.id = id; } @Override public boolean isEnabled() { return EndpointProperties.isEnabled(this.environment, this.enabled); } }
/** * 實現類容許使用@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(); }
2.一、包括邏輯名稱的MVC端點(NamedMvcEndpoint)app
/** * 名稱提供了引用端點的一致方式。 * * @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(); // 向上調用,鏈式模式 } }
其抽象實現基類 AbstractEndpointMvcAdapter<E extends Endpoint<?>>cors
/** * 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的自動配置機制(Auto-configuration),其自動配置文件位於spring-boot-actuator資源目錄下的META-INF/spring.factories文件:ide
# 啓用自動配置 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)
/** * {@link EnableAutoConfiguration Auto-configuration} for common management * {@link Endpoint}s. */ // 核心類 公共管理的端點自動配置 @Configuration // 組件配置 @AutoConfigureAfter({ FlywayAutoConfiguration.class, LiquibaseAutoConfiguration.class }) @EnableConfigurationProperties(EndpointProperties.class) // 啓用配置屬性(端點屬性) public class EndpointAutoConfiguration { @Bean @ConditionalOnMissingBean public EnvironmentEndpoint environmentEndpoint() { return new EnvironmentEndpoint(); } @Bean @ConditionalOnMissingBean public HealthEndpoint healthEndpoint() { return new HealthEndpoint( this.healthAggregator == null ? new OrderedHealthAggregator() : this.healthAggregator, this.healthIndicators == null ? Collections.<String, HealthIndicator>emptyMap() : this.healthIndicators); } @Bean @ConditionalOnMissingBean public TraceEndpoint traceEndpoint() { return new TraceEndpoint(this.traceRepository == null ? new InMemoryTraceRepository() : this.traceRepository); } @Bean @ConditionalOnBean(ConditionEvaluationReport.class) @ConditionalOnMissingBean(search = SearchStrategy.CURRENT) public AutoConfigurationReportEndpoint autoConfigurationReportEndpoint() { return new AutoConfigurationReportEndpoint(); } @Bean @ConditionalOnMissingBean public ShutdownEndpoint shutdownEndpoint() { return new ShutdownEndpoint(); } }
4.二、全局的端點屬性(EndpointProperties)
/** * Global endpoint properties. * <p>全局的端點屬性。 * * @since 1.3.0 */ @ConfigurationProperties(prefix = "endpoints") // 端點屬性配置前綴 public class EndpointProperties { private static final String ENDPOINTS_ENABLED_PROPERTY = "endpoints.enabled"; private static final String ENDPOINTS_SENSITIVE_PROPERTY = "endpoints.sensitive"; /** * Enable endpoints. * 啓用端點 */ private Boolean enabled = true; /** * Default endpoint sensitive setting. */ private Boolean sensitive; public static boolean isEnabled(Environment environment, Boolean enabled) { if (enabled != null) { return enabled; } if (environment != null && environment.containsProperty(ENDPOINTS_ENABLED_PROPERTY)) { return environment.getProperty(ENDPOINTS_ENABLED_PROPERTY, Boolean.class); } return true; } }
4.三、外部化配置的註解(ConfigurationProperties)
/** * 若是要綁定和驗證一些外部屬性(例如來自.properties文件),請將其添加到@Configuration類中的類定義或@Bean方法。 * <p> * Note that contrary to {@code @Value}, SpEL expressions are not evaluated since property * values are externalized. * * @author Dave Syer * @see ConfigurationPropertiesBindingPostProcessor * @see EnableConfigurationProperties */ // 外部化配置的註解 @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ConfigurationProperties { // 屬性的名稱前綴 @AliasFor("value") String prefix() default ""; }
/** * Configuration to expose {@link Endpoint} instances over Spring MVC. * * @author Dave Syer * @since 1.3.0 */ // 核心類 經過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); } }
/** * 全部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 { // 現有的MVC端點列表 Collection<MvcEndpoint> existing = BeanFactoryUtils .beansOfTypeIncludingAncestors(this.applicationContext, MvcEndpoint.class) // MVC端點 .values(); this.endpoints.addAll(existing); this.customTypes = findEndpointClasses(existing); // 現有的代理端點列表 @SuppressWarnings("rawtypes") Collection<Endpoint> delegates = BeanFactoryUtils .beansOfTypeIncludingAncestors(this.applicationContext, Endpoint.class) // 端點 .values(); for (Endpoint<?> endpoint : delegates) { 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 !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)
/** * @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>
/** * @RequestMapping的語義應該與普通的@Controller相同, * 可是端點不該該被註釋爲@Controller,不然它們將被正常的MVC機制映射。 * <p>映射的目標之一是支持做爲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; /** * Create a new {@link AbstractEndpointHandlerMapping} instance. All {@link Endpoint}s * will be detected from the {@link ApplicationContext}. The endpoints will accepts * CORS requests based on the given {@code corsConfiguration}. * <p>將從應用上下文檢測到全部端點。 * @param endpoints the endpoints * @param corsConfiguration the CORS configuration for the endpoints * @since 1.3.0 */ 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.3.一、未注入組件條件(ConditionalOnMissingBean)
/** * 僅當指定的組件類型或名稱還沒有包含在{@link BeanFactory}中時才匹配的條件。 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnMissingBean { /** * The class type of bean that should be checked. The condition matches when each * class specified is missing in the {@link ApplicationContext}. * @return the class types of beans to check */ // 組件的類型 Class<?>[] value() default {}; String[] type() default {}; /** * The class type of beans that should be ignored when identifying matching beans. * @return the class types of beans to ignore * @since 1.2.5 */ Class<?>[] ignored() default {}; String[] ignoredType() default {}; // 裝飾組件的註解類型 Class<? extends Annotation>[] annotation() default {}; // 組件的名稱列表 String[] name() default {}; // 應用上下文層次結構的搜索策略 SearchStrategy search() default SearchStrategy.ALL; }
5.3.二、組件條件(ConditionalOnBean)
/** * 僅當指定的組件類型或名稱已經包含在{@link BeanFactory}中時才匹配的條件 */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(OnBeanCondition.class) public @interface ConditionalOnBean { /** * The class type of bean that should be checked. The condition matches when all of * the classes specified are contained in the {@link ApplicationContext}. * @return the class types of beans to check */ // 組件的類型 Class<?>[] value() default {}; String[] type() default {}; // 裝飾組件的註解類型 Class<? extends Annotation>[] annotation() default {}; // 組件的名稱列表 String[] name() default {}; // 應用上下文層次結構的搜索策略 SearchStrategy search() default SearchStrategy.ALL; }
5.3.三、啓用端點條件(ConditionalOnEnabledEndpoint)
/** * 檢查端點是否啓用的條件。 * 若是endpoints.<name>.enabled屬性的值是true,則匹配。 * * @since 1.2.4 */ // 啓用端點上的條件 @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) @Documented @Conditional(OnEnabledEndpointCondition.class) public @interface ConditionalOnEnabledEndpoint { // 端點的名稱 String value(); /** * Returns whether or not the endpoint is enabled by default. */ boolean enabledByDefault() default true; }
6.一、關閉應用程序的端點(ShutdownEndpoint)
/** * {@link Endpoint} to shutdown the {@link ApplicationContext}. * <p>用於優雅地關閉應用上下文({@link ApplicationContext})的端點。 * 容許應用以優雅的方式關閉 * * @author Dave Syer * @author Christian Dupuis * @author Andy Wilkinson */ @ConfigurationProperties(prefix = "endpoints.shutdown") // 屬性前綴配置 public class ShutdownEndpoint extends AbstractEndpoint<Map<String, Object>> implements ApplicationContextAware { // 應用上下文感知 /** 無關閉上下文信息 */ private static final Map<String, Object> NO_CONTEXT_MESSAGE = Collections .unmodifiableMap(Collections.<String, Object>singletonMap("message", "No context to shutdown.")); /** 關閉信息 */ private static final Map<String, Object> SHUTDOWN_MESSAGE = Collections .unmodifiableMap(Collections.<String, Object>singletonMap("message", "Shutting down, bye...")); /** * 可配置的應用上下文 */ private ConfigurableApplicationContext context; /** * Create a new {@link ShutdownEndpoint} instance. */ public ShutdownEndpoint() { super("shutdown", true, false); } // 核心實現 新啓線程來關閉應用上下文,並釋放全部資源和鎖 @Override public Map<String, Object> invoke() { if (this.context == null) { return NO_CONTEXT_MESSAGE; } try { return SHUTDOWN_MESSAGE; } finally { Thread thread = new Thread(() -> { try { Thread.sleep(500L); // 使當前正在執行的線程休眠(500ms) } catch (InterruptedException ex) { Thread.currentThread().interrupt(); // 若是出現中斷異常,則中斷當前線程 } ShutdownEndpoint.this.context.close(); // 關閉應用上下文,並釋放全部資源和鎖 }); thread.setContextClassLoader(getClass().getClassLoader()); // 本類的類加載器 thread.start(); } } // 核心實現 設置可配置的應用上下文 @Override public void setApplicationContext(ApplicationContext context) throws BeansException { if (context instanceof ConfigurableApplicationContext) { this.context = (ConfigurableApplicationContext) context; } } }
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(); // 向上調用,鏈式模式 } }
至此,HTTP端點實現原理就所有分析完成。