随笔- 2038  评论- 180  文章- 4 

Springboot Actuator之八:actuator的执行原理

本文接着《Springboot Actuator之七:actuator 中原生endpoint源码解析1》,前面主要分析了原生endpoint的作用。

现在着重了解actuator的执行原理。

在前面一篇文章中,我们已经了解endpoint的暴露方式有http(spring MVC)协议,jmx协议。

整体实现思路是将端点(Endpoint)适配委托给MVC层策略端点(MvcEndpoint),再通过端点MVC适配器(EndpointMvcAdapter)将端点暴露为HTTP请求方式的MVC端点,最后分别使用端点自动配置(EndpointAutoConfiguration)和MVC方式暴露端点的配置(EndpointWebMvcManagementContextConfiguration)来注入端点组件和端点处理程序映射组件、MVC端点注册表组件、MVC端点组件。

其中,端点处理程序映射(EndpointHandlerMapping)通过Spring MVC方式来暴露MVC端点。最后,本文以“shutdown端点示例”收尾。

现在就按照整体实现思路来剖析HTTP端点的实现原理。

1、端点接口(Endpoint<T>)

其抽象实现基类 AbstractEndpoint<T>

2、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.1、包括逻辑名称的MVC端点(NamedMvcEndpoint)

/**
* 名称提供了引用端点的一致方式。
*
* @author Madhura Bhave
* @since 1.5.0
*/
// 包括逻辑名称的MVC端点
public interface NamedMvcEndpoint extends MvcEndpoint {

    /**
    * 返回端点的逻辑名称。
    */
    String getName();

    }
}

3、端点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(); // 向上调用,链式模式
   }
}

@ActuatorGetMapping注解源码:

@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<?>>

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

}

4、端点组件自动配置

基于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.1、公共管理的端点自动配置(EndpointAutoConfiguration)

4.2、全局的端点属性(EndpointProperties)

4.3、外部化配置的注解(ConfigurationProperties)

/**
 * 如果要绑定和验证一些外部属性(例如来自.properties文件),请将其添加到@Configuration类中的类定义或@Bean方法。
 */
// 外部化配置的注解
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ConfigurationProperties {

    // 属性的名称前缀
    @AliasFor("value")
    String prefix() default "";

}

5、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.1、MVC端点注册表(MvcEndpoints)

1、MvcEndpoints实现了ApplicationContextAware,取ApplicationContext;

2、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 {
        //1、从applicationContext中检索出来已经实例化的MVC端点列表
        Collection<MvcEndpoint> existing = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(this.applicationContext, MvcEndpoint.class) // MVC端点
                .values();
        this.endpoints.addAll(existing);
        this.customTypes = findEndpointClasses(existing);
        //2、从applicationContext中检索出来已经实例化的代理端点列表
        @SuppressWarnings("rawtypes")
        Collection<Endpoint> delegates = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(this.applicationContext, Endpoint.class) // 端点
                .values();
        for (Endpoint<?> endpoint : delegates) {
            //3、判断是否是通用的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.2、端点处理程序映射(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.3、组件存在条件(OnBeanCondition)
5.3.1、未注入组件条件(ConditionalOnMissingBean)

5.3.2、组件条件(ConditionalOnBean)

6、shutdown端点示例

6.1、关闭应用程序的端点(ShutdownEndpoint)

6.2、关闭应用程序的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

posted on 2019-08-05 18:06 duanxz 阅读(...) 评论(...) 编辑 收藏