Spring Boot 源碼分析 (二)
sschrodinger
2019/05/30
引用
基于 Spring boot 2.1.5.RELEASE 版本
Spring 項(xiàng)目監(jiān)控
Spring Boot 使用 Spring Boot Actuator 組件監(jiān)控應(yīng)用程序的運(yùn)行狀況。
通過(guò)在 Maven 中增加 spring-boot-starter-actuator 依賴(lài),可以快速增加 actuator 功能。
actuator 使用 REST 風(fēng)格的接口暴露了多個(gè)監(jiān)控端點(diǎn)。默認(rèn)使用 http:{your ip}/acturator/{end point} 來(lái)訪問(wèn)這些端點(diǎn)。
| 端點(diǎn) | 描述 | HTTP 方法 | 是否敏感 |
|---|---|---|---|
| autoconfig | 顯示自動(dòng)配置的信息 | GET | 是 |
| beans | 顯示應(yīng)用上下文程序 | GET | |
| configgroups | 顯示所有 @ConfigurationProperties 的配置屬性列表 |
GET | 是 |
| dump | 顯示線程活動(dòng)的快照 | GET | 是 |
| env | 顯示應(yīng)用的環(huán)境變量 | GET | 是 |
| health | 顯示應(yīng)用的健康指標(biāo),這些值由 HealthIndicator 的實(shí)現(xiàn)提供 |
GET | 否 |
| info | 顯示應(yīng)用的信息 | GET | 否 |
| metrics | 顯示應(yīng)用的度量標(biāo)準(zhǔn)信息 | GET | 是 |
| mapping | 顯示所有 @RequestMapping 的路徑列表 |
GET | 是 |
| ... | ... | ... | ... |
比如,在開(kāi)啟了 actuator 的服務(wù)器上,在地址欄輸入 URL http://localhost:8080/actuator/health,會(huì)回顯 {"status":"UP"}。
Actuator 實(shí)現(xiàn)原理
主要分為兩部分,第一部分是獲得信息、第二部分是創(chuàng)建 REST 風(fēng)格的端口以供用戶訪問(wèn)(Endpoint)。
以 health 端口為例,進(jìn)行分析。
獲取 health 信息
在 Actuator 中,健康信息被封裝成了類(lèi) 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;
// ...
}
健康信息只包含四種狀態(tài),即 UP(正常)、DOWN(不正常)、OUT_OF_SERVICE(停止服務(wù))和 UNKNOW(未知),每一種狀態(tài)都可以有自己的信息。
所有健康監(jiān)測(cè)的基類(lèi)都是 HealthIndicator,該接口根據(jù)信息返回當(dāng)前的健康狀態(tài),定義如下:
public interface HealthIndicator {
Health health();
}
在該類(lèi)的基礎(chǔ)上,有一個(gè)通用的 HealthIndicator 抽象實(shí)現(xiàn) AbstractHealthIndicator,該抽象類(lèi)適合用于在檢測(cè)過(guò)程中如果拋出異常,則狀態(tài)自動(dòng)修改為 DOWN 的檢測(cè)邏輯,關(guān)鍵代碼如下:
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;
// ...
}
在該抽象類(lèi)的基礎(chǔ)上,Actuator 實(shí)現(xiàn)了多個(gè)檢測(cè)類(lèi),包括 DiskSpaceHealthIndicator、ApplicationHealthIndicator 和 DataSourceHealthIndicator 等。
DiskSpaceHealthIndicator 主要用于檢測(cè)磁盤(pán)空間是否小于給定閾值,通過(guò) File 類(lèi)的 getUsableSpace() 方法實(shí)現(xiàn),如下:
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 主要用于檢測(cè)數(shù)據(jù)源的健康度,主要是使用一個(gè)給定的 sql 語(yǔ)句去測(cè)試數(shù)據(jù)庫(kù),如下:
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 用于檢測(cè)應(yīng)用整體的狀態(tài),這里會(huì)直接返回 UP:
public class ApplicationHealthIndicator extends AbstractHealthIndicator {
public ApplicationHealthIndicator() {
super("Application health check failed");
}
@Override
protected void doHealthCheck(Health.Builder builder) throws Exception {
builder.up();
}
}
同時(shí),在基類(lèi) HealthIndicator 的基礎(chǔ)上,實(shí)現(xiàn)了 CompositeHealthIndicator 類(lèi),這是一個(gè)組合類(lèi),用于聚合多個(gè)狀態(tài)信息的結(jié)果(對(duì)所有狀態(tài)進(jìn)行聚合,并按照 Status.DOWN, Status.OUT_OF_SERVICE, Status.UP, Status.UNKNOWN 進(jìn)行排序,返回第一個(gè)結(jié)果)。
同理,beans 的檢測(cè)主要是 BeansEndpoint 持有一個(gè) ConfigurableApplicationContext 的引用實(shí)例,顯示所有加載的 bean。
Endpoint 實(shí)現(xiàn)
Endpoint 是 Actuator 提供的用于接口訪問(wèn)的包,在 org.springframework.boot.actuate.endpoint 中還有 2 個(gè)子包 -jmx (可通過(guò) jmx 協(xié)議訪問(wèn)),mvc(通過(guò) spring mvc 暴露)。
首先看 EndPoint 的基類(lèi),如下:
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
}
在基類(lèi) ExposableEndpoint 之下,拓展了多個(gè)接口和類(lèi),如下:
// 用于標(biāo)注可否被其他 EndpointDiscoverer 發(fā)現(xiàn)
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 發(fā)現(xiàn)
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 的核心思想就是通過(guò)在 Mvc 中注冊(cè) WebMvcEndpointHandlerMapping,類(lèi)似于編寫(xiě) @Controller,每一個(gè) @Controller 調(diào)用對(duì)應(yīng) EndPoint 的 Operation 中的 invoke 方法,來(lái)執(zhí)行監(jiān)控作用。
首先介紹 EndpointMapping,該類(lèi)的作用是根據(jù)一個(gè) path 返回一個(gè)規(guī)范化的 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 的基類(lèi),主要作用就是將一個(gè)方法和一個(gè) url 組合起來(lái),如下例子:
@RequestMapping(value = "url_1", method = RequestMethod.GET)
public String method_1() {
}
@RequestMapping(value = "url_2", method = RequestMethod.GET)
public String method_2() {
}
在 RequestMappingInfoHandlerMapping 中,會(huì)將如上所示的使用 @RequestMapping 注解包括的信息封裝成 RequestMappingInfo,包括了 url 地址,請(qǐng)求方法等信息,最后根據(jù)這些信息選擇合適的 handler,即選擇一個(gè)合適的方法,在這里是 method_1() 或者 method_2() 對(duì)連接進(jìn)行處理(這中間包括了很多參數(shù)的封裝,略過(guò)),對(duì)應(yīng)關(guān)系如下圖:
RequestMappingInfo("url_1", get) ------->>------ method_1()
RequestMappingInfo("url_2", get) ------->>------ method_2()
RequestMappingInfoHandlerMapping 繼承自 AbstractHandlerMethodMapping<T>,實(shí)現(xiàn)了 InitializingBean 接口的 afterPropertiesSet() 方法,該方法會(huì)在設(shè)置了所有的屬性之后自動(dòng)調(diào)用,在 AbstractHandlerMethodMapping<T> 的實(shí)現(xiàn)中,只是在該方法中調(diào)用了一個(gè)抽象函數(shù),initHandlerMethods(),用于初始化 HandlerMethods。
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
AbstractWebMvcEndpointHandlerMapping 是 WebMvcEndpointHandlerMapping 的直接父類(lèi),先看該類(lèi)持有的變量:
// 用于轉(zhuǎn)換 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();
最重要的為上面注釋的三個(gè)變量,其中 handleMethod 指的是在 OperationHandler 這個(gè)類(lèi)中,名字為 handle,參數(shù)為 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);
}
}
由上的定義,可以在運(yùn)行時(shí),根據(jù) operation 的不同,返回不同的 @ResponseBody。根據(jù)此,我們可以猜測(cè)所有的監(jiān)控都會(huì)被封裝在 Opretion 中,監(jiān)控提供的端點(diǎn) 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();
}
}
最主要的是注冊(cè) 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,然后注冊(cè)到 mapping 中。
在 AbstractHealthIndicator 的 health() 方法處設(shè)置斷點(diǎn),
可以得到如下的棧:
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) ====>>>====== 該處正式調(diào)用 health 的方法
-------------------- 中間過(guò)程 \/--------------------------
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)
-------------------- 中間過(guò)程 /\--------------------------
handle:294, AbstractWebMvcEndpointHandlerMapping$ServletWebOperationAdapter (org.springframework.boot.actuate.endpoint.web.servlet) ====>>>====== 該處開(kāi)始調(diào)用 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 中,允許多個(gè)
handlerMapping同時(shí)運(yùn)行,DispatcherServlet根據(jù)優(yōu)先級(jí)優(yōu)先使用優(yōu)先級(jí)在前的HandlerMapping。如果當(dāng)前的HandlerMapping能夠返回可用的Handler,DispatcherServlet則使用當(dāng)前返回的Handler進(jìn)行 Web 請(qǐng)求的處理- 因
WebMvcEndpointHandlerMapping設(shè)置優(yōu)先級(jí)為 -100,所以會(huì)有優(yōu)先運(yùn)行的權(quán)力。