Spring Boot 源码分析 (二)
sschrodinger
2019/05/30
引用
基于 Spring boot 2.1.5.RELEASE 版本
Spring 项目监控
Spring Boot 使用 Spring Boot Actuator 组件监控应用程序的运行状况。
通过在 Maven 中增加 spring-boot-starter-actuator
依赖,可以快速增加 actuator
功能。
actuator
使用 REST 风格的接口暴露了多个监控端点。默认使用 http:{your ip}/acturator/{end point}
来访问这些端点。
端点 | 描述 | HTTP 方法 | 是否敏感 |
---|---|---|---|
autoconfig | 显示自动配置的信息 | GET | 是 |
beans | 显示应用上下文程序 | GET | |
configgroups | 显示所有 @ConfigurationProperties 的配置属性列表 |
GET | 是 |
dump | 显示线程活动的快照 | GET | 是 |
env | 显示应用的环境变量 | GET | 是 |
health | 显示应用的健康指标,这些值由 HealthIndicator 的实现提供 |
GET | 否 |
info | 显示应用的信息 | GET | 否 |
metrics | 显示应用的度量标准信息 | GET | 是 |
mapping | 显示所有 @RequestMapping 的路径列表 |
GET | 是 |
... | ... | ... | ... |
比如,在开启了 actuator
的服务器上,在地址栏输入 URL http://localhost:8080/actuator/health
,会回显 {"status":"UP"}
。
Actuator 实现原理
主要分为两部分,第一部分是获得信息、第二部分是创建 REST 风格的端口以供用户访问(Endpoint)。
以 health 端口为例,进行分析。
获取 health 信息
在 Actuator 中,健康信息被封装成了类 Health
,如下:
public final class Health {
private final Status status;
private final Map<String, Object> details;
// ...
// constructor
// functional method
// build model interface
}
public final class Status {
public static final Status UNKNOWN = new Status("UNKNOWN");
public static final Status UP = new Status("UP");
public static final Status DOWN = new Status("DOWN");
public static final Status OUT_OF_SERVICE = new Status("OUT_OF_SERVICE");
private final String code;
private final String description;
// ...
}
健康信息只包含四种状态,即 UP(正常)、DOWN(不正常)、OUT_OF_SERVICE(停止服务)和 UNKNOW(未知),每一种状态都可以有自己的信息。
所有健康监测的基类都是 HealthIndicator
,该接口根据信息返回当前的健康状态,定义如下:
public interface HealthIndicator {
Health health();
}
在该类的基础上,有一个通用的 HealthIndicator
抽象实现 AbstractHealthIndicator
,该抽象类适合用于在检测过程中如果抛出异常,则状态自动修改为 DOWN
的检测逻辑,关键代码如下:
public abstract class AbstractHealthIndicator implements HealthIndicator {
private static final String NO_MESSAGE = null;
private static final String DEFAULT_MESSAGE = "Health check failed";
@Override
public final Health health() {
Health.Builder builder = new Health.Builder();
try {
doHealthCheck(builder);
}
catch (Exception ex) {
if (this.logger.isWarnEnabled()) {
String message = this.healthCheckFailedMessage.apply(ex);
this.logger.warn(StringUtils.hasText(message) ? message : DEFAULT_MESSAGE,
ex);
}
builder.down(ex);
}
return builder.build();
}
/**
* Actual health check logic.
* @param builder the {@link Builder} to report health status and details
* @throws Exception any {@link Exception} that should create a {@link Status#DOWN}
* system status.
*/
protected abstract void doHealthCheck(Health.Builder builder) throws Exception;
// ...
}
在该抽象类的基础上,Actuator 实现了多个检测类,包括 DiskSpaceHealthIndicator
、ApplicationHealthIndicator
和 DataSourceHealthIndicator
等。
DiskSpaceHealthIndicator
主要用于检测磁盘空间是否小于给定阈值,通过 File
类的 getUsableSpace()
方法实现,如下:
public class DiskSpaceHealthIndicator extends AbstractHealthIndicator {
private final File path;
private final DataSize threshold;
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
long diskFreeInBytes = this.path.getUsableSpace();
if (diskFreeInBytes >= this.threshold.toBytes()) {
builder.up();
}
else {
logger.warn(String.format(
"Free disk space below threshold. "
+ "Available: %d bytes (threshold: %s)",
diskFreeInBytes, this.threshold));
builder.down();
}
builder.withDetail("total", this.path.getTotalSpace())
.withDetail("free", diskFreeInBytes)
.withDetail("threshold", this.threshold.toBytes());
}
}
DataSourceHealthIndicator
主要用于检测数据源的健康度,主要是使用一个给定的 sql 语句去测试数据库,如下:
public class DataSourceHealthIndicator extends AbstractHealthIndicator
implements InitializingBean {
private static final String DEFAULT_QUERY = "SELECT 1";
private DataSource dataSource;
private String query;
private JdbcTemplate jdbcTemplate;
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
if (this.dataSource == null) {
builder.up().withDetail("database", "unknown");
}
else {
doDataSourceHealthCheck(builder);
}
}
private void doDataSourceHealthCheck(Health.Builder builder) throws Exception {
String product = getProduct();
builder.up().withDetail("database", product);
String validationQuery = getValidationQuery(product);
if (StringUtils.hasText(validationQuery)) {
List<Object> results = this.jdbcTemplate.query(validationQuery,
new SingleColumnRowMapper());
Object result = DataAccessUtils.requiredSingleResult(results);
builder.withDetail("hello", result);
}
}
private String getProduct() {
return this.jdbcTemplate.execute((ConnectionCallback<String>) this::getProduct);
}
private String getProduct(Connection connection) throws SQLException {
return connection.getMetaData().getDatabaseProductName();
}
protected String getValidationQuery(String product) {
String query = this.query;
if (!StringUtils.hasText(query)) {
DatabaseDriver specific = DatabaseDriver.fromProductName(product);
query = specific.getValidationQuery();
}
if (!StringUtils.hasText(query)) {
query = DEFAULT_QUERY;
}
return query;
}
/**
* Set the {@link DataSource} to use.
* @param dataSource the data source
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
private static class SingleColumnRowMapper implements RowMapper<Object> {
@Override
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
ResultSetMetaData metaData = rs.getMetaData();
int columns = metaData.getColumnCount();
if (columns != 1) {
throw new IncorrectResultSetColumnCountException(1, columns);
}
return JdbcUtils.getResultSetValue(rs, 1);
}
}
}
ApplicationHealthIndicator
用于检测应用整体的状态,这里会直接返回 UP
:
public class ApplicationHealthIndicator extends AbstractHealthIndicator {
public ApplicationHealthIndicator() {
super("Application health check failed");
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.up();
}
}
同时,在基类 HealthIndicator
的基础上,实现了 CompositeHealthIndicator
类,这是一个组合类,用于聚合多个状态信息的结果(对所有状态进行聚合,并按照 Status.DOWN, Status.OUT_OF_SERVICE, Status.UP, Status.UNKNOWN
进行排序,返回第一个结果)。
同理,beans
的检测主要是 BeansEndpoint
持有一个 ConfigurableApplicationContext
的引用实例,显示所有加载的 bean。
Endpoint 实现
Endpoint
是 Actuator 提供的用于接口访问的包,在 org.springframework.boot.actuate.endpoint
中还有 2 个子包 -jmx
(可通过 jmx 协议访问),mvc(通过 spring mvc 暴露)。
首先看 EndPoint
的基类,如下:
public interface ExposableEndpoint<O extends Operation> {
EndpointId getEndpointId();
boolean isEnableByDefault();
Collection<O> getOperations();
}
public interface Operation {
OperationType getType();
Object invoke(InvocationContext context);
}
public enum OperationType {
READ,
WRITE,
DELETE
}
在基类 ExposableEndpoint
之下,拓展了多个接口和类,如下:
// 用于标注可否被其他 EndpointDiscoverer 发现
public interface DiscoveredEndpoint<O extends Operation> extends ExposableEndpoint<O> {
boolean wasDiscoveredBy(Class<? extends EndpointDiscoverer<?, ?>> discoverer);
Object getEndpointBean();
}
public abstract class AbstractDiscoveredEndpoint<O extends Operation>
extends AbstractExposableEndpoint<O> implements DiscoveredEndpoint<O> {
private final EndpointDiscoverer<?, ?> discoverer;
private final Object endpointBean;
@Override
public boolean wasDiscoveredBy(Class<? extends EndpointDiscoverer<?, ?>> discoverer) {
// 判断该 EndPoint 是否能被 EndpointDiscoverer 发现
return discoverer.isInstance(this.discoverer);
}
protected void appendFields(ToStringCreator creator) {
}
}
public abstract class AbstractExposableEndpoint<O extends Operation>
implements ExposableEndpoint<O> {
private final EndpointId id;
private boolean enabledByDefault;
private List<O> operations;
public AbstractExposableEndpoint(EndpointId id, boolean enabledByDefault,
Collection<? extends O> operations) {
// ...
}
// ...
}
public abstract class AbstractDiscoveredEndpoint<O extends Operation>
extends AbstractExposableEndpoint<O> implements DiscoveredEndpoint<O> {
private final EndpointDiscoverer<?, ?> discoverer;
private final Object endpointBean;
public AbstractDiscoveredEndpoint(EndpointDiscoverer<?, ?> discoverer,
Object endpointBean, EndpointId id, boolean enabledByDefault,
Collection<? extends O> operations) {
// ...
}
}
class DiscoveredWebEndpoint extends AbstractDiscoveredEndpoint<WebOperation>
implements ExposableWebEndpoint {
private final String rootPath;
DiscoveredWebEndpoint(EndpointDiscoverer<?, ?> discoverer, Object endpointBean,
EndpointId id, String rootPath, boolean enabledByDefault,
Collection<WebOperation> operations) {
super(discoverer, endpointBean, id, enabledByDefault, operations);
this.rootPath = rootPath;
}
@Override
public String getRootPath() {
return this.rootPath;
}
}
// ...
Actuator 的核心思想就是通过在 Mvc 中注册 WebMvcEndpointHandlerMapping
,类似于编写 @Controller
,每一个 @Controller
调用对应 EndPoint
的 Operation
中的 invoke
方法,来执行监控作用。
首先介绍 EndpointMapping
,该类的作用是根据一个 path 返回一个规范化的 path。如下:
public class EndpointMapping {
private final String path;
public EndpointMapping(String path) {
this.path = normalizePath(path);
}
public String getPath() {
return this.path;
}
public String createSubPath(String path) {
return this.path + normalizePath(path);
}
private static String normalizePath(String path) {
if (!StringUtils.hasText(path)) {
return path;
}
String normalizedPath = path;
if (!normalizedPath.startsWith("/")) {
normalizedPath = "/" + normalizedPath;
}
if (normalizedPath.endsWith("/")) {
normalizedPath = normalizedPath.substring(0, normalizedPath.length() - 1);
}
return normalizedPath;
}
}
RequestMappingInfoHandlerMapping
是 WebMvcEndpointHandlerMapping
的基类,主要作用就是将一个方法和一个 url 组合起来,如下例子:
@RequestMapping(value = "url_1", method = RequestMethod.GET)
public String method_1() {
}
@RequestMapping(value = "url_2", method = RequestMethod.GET)
public String method_2() {
}
在 RequestMappingInfoHandlerMapping
中,会将如上所示的使用 @RequestMapping
注解包括的信息封装成 RequestMappingInfo
,包括了 url 地址,请求方法等信息,最后根据这些信息选择合适的 handler,即选择一个合适的方法,在这里是 method_1()
或者 method_2()
对连接进行处理(这中间包括了很多参数的封装,略过),对应关系如下图:
RequestMappingInfo("url_1", get) ------->>------ method_1()
RequestMappingInfo("url_2", get) ------->>------ method_2()
RequestMappingInfoHandlerMapping
继承自 AbstractHandlerMethodMapping<T>
,实现了 InitializingBean
接口的 afterPropertiesSet()
方法,该方法会在设置了所有的属性之后自动调用,在 AbstractHandlerMethodMapping<T>
的实现中,只是在该方法中调用了一个抽象函数,initHandlerMethods()
,用于初始化 HandlerMethods。
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
AbstractWebMvcEndpointHandlerMapping
是 WebMvcEndpointHandlerMapping
的直接父类,先看该类持有的变量:
// 用于转换 url
private final EndpointMapping endpointMapping;
// 所持有的 endpoint
private final Collection<ExposableWebEndpoint> endpoints;
private final EndpointMediaTypes endpointMediaTypes;
private final CorsConfiguration corsConfiguration;
// handler method
private final Method handleMethod = ReflectionUtils.findMethod(OperationHandler.class,
"handle", HttpServletRequest.class, Map.class);
private static final RequestMappingInfo.BuilderConfiguration builderConfig = getBuilderConfig();
最重要的为上面注释的三个变量,其中 handleMethod
指的是在 OperationHandler
这个类中,名字为 handle
,参数为 HttpServletRequest
和 Map
的方法,
private final class OperationHandler {
private final ServletWebOperation operation;
OperationHandler(ServletWebOperation operation) {this.operation = operation;}
@ResponseBody
public Object handle(HttpServletRequest request,
@RequestBody(required = false) Map<String, String> body) {
return this.operation.handle(request, body);
}
}
由上的定义,可以在运行时,根据 operation
的不同,返回不同的 @ResponseBody
。根据此,我们可以猜测所有的监控都会被封装在 Opretion
中,监控提供的端点 url 都封装在 AbstractEndpint
中。
再看 initHandlerMethods
方法,如下:
protected void initHandlerMethods() {
for (ExposableWebEndpoint endpoint : this.endpoints) {
for (WebOperation operation : endpoint.getOperations()) {
registerMappingForOperation(endpoint, operation);
}
}
if (StringUtils.hasText(this.endpointMapping.getPath())) {
registerLinksMapping();
}
}
最主要的是注册 registerMappingForOperation
,该方法如下:
private void registerMappingForOperation(ExposableWebEndpoint endpoint,
WebOperation operation) {
ServletWebOperation servletWebOperation = wrapServletWebOperation(endpoint,
operation, new ServletWebOperationAdapter(operation));
registerMapping(createRequestMappingInfo(operation),
new OperationHandler(servletWebOperation), this.handleMethod);
}
将 opretion 和 endpoint 包装成 ServletWebOperation
,然后注册到 mapping 中。
在 AbstractHealthIndicator
的 health()
方法处设置断点,
可以得到如下的栈:
health:82, AbstractHealthIndicator (org.springframework.boot.actuate.health)
health:98, CompositeHealthIndicator (org.springframework.boot.actuate.health)
health:50, HealthEndpoint (org.springframework.boot.actuate.health)
health:54, HealthEndpointWebExtension (org.springframework.boot.actuate.health) ====>>>====== 该处正式调用 health 的方法
-------------------- 中间过程 \/--------------------------
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethod:282, ReflectionUtils (org.springframework.util)
invoke:76, ReflectiveOperationInvoker (org.springframework.boot.actuate.endpoint.invoke.reflect)
invoke:61, AbstractDiscoveredOperation (org.springframework.boot.actuate.endpoint.annotation)
-------------------- 中间过程 /\--------------------------
handle:294, AbstractWebMvcEndpointHandlerMapping$ServletWebOperationAdapter (org.springframework.boot.actuate.endpoint.web.servlet) ====>>>====== 该处开始调用 health 的方法
handle:355, AbstractWebMvcEndpointHandlerMapping$OperationHandler (org.springframework.boot.actuate.endpoint.web.servlet)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:190, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:138, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:104, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:892, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:797, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1039, DispatcherServlet (org.springframework.web.servlet)
// ...
note
- 在 sping 中,允许多个
handlerMapping
同时运行,DispatcherServlet
根据优先级优先使用优先级在前的HandlerMapping
。如果当前的HandlerMapping能够返回可用的Handler
,DispatcherServlet
则使用当前返回的Handler
进行 Web 请求的处理- 因
WebMvcEndpointHandlerMapping
设置优先级为 -100,所以会有优先运行的权力。
网友评论