spring boot actuator的官方文档地址:https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready.html

1.增加actuator支持

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
</dependencies>

2.修改配置

示例:

endpoints.actuator.enabled=true
endpoints.actuator.sensitive=false
endpoints.beans.sensitive=false
endpoints.beans.enabled=true
endpoints.health.sensitive=false
endpoints.health.enabled=true
management.security.enabled=false

红色部分重要,默认是需要身份认证的,一些页面不能访问,加上后所有页面不需要认证,都可以访问。

3.启动效果如下:

2017-04-07 14:42:46.569  INFO 10912 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-04-07 14:42:46.569  INFO 10912 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-04-07 14:42:46.621  INFO 10912 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@3224bdee: startup date [Fri Apr 07 14:42:43 CST 2017]; root of context hierarchy
2017-04-07 14:42:47.127  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.MetricsMvcEndpoint.value(java.lang.String)
2017-04-07 14:42:47.127  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/metrics || /metrics.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-04-07 14:42:47.128  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/mappings || /mappings.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-04-07 14:42:47.128  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EnvironmentMvcEndpoint.value(java.lang.String)
2017-04-07 14:42:47.128  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/env || /env.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-04-07 14:42:47.129  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/configprops || /configprops.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-04-07 14:42:47.129  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/dump || /dump.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-04-07 14:42:47.129  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/health || /health.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.HealthMvcEndpoint.invoke(javax.servlet.http.HttpServletRequest)
2017-04-07 14:42:47.129  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/trace || /trace.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-04-07 14:42:47.130  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/autoconfig || /autoconfig.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-04-07 14:42:47.130  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/info || /info.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-04-07 14:42:47.131  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/auditevents || /auditevents.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public org.springframework.http.ResponseEntity<?> org.springframework.boot.actuate.endpoint.mvc.AuditEventsMvcEndpoint.findByPrincipalAndAfterAndType(java.lang.String,java.util.Date,java.lang.String)
2017-04-07 14:42:47.131  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/heapdump || /heapdump.json],methods=[GET],produces=[application/octet-stream]}" onto public void org.springframework.boot.actuate.endpoint.mvc.HeapdumpMvcEndpoint.invoke(boolean,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse) throws java.io.IOException,javax.servlet.ServletException
2017-04-07 14:42:47.132  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/loggers/{name:.*}],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.get(java.lang.String)
2017-04-07 14:42:47.132  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/loggers/{name:.*}],methods=[POST],consumes=[application/vnd.spring-boot.actuator.v1+json || application/json],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.LoggersMvcEndpoint.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)
2017-04-07 14:42:47.132  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/loggers || /loggers.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-04-07 14:42:47.133  INFO 10912 --- [           main] o.s.b.a.e.mvc.EndpointHandlerMapping     : Mapped "{[/beans || /beans.json],methods=[GET],produces=[application/vnd.spring-boot.actuator.v1+json || application/json]}" onto public java.lang.Object org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter.invoke()
2017-04-07 14:42:47.247  INFO 10912 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-04-07 14:42:47.248  INFO 10912 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Bean with name 'dataSource' has been autodetected for JMX exposure
2017-04-07 14:42:47.251  INFO 10912 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Located MBean 'dataSource': registering with JMX server as MBean [com.zaxxer.hikari:name=dataSource,type=HikariDataSource]
2017-04-07 14:42:47.256  INFO 10912 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2017-04-07 14:42:47.326  INFO 10912 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-04-07 14:42:47.330  INFO 10912 --- [           main] xxx.xxx.xxx.Application          : Started Application in 3.642 seconds (JVM running for 6.678)
2017-04-07 14:43:05.269  INFO 10912 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-04-07 14:43:05.269  INFO 10912 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2017-04-07 14:43:05.282  INFO 10912 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 13 ms

4.工作原理分析

4.1 EndpointHandlerMapping

从上述日志中,我们可以看到映射是由EndpointHandlerMapping完成的。我们看一下EndpointHandlerMapping的定义:

