Springboot Endpoint之二:Endpoint源碼剖析

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

1、類圖

首先找到AbstractEndpoint,服務器

 

一、端點接口(Endpoint<T>)

/**
* 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);
}

}

 


二、MVC層策略端點(MvcEndpoint)

/**
* 實現類容許使用@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();

}

 


三、端點MVC適配器(EndpointMvcAdapter)

/**
* 暴露端點({@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 "";
 
}

 

五、MVC方式暴露端點的配置(EndpointWebMvcManagementContextConfiguration)

/**
 * 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);
    }
 
}

 

5.一、MVC端點註冊表(MvcEndpoints)

/**
 * 全部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.三、組件存在條件(OnBeanCondition)

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;
 
}

 


六、shutdown端點示例

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端點實現原理就所有分析完成。

相關文章
相關標籤/搜索