spring boot actuator的运行原理

最近项目正好用到spring boot actuator的health检测。
于是调查了一下关于这方面的东西,简单记录一下。

问题

通过调查,解答一下疑问:

  • 设定了地址,端口,如何启动一个新的Tomcat服务器
  • 请求的mapping是怎么设定的
  • 安全认知是如何运行的

Tomcat的启动

根据对source跟踪调查,发现在EndpointWebMvcAutoConfiguration#afterSingletonsInstantiated方法内,有以下逻辑。

1
2
3
4
5
6
7
if (managementPort == ManagementServerPort.DIFFERENT) {
if (this.applicationContext instanceof EmbeddedWebApplicationContext
&& ((EmbeddedWebApplicationContext) this.applicationContext)
.getEmbeddedServletContainer() != null) {
createChildManagementContext();
}
}

这个逻辑是判定server设定的端口跟management设定的端口是否一致,不一致的话,就启动一个新的Tomcat。
比如appliction.properties里的设定

1
2
3
server.port=8080

management.port=8800

SmartInitializingSingleton的afterSingletonsInstantiated方法是当所有的单例bean都初始化完以后被调用的。

在方法createChildManagementContext里创建了一个子context,并注册了几个配置类,这里面就有EndpointWebMvcChildContextConfiguration,在这个配置类里有个EmbeddedServletContainerCustomizer的实例ServerCustomizationTomcatEmbeddedServletContainerFactory进行配置。
ServerCustomization:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
if (this.managementServerProperties == null) {
this.managementServerProperties = BeanFactoryUtils
.beanOfTypeIncludingAncestors(this.beanFactory,
ManagementServerProperties.class);
this.server = BeanFactoryUtils.beanOfTypeIncludingAncestors(
this.beanFactory, ServerProperties.class);
}
// Customize as per the parent context first (so e.g. the access logs go to
// the same place)
this.server.customize(container);
// Then reset the error pages
container.setErrorPages(Collections.<ErrorPage>emptySet());
// and the context path
container.setContextPath("");
// and add the management-specific bits
container.setPort(this.managementServerProperties.getPort());
if (this.managementServerProperties.getSsl() != null) {
container.setSsl(this.managementServerProperties.getSsl());
}
container.setServerHeader(this.server.getServerHeader());
container.setAddress(this.managementServerProperties.getAddress());
container.addErrorPages(new ErrorPage(this.server.getError().getPath()));
}

然后在TomcatEmbeddedServletContainerFactory#getEmbeddedServletContainer中启动Tomcat。

mapping的设定

首先,先简述一个注解@ManagementContextConfiguration,这是个特殊的@Configuration,只用于management context,并且用这个注解所标注的配置类要在文件/META-INF/spring.factories

org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration

里明确指定,这里面就有配置类EndpointWebMvcManagementContextConfiguration
这个配置类里,有个Bean

1
2
3
4
5
@Bean
@ConditionalOnMissingBean
public MvcEndpoints mvcEndpoints() {
return new MvcEndpoints();
}

用来收集所有注册了的Endpoint,然后作成BeanEndpointHandlerMapping

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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;
}

由于这个是个标准的RequestMappingHandlerMapping类,会在他的配置方法afterPropertiesSet里注册各个Endpoint的mapping方法,也就是用@ActuatorGetMapping注解的方法。

安全认证

application.properties里有个配置

1
management.security.enabled

这个配置有以下几个作用

  • 如果Spring Security使用的默认配置的话,如果这个设置为false,会自动把management.context-path所设置的path加入到非认证的path里。但是如果自定义了WebSecurityConfigurerAdapter的话,那么根据需要自己手动把这个path加入到非认证path里了。
  • 如果这个安全设定设为true的话,那么在请求Endpoint时,会验证用户的Role,根据是否匹配来决定是否可访问。这个过程是由类MvcEndpointSecurityInterceptor来处理的。

另外还可以根据各个Endpoint设定的secure来进一步精确控制。
他们之间有以下关系:

management.security.enabled endpoints.xxx.enabled 结果
true true 有角色权限:返回详细信息,无角色权限:拒绝访问
true false 角色无关,返回详细信息
false true 有角色权限:返回详细信息,无角色权限:返回简略信息
false false 角色无关,返回详细信息