/**
 * {@link HandlerMapping} to map {@link Endpoint}s to URLs via {@link Endpoint#getId()}.
 * The semantics of {@code @RequestMapping} should be identical to a normal
 * {@code @Controller}, but the endpoints should not be annotated as {@code @Controller}
 * (otherwise they will be mapped by the normal MVC mechanisms).
 * <p>
 * One of the aims of the mapping is to support endpoints that work as HTTP endpoints but
 * can still provide useful service interfaces when there is no HTTP server (and no Spring
 * MVC on the classpath). Note that any endpoints having method signatures will break in a
 * non-servlet environment.
 *
 * @author Phillip Webb
 * @author Christian Dupuis
 * @author Dave Syer
 */
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 not accept CORS
     * requests.
     * @param endpoints the endpoints
     */
    public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints) {
        super(endpoints);
    }

    /**
     * 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
     * @param corsConfiguration the CORS configuration for the endpoints
     * @since 1.3.0
     */
    public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints,
            CorsConfiguration corsConfiguration) {
        super(endpoints, corsConfiguration);
    }

}

 

4.2 EndpointWebMvcManagementContextConfiguration

EndpointHandlerMapping从哪里来的呢?EndpointWebMvcManagementContextConfiguration定义了EndpointHandlerMapping:

/**
 * Configuration to expose {@link Endpoint} instances over Spring MVC.
 *
 * @author Dave Syer
 * @author Ben Hale
 * @author Vedran Pavic
 * @since 1.3.0
 */
@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;

    public EndpointWebMvcManagementContextConfiguration(
            HealthMvcEndpointProperties healthMvcEndpointProperties,
            ManagementServerProperties managementServerProperties,
            EndpointCorsProperties corsProperties,
            ObjectProvider<List<EndpointHandlerMappingCustomizer>> mappingCustomizers) {
        this.healthMvcEndpointProperties = healthMvcEndpointProperties;
        this.managementServerProperties = managementServerProperties;
        this.corsProperties = corsProperties;
        List<EndpointHandlerMappingCustomizer> providedCustomizers = mappingCustomizers
                .getIfAvailable();
        this.mappingCustomizers = providedCustomizers == null
                ? Collections.<EndpointHandlerMappingCustomizer>emptyList()
                : providedCustomizers;
    }

    @Bean
    @ConditionalOnMissingBean
    public EndpointHandlerMapping endpointHandlerMapping() {
        Set<MvcEndpoint> endpoints = mvcEndpoints().getEndpoints();
        CorsConfiguration corsConfiguration = getCorsConfiguration(this.corsProperties);
        EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints,
                corsConfiguration);
        mapping.setPrefix(this.managementServerProperties.getContextPath());
        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;
    }

    private CorsConfiguration getCorsConfiguration(EndpointCorsProperties properties) {
        if (CollectionUtils.isEmpty(properties.getAllowedOrigins())) {
            return null;
        }
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(properties.getAllowedOrigins());
        if (!CollectionUtils.isEmpty(properties.getAllowedHeaders())) {
            configuration.setAllowedHeaders(properties.getAllowedHeaders());
        }
        if (!CollectionUtils.isEmpty(properties.getAllowedMethods())) {
            configuration.setAllowedMethods(properties.getAllowedMethods());
        }
        if (!CollectionUtils.isEmpty(properties.getExposedHeaders())) {
            configuration.setExposedHeaders(properties.getExposedHeaders());
        }
        if (properties.getMaxAge() != null) {
            configuration.setMaxAge(properties.getMaxAge());
        }
        if (properties.getAllowCredentials() != null) {
            configuration.setAllowCredentials(properties.getAllowCredentials());
        }
        return configuration;
    }

    @Bean
    @ConditionalOnMissingBean
    public MvcEndpoints mvcEndpoints() {
        return new MvcEndpoints();
    }

    @Bean
    @ConditionalOnBean(EnvironmentEndpoint.class)
    @ConditionalOnEnabledEndpoint("env")
    public EnvironmentMvcEndpoint environmentMvcEndpoint(EnvironmentEndpoint delegate) {
        return new EnvironmentMvcEndpoint(delegate);
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnEnabledEndpoint("heapdump")
    public HeapdumpMvcEndpoint heapdumpMvcEndpoint() {
        return new HeapdumpMvcEndpoint();
    }

    @Bean
    @ConditionalOnBean(HealthEndpoint.class)
    @ConditionalOnEnabledEndpoint("health")
    public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate,
            ManagementServerProperties managementServerProperties) {
        HealthMvcEndpoint healthMvcEndpoint = new HealthMvcEndpoint(delegate,
                this.managementServerProperties.getSecurity().isEnabled(),
                managementServerProperties.getSecurity().getRoles());
        if (this.healthMvcEndpointProperties.getMapping() != null) {
            healthMvcEndpoint
                    .addStatusMapping(this.healthMvcEndpointProperties.getMapping());
        }
        return healthMvcEndpoint;
    }

    @Bean
    @ConditionalOnBean(LoggersEndpoint.class)
    @ConditionalOnEnabledEndpoint("loggers")
    public LoggersMvcEndpoint loggersMvcEndpoint(LoggersEndpoint delegate) {
        return new LoggersMvcEndpoint(delegate);
    }

    @Bean
    @ConditionalOnBean(MetricsEndpoint.class)
    @ConditionalOnEnabledEndpoint("metrics")
    public MetricsMvcEndpoint metricsMvcEndpoint(MetricsEndpoint delegate) {
        return new MetricsMvcEndpoint(delegate);
    }

    @Bean
    @ConditionalOnEnabledEndpoint("logfile")
    @Conditional(LogFileCondition.class)
    public LogFileMvcEndpoint logfileMvcEndpoint() {
        return new LogFileMvcEndpoint();
    }

    @Bean
    @ConditionalOnBean(ShutdownEndpoint.class)
    @ConditionalOnEnabledEndpoint(value = "shutdown", enabledByDefault = false)
    public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) {
        return new ShutdownMvcEndpoint(delegate);
    }

    @Bean
    @ConditionalOnBean(AuditEventRepository.class)
    @ConditionalOnEnabledEndpoint("auditevents")
    public AuditEventsMvcEndpoint auditEventMvcEndpoint(
            AuditEventRepository auditEventRepository) {
        return new AuditEventsMvcEndpoint(auditEventRepository);
    }

    private static class LogFileCondition extends SpringBootCondition {

        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context,
                AnnotatedTypeMetadata metadata) {
            Environment environment = context.getEnvironment();
            String config = environment.resolvePlaceholders("${logging.file:}");
            ConditionMessage.Builder message = ConditionMessage.forCondition("Log File");
            if (StringUtils.hasText(config)) {
                return ConditionOutcome
                        .match(message.found("logging.file").items(config));
            }
            config = environment.resolvePlaceholders("${logging.path:}");
            if (StringUtils.hasText(config)) {
                return ConditionOutcome
                        .match(message.found("logging.path").items(config));
            }
            config = new RelaxedPropertyResolver(environment, "endpoints.logfile.")
                    .getProperty("external-file");
            if (StringUtils.hasText(config)) {
                return ConditionOutcome.match(
                        message.found("endpoints.logfile.external-file").items(config));
            }
            return ConditionOutcome.noMatch(message.didNotFind("logging file").atAll());
        }

    }

}

