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接口代理了其子类:
微信公众号: 架构师日常笔记 欢迎关注!