• spring boot actuator工作原理之http服务暴露源码分析


    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接口代理了其子类:

  • 相关阅读:
    16.14
    16.13
    JAVA JLabel自定义子类无法显示
    16.12
    16.11
    css实现垂直居中
    HTML5学习笔记
    HTML、Css中插入图片的一些问题
    MySQL的if函数
    java实现将汉字转为首字母、拼音
  • 原文地址:https://www.cnblogs.com/davidwang456/p/6678982.html
Copyright © 2020-2023  润新知