红色部分:

4.2.1.获取endpoint,Set<MvcEndpoint> endpoints = mvcEndpoints().getEndpoints(); 

方法如下:

@Override
    public void afterPropertiesSet() throws Exception {
        Collection<MvcEndpoint> existing = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(this.applicationContext, MvcEndpoint.class)
                .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);
                String path = determinePath(endpoint,
                        this.applicationContext.getEnvironment());
                if (path != null) {
                    adapter.setPath(path);
                }
                this.endpoints.add(adapter);
            }
        }
    }

获取容器中的MvcEndpoint接口实现类。

4.2.2.实例化EndpointHandlerMapping 

EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints,
corsConfiguration);

创建实例

    /**
     * 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
     * @param corsConfiguration the CORS configuration for the endpoints
     * @since 1.3.0
     */
    public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints,
            CorsConfiguration corsConfiguration) {
        super(endpoints, corsConfiguration);
    }

4.2.3.设置安全过滤器

MvcEndpointSecurityInterceptor securityInterceptor = new MvcEndpointSecurityInterceptor(
this.managementServerProperties.getSecurity().isEnabled(),
this.managementServerProperties.getSecurity().getRoles());
mapping.setSecurityInterceptor(securityInterceptor);

定义:

/**
 * Security interceptor for MvcEndpoints.
 *
 * @author Madhura Bhave
 * @since 1.5.0
 */
public class MvcEndpointSecurityInterceptor extends HandlerInterceptorAdapter {

    private static final Log logger = LogFactory
            .getLog(MvcEndpointSecurityInterceptor.class);

    private final boolean secure;

    private final List<String> roles;

    private AtomicBoolean loggedUnauthorizedAttempt = new AtomicBoolean();

    public MvcEndpointSecurityInterceptor(boolean secure, List<String> roles) {
        this.secure = secure;
        this.roles = roles;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
            Object handler) throws Exception {
        if (CorsUtils.isPreFlightRequest(request) || !this.secure) {
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        if (HttpMethod.OPTIONS.matches(request.getMethod())
                && !(handlerMethod.getBean() instanceof MvcEndpoint)) {
            return true;
        }
        MvcEndpoint mvcEndpoint = (MvcEndpoint) handlerMethod.getBean();
        if (!mvcEndpoint.isSensitive()) {
            return true;
        }
        if (isUserAllowedAccess(request)) {
            return true;
        }
        sendFailureResponse(request, response);
        return false;
    }

    private boolean isUserAllowedAccess(HttpServletRequest request) {
        AuthoritiesValidator authoritiesValidator = null;
        if (isSpringSecurityAvailable()) {
            authoritiesValidator = new AuthoritiesValidator();
        }
        for (String role : this.roles) {
            if (request.isUserInRole(role)) {
                return true;
            }
            if (authoritiesValidator != null && authoritiesValidator.hasAuthority(role)) {
                return true;
            }
        }
        return false;
    }

    private boolean isSpringSecurityAvailable() {
        return ClassUtils.isPresent(
                "org.springframework.security.config.annotation.web.WebSecurityConfigurer",
                getClass().getClassLoader());
    }

    private void sendFailureResponse(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        if (request.getUserPrincipal() != null) {
            String roles = StringUtils.collectionToDelimitedString(this.roles, " ");
            response.sendError(HttpStatus.FORBIDDEN.value(),
                    "Access is denied. User must have one of the these roles: " + roles);
        }
        else {
            logUnauthorizedAttempt();
            response.sendError(HttpStatus.UNAUTHORIZED.value(),
                    "Full authentication is required to access this resource.");
        }
    }

    private void logUnauthorizedAttempt() {
        if (this.loggedUnauthorizedAttempt.compareAndSet(false, true)
                && logger.isInfoEnabled()) {
            logger.info("Full authentication is required to access "
                    + "actuator endpoints. Consider adding Spring Security "
                    + "or set 'management.security.enabled' to false.");
        }
    }

    /**
     * Inner class to check authorities using Spring Security (when available).
     */
    private static class AuthoritiesValidator {

        private boolean hasAuthority(String role) {
            Authentication authentication = SecurityContextHolder.getContext()
                    .getAuthentication();
            if (authentication != null) {
                for (GrantedAuthority authority : authentication.getAuthorities()) {
                    if (authority.getAuthority().equals(role)) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

}

4.2.4. 自定义EndpointHandlerMapping 

@FunctionalInterface
public interface EndpointHandlerMappingCustomizer {

    /**
     * Customize the specified {@link EndpointHandlerMapping}.
     * @param mapping the {@link EndpointHandlerMapping} to customize
     */
    void customize(EndpointHandlerMapping mapping);

}

5.映射的实现EndpointWebMvcManagementContextConfiguration

    @Bean
    @ConditionalOnBean(EnvironmentEndpoint.class)
    @ConditionalOnEnabledEndpoint("env")
    public EnvironmentMvcEndpoint environmentMvcEndpoint(EnvironmentEndpoint delegate) {
        return new EnvironmentMvcEndpoint(delegate);
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnEnabledEndpoint("heapdump")
    public HeapdumpMvcEndpoint heapdumpMvcEndpoint() {
        return new HeapdumpMvcEndpoint();
    }

    @Bean
    @ConditionalOnBean(HealthEndpoint.class)
    @ConditionalOnEnabledEndpoint("health")
    public HealthMvcEndpoint healthMvcEndpoint(HealthEndpoint delegate,
            ManagementServerProperties managementServerProperties) {
        HealthMvcEndpoint healthMvcEndpoint = new HealthMvcEndpoint(delegate,
                this.managementServerProperties.getSecurity().isEnabled(),
                managementServerProperties.getSecurity().getRoles());
        if (this.healthMvcEndpointProperties.getMapping() != null) {
            healthMvcEndpoint
                    .addStatusMapping(this.healthMvcEndpointProperties.getMapping());
        }
        return healthMvcEndpoint;
    }

    @Bean
    @ConditionalOnBean(LoggersEndpoint.class)
    @ConditionalOnEnabledEndpoint("loggers")
    public LoggersMvcEndpoint loggersMvcEndpoint(LoggersEndpoint delegate) {
        return new LoggersMvcEndpoint(delegate);
    }

    @Bean
    @ConditionalOnBean(MetricsEndpoint.class)
    @ConditionalOnEnabledEndpoint("metrics")
    public MetricsMvcEndpoint metricsMvcEndpoint(MetricsEndpoint delegate) {
        return new MetricsMvcEndpoint(delegate);
    }

    @Bean
    @ConditionalOnEnabledEndpoint("logfile")
    @Conditional(LogFileCondition.class)
    public LogFileMvcEndpoint logfileMvcEndpoint() {
        return new LogFileMvcEndpoint();
    }

    @Bean
    @ConditionalOnBean(ShutdownEndpoint.class)
    @ConditionalOnEnabledEndpoint(value = "shutdown", enabledByDefault = false)
    public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) {
        return new ShutdownMvcEndpoint(delegate);
    }

    @Bean
    @ConditionalOnBean(AuditEventRepository.class)
    @ConditionalOnEnabledEndpoint("auditevents")
    public AuditEventsMvcEndpoint auditEventMvcEndpoint(
            AuditEventRepository auditEventRepository) {
        return new AuditEventsMvcEndpoint(auditEventRepository);
    }

最终的映射来自MvcEndpoint的各种实现

以health为例:

    @ActuatorGetMapping
    @ResponseBody
    public Object invoke(HttpServletRequest request, Principal principal) {
        if (!getDelegate().isEnabled()) {
            // Shouldn't happen because the request mapping should not be registered
            return getDisabledResponse();
        }
        Health health = getHealth(request, principal);
        HttpStatus status = getStatus(health);
        if (status != null) {
            return new ResponseEntity<>(health, status);
        }
        return health;
    }

其中,@ActuatorGetMapping注解等同于@RequestMapping

/**
 * Specialized {@link RequestMapping} for {@link RequestMethod#GET GET} requests that
 * produce {@code application/json} or
 * {@code application/vnd.spring-boot.actuator.v1+json} responses.
 *
 * @author Andy Wilkinson
 */
@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 {};

}

注意,其中涉及到非常重要的一个类:EndpointMvcAdapter,它代理了MvcEndpoint,实现其invoke方法

/**
 * Adapter class to expose {@link Endpoint}s as {@link MvcEndpoint}s.
 *
 * @author Dave Syer
 * @author Andy Wilkinson
 */
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);
    }

    @Override
    @ActuatorGetMapping
    @ResponseBody
    public Object invoke() {
        return super.invoke();
    }

}

总结:

1.代理层

 2.实现层

3.代理逻辑

以HealthMvcEndpoint为例讲述:

HealthMvcEndpoint主方法

    @ActuatorGetMapping
    @ResponseBody
    public Object invoke(HttpServletRequest request, Principal principal) {
        if (!getDelegate().isEnabled()) {
            // Shouldn't happen because the request mapping should not be registered
            return getDisabledResponse();
        }
        Health health = getHealth(request, principal);
        HttpStatus status = getStatus(health);
        if (status != null) {
            return new ResponseEntity<>(health, status);
        }
        return health;
    }

调用逻辑

    private Health getHealth(HttpServletRequest request, Principal principal) {
        long accessTime = System.currentTimeMillis();
        if (isCacheStale(accessTime)) {
            this.lastAccess = accessTime;
            this.cached = getDelegate().invoke();
        }
        if (exposeHealthDetails(request, principal)) {
            return this.cached;
        }
        return Health.status(this.cached.getStatus()).build();
    }

delegate获取由HealthMvcEndpoint构造方法注入

    public HealthMvcEndpoint(HealthEndpoint delegate, boolean secure,
            List<String> roles) {
        super(delegate);
        this.secure = secure;
        setupDefaultStatusMapping();
        this.roles = roles;
    }

触发HealthEndpoint#invoke()方法:

    /**
     * Invoke all {@link HealthIndicator} delegates and collect their health information.
     */
    @Override
    public Health invoke() {
        return this.healthIndicator.health();
    }

HealthIndicator接口代理了其子类:

 

posted on 2017-04-07 16:54  一天不进步,就是退步  阅读(8696)  评论(0编辑  收藏  举报