spring boot 源码解析52-actuate中MVCEndPoint解析
今天有个别项目的jolokia的endpoint不能访问,调试源码发现:endpoint.enabled的开关导致的。
关于Endpoint,
《Springboot Endpoint之二:Endpoint源码剖析》
之前的几篇文章分析了spring boot 中有关endpoint的实现,细心的朋友可以发现,在org.springframework.boot.actuate.endpoint.mvc 包下也有一系列的xxxEndpoint,这又是为什么呢?
原因是: 我们很多情况下,都是访问接口的方式获取应用的监控,之前的分析是其实现的底层,要想实现通过接口访问,还需要对其进行包装一番,org.springframework.boot.actuate.endpoint.mvc 包下的实现就是干的这种事,下面,我们就来分析一下吧
解析
关于mvcEndPoint的类图如下,
下面我们就来1个1个的来分析吧.
MvcEndpoint
MvcEndpoint –> 顶层接口,实现类允许使用@RequestMapping和完整的Spring MVC机制,但不能在类型级别使用@Controller或@RequestMapping,因为这将导致路径的双重映射,一次通过常规MVC处理程序映射,一次通过EndpointHandlerMapping。
该类声明如下:
public interface MvcEndpoint {
// 禁用端点的响应实体
ResponseEntity<Map<String, String>> DISABLED_RESPONSE = new ResponseEntity<Map<String, String>>(
Collections.singletonMap("message", "This endpoint is disabled"),
HttpStatus.NOT_FOUND);
// 返回端点的MVC路径
String getPath();
// 返回端点是否暴露敏感信息
boolean isSensitive();
// 返回端点暴露的类型。或者返回null 如果当前的MvcEndpoint 暴露的信息 不能视为一个传统的Endpoint(也就是我们之前分析的那一堆Endpoint)
Class<? extends Endpoint> getEndpointType();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
AbstractMvcEndpoint
AbstractMvcEndpoint继承了WebMvcConfigurerAdapter,实现了MvcEndpoint, EnvironmentAware接口,此时 AbstractMvcEndpoint 就持有了Environment,可以对spring mvc 做个性化设置.
字段,构造器分别如下:
// Endpoint 请求路径
private String path;
// endpoint是否可用
private Boolean enabled;
// 标识该endpoint 是否暴露了敏感数据,如果为true,访问该Endpoint需要进行校验
private Boolean sensitive;
// 是否默认敏感
private final boolean sensitiveDefault;
public AbstractMvcEndpoint(String path, boolean sensitive) {
setPath(path);
this.sensitiveDefault = sensitive;
}
public AbstractMvcEndpoint(String path, boolean sensitive, boolean enabled) {
setPath(path);
this.sensitiveDefault = sensitive;
this.enabled = enabled;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
AbstractMvcEndpoint 实现了MvcEndpoint的方法,分别如下:
getPath,直接返回其字段值即可,代码如下:
public String getPath() {
return this.path;
}
1
2
3
isSensitive–> 默认返回false.代码如下:
public boolean isSensitive() {
// 默认返回false
return EndpointProperties.isSensitive(this.environment, this.sensitive,
this.sensitiveDefault);
}
1
2
3
4
5
调用:
public static boolean isSensitive(Environment environment, Boolean sensitive,
boolean sensitiveDefault) {
// 1. 如果sensitive 不等于null,则直接返回
if (sensitive != null) {
return sensitive;
}
// 2. 如果environment 不等于null 并且 environment中配置有endpoints.sensitive的属性,则
// 返回其配置值
if (environment != null
&& environment.containsProperty(ENDPOINTS_SENSITIVE_PROPERTY)) {
return environment.getProperty(ENDPOINTS_SENSITIVE_PROPERTY, Boolean.class);
}
// 3. 返回给的默认值
return sensitiveDefault;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
如果sensitive 不等于null,则直接返回
如果environment 不等于null 并且 environment中配置有endpoints.sensitive的属性,则 返回其配置值
返回给的默认值
getEndpointType –> 默认返回null,代码如下:
public Class<? extends Endpoint> getEndpointType() {
return null;
}
1
2
3
此外,该类还声明了1个方法,判断当前端点是否可用,默认返回true代码如下:
public boolean isEnabled() {
// 默认返回true
return EndpointProperties.isEnabled(this.environment, this.enabled);
}
1
2
3
4
调用:
public static boolean isEnabled(Environment environment, Boolean enabled) {
// 1. 如果enabled 不为null,则进行返回.
if (enabled != null) {
return enabled;
}
// 2. 如果Environment 不等于null 并且Environment 配置有endpoints.enabled的属性,
// 则返回其配置的值
if (environment != null
&& environment.containsProperty(ENDPOINTS_ENABLED_PROPERTY)) {
return environment.getProperty(ENDPOINTS_ENABLED_PROPERTY, Boolean.class);
}
// 3. 默认为true
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
如果enabled 不为null,则进行返回.
如果Environment 不等于null 并且Environment 配置有endpoints.enabled的属性,则返回其配置的值
默认为true
NamedMvcEndpoint
NamedMvcEndpoint继承自MvcEndpoint,使一个MvcEndpoint可以包含一个逻辑名.不像getPath()–>它没有给用户一个机会去改变endpoint的名字.NamedMvcEndpoint 提供了一个一致的方式去引用一个endpoint.
该类声明了1个方法,如下:
// 返回一个逻辑名字,不能为null,空,字母数字组成
String getName();
1
2
AbstractNamedMvcEndpoint
该类继承自AbstractMvcEndpoint,实现了NamedMvcEndpoint接口.
字段,构造器如下:
// Endpoint 名字
private final String name;
public AbstractNamedMvcEndpoint(String name, String path, boolean sensitive) {
super(path, sensitive);
Assert.hasLength(name, "Name must not be empty");
this.name = name;
}
public AbstractNamedMvcEndpoint(String name, String path, boolean sensitive,
boolean enabled) {
super(path, sensitive, enabled);
Assert.hasLength(name, "Name must not be empty");
this.name = name;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
getName,只需返回name属性值即可,很简单,代码如下:
public String getName() {
return this.name;
}
1
2
3
该类的子类有:
AuditEventsMvcEndpoint
DocsMvcEndpoint
HalJsonMvcEndpoint
HeapdumpMvcEndpoint
JolokiaMvcEndpoint
LogFileMvcEndpoint
这些子类的实现我们后续进行分析…
AbstractEndpointMvcAdapter
AbstractEndpointMvcAdapter 实现了NamedMvcEndpoint接口,是MvcEndpoint的抽象基类
字段,构造器如下:
// 被代理的底层端点(端点子类
private final E delegate;
// 端点URL路径
private String path;
//
public AbstractEndpointMvcAdapter(E delegate) {
Assert.notNull(delegate, "Delegate must not be null");
this.delegate = delegate;
}
1
2
3
4
5
6
7
8
9
10
11
NamedMvcEndpoint接口方法实现如下:
getName,返回被代理的Endpoint的id,代码如下:
public String getName() {
return this.delegate.getId();
}
1
2
3
getPath –> 如果path不等于null,则直接返回,否则使用/+Endpoint的id,代码如下:
public String getPath() {
// 如果path不等于null,则直接返回,否则使用/+Endpoint的id
return (this.path != null ? this.path : "/" + this.delegate.getId());
}
1
2
3
4
isSensitive–> 直接调用被代理的Endpoint的isSensitive方法即可.代码如下:
public boolean isSensitive() {
return this.delegate.isSensitive();
}
1
2
3
getEndpointType –> 返回被代理的 Endpoint的类型即可,代码如下:
public Class<? extends Endpoint> getEndpointType() {
return this.delegate.getClass();
}
1
2
3
AbstractEndpointMvcAdapter 还声明了2个方法,供子类使用
getDisabledResponse–> 返回该相应当所代理的endpoint不可用时.代码如下:
protected ResponseEntity<?> getDisabledResponse() {
return MvcEndpoint.DISABLED_RESPONSE;
}
1
2
3
即:
ResponseEntity<Map<String, String>> DISABLED_RESPONSE = new ResponseEntity<Map<String, String>>(
Collections.singletonMap("message", "This endpoint is disabled"),
HttpStatus.NOT_FOUND);
1
2
3
invoke–> 调用代理的Endpoint,并返回调用结果,代码如下:
protected Object invoke() {
if (!this.delegate.isEnabled()) {
return getDisabledResponse();
}
return this.delegate.invoke();
}
1
2
3
4
5
6
端点不可用(禁用),则返回默认的不可用信息.当Endpoint被禁用时,是不会注册的.
否则,调用Endpoint的invoke方法
EndpointMvcAdapter
EndpointMvcAdapter,继承自AbstractEndpointMvcAdapter,将Endpoint适配为MvcEndpoint. 构造器如下:
public EndpointMvcAdapter(Endpoint<?> delegate) {
super(delegate);
}
1
2
3
该类覆写了invoke,使其能过被spring mvc 处理–> 暴露接口(关于这部分,我们后续有分析),代码如下:
@Override
@ActuatorGetMapping
@ResponseBody
public Object invoke() {
return super.invoke();
}
1
2
3
4
5
6
其中@ActuatorGetMapping 就是@RequestMapping的封装,被该注解标注的方法,其请求方式为get,产生的数据格式为application/vnd.spring-boot.actuator.v1+json和application/json.代码如下:
@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 {};
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EnvironmentMvcEndpoint
该类继承自EndpointMvcAdapter,实现了EnvironmentAware,因此,该类也就持有了Environment.
字段,构造器如下:
private Environment environment;
public EnvironmentMvcEndpoint(EnvironmentEndpoint delegate) {
super(delegate);
}
1
2
3
4
5
此时, EnvironmentMvcEndpoint 也就持有了EnvironmentEndpoint的实例
该类声明了1个被@ActuatorGetMapping注解的方法,value,代码如下:
@ActuatorGetMapping("/{name:.*}")
@ResponseBody
@HypermediaDisabled
public Object value(@PathVariable String name) {
if (!getDelegate().isEnabled()) {
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's
// disabled
return getDisabledResponse();
}
return new NamePatternEnvironmentFilter(this.environment).getResults(name);
}
1
2
3
4
5
6
7
8
9
10
11
@ActuatorGetMapping(“/{name:.*}”)与@PathVariable String name –> rest风格,将其name注入到方法的参数name中,匹配规则是任意字符
@ResponseBody –> 返回json格式的数据
@HypermediaDisabled–>表明该MvcEndpoint或者@RequestMapping注解的方法不会生成hypermedia 的响应.代码如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface HypermediaDisabled {
}
1
2
3
4
5
其方法逻辑如下:
如果EnvironmentEndpoint不可用,则返回Disabled Response.
否则实例化NamePatternEnvironmentFilter,调用其getResults方法获得对应name的属性值.
NamePatternEnvironmentFilter继承自NamePatternFilter.
NamePatternFilter:
NamePatternFilter–> 可以使用name正则表达式过滤源数据的实用工具类,用来检测检测名称是否是经典的“single value”键或正则表达式.子类必须实现getValue,getNames 方法.该类是1个泛型类,其泛型参数T 代表着原始数据的类型.
字段,构造器如下:
private static final String[] REGEX_PARTS = { "*", "$", "^", "+", "[" };
private final T source;
NamePatternFilter(T source) {
this.source = source;
}
1
2
3
4
5
声明了3个抽象方法:
protected abstract void getNames(T source, NameCallback callback);
protected abstract Object getValue(T source, String name);
protected abstract Object getOptionalValue(T source, String name);
1
2
3
NamePatternFilter中声明了1个方法,代码如下:
public Map<String, Object> getResults(String name) {
// 1. 如果name含有 "*", "$", "^", "+", "[" ,则认为是一个正则表达式,将其返回Pattern.否则返回null
Pattern pattern = compilePatternIfNecessary(name);
if (pattern == null) {
// 2. 如果pattern 等于null,则说明name 是一个普通字符串,调用getValue 这一抽象方法获得value,放入result后返回
Object value = getValue(this.source, name);
Map<String, Object> result = new HashMap<String, Object>();
result.put(name, value);
return result;
}
// 3. 实例化 ResultCollectingNameCallback
ResultCollectingNameCallback resultCollector = new ResultCollectingNameCallback(
pattern);
// 4. 抽象方法
getNames(this.source, resultCollector);
// 5, 返回结果
return resultCollector.getResults();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
如果name含有 “*”, “$”, “^”, “+”, “[” ,则认为是一个正则表达式,将其返回Pattern.否则返回null,代码如下:
private Pattern compilePatternIfNecessary(String name) {
for (String part : REGEX_PARTS) {
if (name.contains(part)) {
try {
return Pattern.compile(name);
}
catch (PatternSyntaxException ex) {
return null;
}
}
}
return null;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
如果pattern 等于null,则说明name 是一个普通字符串,调用getValue 这一抽象方法获得value,放入result后返回
实例化 ResultCollectingNameCallback,该类实现了NameCallback接口,该接口只声明了如下方法:
void addName(String name);
1
ResultCollectingNameCallback中的字段,构造器如下:
// 将name 转换为正则所对应的对象
private final Pattern pattern;
// 结果集
private final Map<String, Object> results = new LinkedHashMap<String, Object>();
ResultCollectingNameCallback(Pattern pattern) {
this.pattern = pattern;
}
1
2
3
4
5
6
7
其addName方法如下:
public void addName(String name) {
// 1. 如果name 符合正则,则通过调用getOptionalValue 获得值后加入到results
if (this.pattern.matcher(name).matches()) {
Object value = getOptionalValue(NamePatternFilter.this.source, name);
if (value != null) {
this.results.put(name, value);
}
}
}
1
2
3
4
5
6
7
8
9
如果name 符合正则,则通过调用getOptionalValue 获得值后加入到results
此外还声明了get方法,来暴露结果集,代码如下:
public Map<String, Object> getResults() {
return this.results;
}
1
2
3
执行getNames方法,进行结果的收集
返回结果
NamePatternEnvironmentFilter
继承了NamePatternFilter 接口,泛型参数为Environment
抽象方法分别实现如下:
getValue:
protected Object getValue(Environment source, String name) {
// 1. 获取值,如果没获取到,则抛出NoSuchPropertyException,否则,对其进行脱敏
Object result = getValue(name);
if (result == null) {
throw new NoSuchPropertyException("No such property: " + name);
}
return ((EnvironmentEndpoint) getDelegate()).sanitize(name, result);
}
1
2
3
4
5
6
7
8
调用getValue方法获取值,如果没获取到,则抛出NoSuchPropertyException,否则,对其进行脱敏.
getValue–>直接从environment 中获取值,代码如下:
private Object getValue(String name) {
// 直接从environment 中获取值,
return ((EnvironmentEndpoint) getDelegate()).getResolver().getProperty(name,
Object.class);
}
1
2
3
4
5
getNames–> 遍历source中的PropertySources,将PropertySource的属性名依次的加入到ResultCollectingNameCallback中.代码如下:
protected void getNames(Environment source, NameCallback callback) {
if (source instanceof ConfigurableEnvironment) {
// 遍历source中的PropertySources,将PropertySource的属性名依次的加入到ResultCollectingNameCallback中
getNames(((ConfigurableEnvironment) source).getPropertySources(),
callback);
}
}
private void getNames(PropertySources propertySources, NameCallback callback) {
for (PropertySource<?> propertySource : propertySources) {
if (propertySource instanceof EnumerablePropertySource) {
EnumerablePropertySource<?> source = (EnumerablePropertySource<?>) propertySource;
for (String name : source.getPropertyNames()) {
callback.addName(name);
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
getOptionalValue,代码如下:
protected Object getOptionalValue(Environment source, String name) {
// 1. 获得name对应的属性值
Object result = getValue(name);
if (result != null) {
// 2. 如果属性值存在则进行脱敏
result = ((EnvironmentEndpoint) getDelegate()).sanitize(name, result);
}
// 3. 否则直接返回null
return result;
}
1
2
3
4
5
6
7
8
9
10
获得name对应的属性值
如果属性值存在则进行脱敏
否则直接返回null
属性配置(有@ConfigurationProperties(prefix = “endpoints.env”)注解):
endpoints.env.path=/env
1
自动装配:
在EndpointWebMvcManagementContextConfiguration中进行了配置,如下:
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(EnvironmentEndpoint.class)
@ConditionalOnEnabledEndpoint("env")
public EnvironmentMvcEndpoint environmentMvcEndpoint(EnvironmentEndpoint delegate) {
return new EnvironmentMvcEndpoint(delegate);
}
1
2
3
4
5
6
7
@Bean –> 注册1个id为 environmentMvcEndpoint,类型为EnvironmentMvcEndpoint的bean
@ConditionalOnMissingBean–>BeanFactory中不存在EnvironmentMvcEndpoint类型的bean时生效
@ConditionalOnBean(EnvironmentEndpoint.class) –> 当BeanFactory中存在EnvironmentEndpoint类型的Bean时生效
@ConditionalOnEnabledEndpoint(“env”)–> 如果配置有endpoints.env.enabled = true 或者endpoints.enabled= true 则该配置生效.关于此处的实现我们后续有文章进行分析.
LoggersMvcEndpoint
字段,构造器如下:
private final LoggersEndpoint delegate;
public LoggersMvcEndpoint(LoggersEndpoint delegate) {
super(delegate);
this.delegate = delegate;
}
1
2
3
4
5
6
该类声明了2个@ActuatorGetMapping(“/{name:.*}”)注解的方法:
get,代码如下:
@ActuatorGetMapping("/{name:.*}")
@ResponseBody
@HypermediaDisabled
public Object get(@PathVariable String name) {
if (!this.delegate.isEnabled()) {
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's
// disabled
return getDisabledResponse();
}
LoggerLevels levels = this.delegate.invoke(name);
return (levels == null ? ResponseEntity.notFound().build() : levels);
}
1
2
3
4
5
6
7
8
9
10
11
12
@ActuatorGetMapping(“/{name:.*}”) 与 @PathVariable String name–> rest风格,将其name注入到方法的参数name中,匹配规则是任意字符
方法逻辑如下:
如果LoggersEndpoint不可用,则返回默认的不可用消息
否则,调用LoggersEndpoint#invoke 获得LoggerLevels.代码如下:
public LoggerLevels invoke(String name) {
Assert.notNull(name, "Name must not be null");
LoggerConfiguration configuration = this.loggingSystem
.getLoggerConfiguration(name);
return (configuration == null ? null : new LoggerLevels(configuration));
}
1
2
3
4
5
6
调用LoggingSystem#getLoggerConfiguration 获得LoggerConfiguration
如果LoggerConfiguration等于null,则返回null,否则,返回LoggerLevels
由于spring boot 默认使用的是logback,因此,此处调用的是LogbackLoggingSystem中的实现,代码如下:
public LoggerConfiguration getLoggerConfiguration(String loggerName) {
return getLoggerConfiguration(getLogger(loggerName));
}
1
2
3
调用getLogger 获得对应的logger,代码如下:
private ch.qos.logback.classic.Logger getLogger(String name) {
LoggerContext factory = getLoggerContext();
if (StringUtils.isEmpty(name) || ROOT_LOGGER_NAME.equals(name)) {
name = Logger.ROOT_LOGGER_NAME;
}
return factory.getLogger(name);
}
1
2
3
4
5
6
7
获得LoggerContext
如果name为空,或者name等于ROOT,则name赋值为ROOT
根据name获得对应的Logger
调用getLoggerConfiguration获得对应的LoggerConfiguration.
private LoggerConfiguration getLoggerConfiguration(
ch.qos.logback.classic.Logger logger) {
if (logger == null) {
return null;
}
LogLevel level = LEVELS.convertNativeToSystem(logger.getLevel());
LogLevel effectiveLevel = LEVELS
.convertNativeToSystem(logger.getEffectiveLevel());
String name = logger.getName();
if (!StringUtils.hasLength(name) || Logger.ROOT_LOGGER_NAME.equals(name)) {
name = ROOT_LOGGER_NAME;
}
return new LoggerConfiguration(name, level, effectiveLevel);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
如果logger等于null,返回null
根据logger对应的level,影响的Level分别获得LogLevel
获得logger对应的name,如果name等于null,或者等于root,则将其赋值为root
实例化LoggerConfiguration进行返回
如果 LoggerLevels,则返回ResponseEntity–>状态码为404,否则,直接返回LoggerLevels
set–>该方法用于设置logger的日志级别.代码如下:
@ActuatorPostMapping("/{name:.*}")
@ResponseBody
@HypermediaDisabled
public Object set(@PathVariable String name,
@RequestBody Map<String, String> configuration) {
// 1. 如果不可用,则返回默认的不可用信息
if (!this.delegate.isEnabled()) {
// Shouldn't happen - MVC endpoint shouldn't be registered when delegate's
// disabled
return getDisabledResponse();
}
// 2. 根据configuration获得LogLevel,然后对指定的logger设置日志级别
LogLevel logLevel = getLogLevel(configuration);
this.delegate.setLogLevel(name, logLevel);
// 3. 返回ok
return ResponseEntity.ok().build();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
逻辑如下:
如果不可用,则返回默认的不可用信息
根据configuration获得LogLevel,代码如下:
private LogLevel getLogLevel(Map<String, String> configuration) {
String level = configuration.get("configuredLevel");
try {
return (level == null ? null : LogLevel.valueOf(level.toUpperCase()));
}
catch (IllegalArgumentException ex) {
throw new InvalidLogLevelException(level);
}
}
1
2
3
4
5
6
7
8
9
获得configuredLevel对应的值
如果没有配置,则返回null,否则,返回LogLevel,合法值有TRACE, DEBUG, INFO, WARN, ERROR, FATAL, OFF.如果值不合法,抛出InvalidLogLevelException异常.由于InvalidLogLevelException类注有@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = “No such log level”),因此,当抛出该异常时,会返回相应的信息.
对指定的logger设置日志级别,代码如下:
public void setLogLevel(String name, LogLevel level) {
Assert.notNull(name, "Name must not be empty");
this.loggingSystem.setLogLevel(name, level);
}
1
2
3
4
默认情况下,会执行LogbackLoggingSystem#setLogLevel,代码如下:
public void setLogLevel(String loggerName, LogLevel level) {
ch.qos.logback.classic.Logger logger = getLogger(loggerName);
if (logger != null) {
logger.setLevel(LEVELS.convertSystemToNative(level));
}
}
1
2
3
4
5
6
根据对应的logger名获得Logger
将传入的loggerLevel转换为LogBack对应的日志级别
修改日志级别
返回ok
至此,我们明白,要想修改日志级别,可以通过对/loggers/logger名,发送post请求,传入类上如下格式的参数:
{"configuredLevel":"INFO"}
1
即可修改日志级别
参数配置–>因为该类有@ConfigurationProperties(prefix = “endpoints.loggers”)注解:
endpoints.loggers.path=/logfile # Endpoint path
1
自动化装配:
在EndpointWebMvcManagementContextConfiguration中进行了声明,代码如下:
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(LoggersEndpoint.class)
@ConditionalOnEnabledEndpoint("loggers")
public LoggersMvcEndpoint loggersMvcEndpoint(LoggersEndpoint delegate) {
return new LoggersMvcEndpoint(delegate);
}
1
2
3
4
5
6
7
@Bean –> 注册1个id为loggersMvcEndpoint,类型为LoggersMvcEndpoint的bean
@ConditionalOnMissingBean–> 当BeanFactory中不包含类型为LoggersMvcEndpoint的bean时生效
@ConditionalOnBean(LoggersEndpoint.class)–> 当BeanFactory中存在LoggersEndpoint类型的bean时生效
@ConditionalOnEnabledEndpoint(“loggers”)–> 如果配置有endpoints. loggers.enabled = true 或者endpoints.enabled= true 则该配置生效
ShutdownMvcEndpoint
该类继承自EndpointMvcAdapter
构造器如下:
public ShutdownMvcEndpoint(ShutdownEndpoint delegate) {
super(delegate);
}
1
2
3
invoke 实现如下:
@PostMapping(produces = { ActuatorMediaTypes.APPLICATION_ACTUATOR_V1_JSON_VALUE,
MediaType.APPLICATION_JSON_VALUE })
@ResponseBody
@Override
public Object invoke() {
if (!getDelegate().isEnabled()) {
return new ResponseEntity<Map<String, String>>(
Collections.singletonMap("message", "This endpoint is disabled"),
HttpStatus.NOT_FOUND);
}
return super.invoke();
}
1
2
3
4
5
6
7
8
9
10
11
12
如果ShutdownEndpoint不可用,则返回{message:This endpoint is disabled},否则,调用ShutdownEndpoint#invoke,关闭spring boot 程序
参数配置–> 因为该类声明了@ConfigurationProperties(prefix = “endpoints.shutdown”):
endpoints.shutdown.path= # Endpoint path.
1
自动装配:
在EndpointWebMvcManagementContextConfiguration中进行了声明,代码如下:
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(ShutdownEndpoint.class)
@ConditionalOnEnabledEndpoint(value = "shutdown", enabledByDefault = false)
public ShutdownMvcEndpoint shutdownMvcEndpoint(ShutdownEndpoint delegate) {
return new ShutdownMvcEndpoint(delegate);
}
1
2
3
4
5
6
7
@Bean –> 注册1个id为shutdownMvcEndpoint,类型为ShutdownMvcEndpoint的bean
@ConditionalOnMissingBean–> 当BeanFactory中不包含类型为ShutdownMvcEndpoint的bean时生效
@ConditionalOnBean(ShutdownEndpoint.class)–> 当BeanFactory中存在ShutdownEndpoint类型的bean时生效
@ConditionalOnEnabledEndpoint(“shutdown”, enabledByDefault = false) –>如果配置有endpoints. shutdown.enabled = true则该配置生效,如果没有配置,该配置不生效
MetricsMvcEndpoint
MetricsMvcEndpoint–> 继承自EndpointMvcAdapter.
字段构造器如下:
private final MetricsEndpoint delegate;
public MetricsMvcEndpoint(MetricsEndpoint delegate) {
super(delegate);
this.delegate = delegate;
}
1
2
3
4
5
6
该类声明了1个被@ActuatorGetMapping注解的方法,如下:
@ActuatorGetMapping("/{name:.*}")
@ResponseBody
@HypermediaDisabled
public Object value(@PathVariable String name) {
if (!this.delegate.isEnabled()) {
return getDisabledResponse();
}
return new NamePatternMapFilter(this.delegate.invoke()).getResults(name);
}
1
2
3
4
5
6
7
8
9
如果不可用,则返回默认的不可用信息
实例化NamePatternMapFilter ,之后调用其getResults方法,根据传入的name 获得对应的map,key –>name,value–>name所对应的Metric的值
NamePatternMapFilter:
继承了NamePatternFilter 接口,泛型参数为Map
抽象方法实现如下:
getValue–> 调用getOptionalValue获得MetricsEndpoint中Metrics名字为传入的值所对应的Metrics的值.,如果获取不到,则抛出NoSuchMetricException.代码如下:
protected Object getValue(Map<String, ?> source, String name) {
Object value = getOptionalValue(source, name);
if (value == null) {
throw new NoSuchMetricException("No such metric: " + name);
}
return value;
}
1
2
3
4
5
6
7
NoSuchMetricException代码如下:
@ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "No such metric")
public static class NoSuchMetricException extends RuntimeException {
public NoSuchMetricException(String string) {
super(string);
}
}
1
2
3
4
5
6
7
8
当抛出该异常时,返回的状态码为404,reason为No such metric
getOptionalValue–> 获得MetricsEndpoint中Metrics名字为传入的值所对应的Metrics的值.代码如下:
protected Object getOptionalValue(Map<String, ?> source, String name) {
return source.get(name);
}
1
2
3
getNames–>遍历MetricsEndpoint中返回的Metrics名,如果Metrics名符合正则的话(MetricsMvcEndpoint#value方法传入的是正则),则加入到ResultCollectingNameCallback的result中.代码如下:
protected void getNames(Map<String, ?> source, NameCallback callback) {
for (String name : source.keySet()) {
try {
callback.addName(name);
}
catch (NoSuchMetricException ex) {
// Metric with null value. Continue.
}
}
}
1
2
3
4
5
6
7
8
9
10
自动装配:
声明在EndpointWebMvcManagementContextConfiguration中.代码如下:
@Bean
@ConditionalOnMissingBean
@ConditionalOnBean(MetricsEndpoint.class)
@ConditionalOnEnabledEndpoint("metrics")
public MetricsMvcEndpoint metricsMvcEndpoint(MetricsEndpoint delegate) {
return new MetricsMvcEndpoint(delegate);
}
1
2
3
4
5
6
7
@Bean –> 注册1个id为metricsMvcEndpoint,类型为MetricsMvcEndpoint的bean
@ConditionalOnMissingBean–> BeanFactory中不存在MetricsMvcEndpoint类型的bean时生效
@ConditionalOnBean(MetricsEndpoint.class)–> BeanFactory中存在MetricsEndpoint类型的bean时生效
@ConditionalOnEnabledEndpoint(“metrics”) –> 如果配置有endpoints. metrics.enabled = true 或者endpoints.enabled= true 则该配置生效
---------------------
作者:一个努力的码农
来源:CSDN
原文:https://blog.csdn.net/qq_26000415/article/details/79220873
版权声明:本文为博主原创文章,转载请附上博文链接!
spring boot 源码解析52-actuate中MVCEndPoint解析的更多相关文章
- Spring Boot源码分析-配置文件加载原理
在Spring Boot源码分析-启动过程中我们进行了启动源码的分析,大致了解了整个Spring Boot的启动过程,具体细节这里不再赘述,感兴趣的同学可以自行阅读.今天让我们继续阅读源码,了解配置文 ...
- 曹工说Spring Boot源码(17)-- Spring从xml文件里到底得到了什么(aop:config完整解析【中】)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(7)-- Spring解析xml文件,到底从中得到了什么(上)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(8)-- Spring解析xml文件,到底从中得到了什么(util命名空间)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(9)-- Spring解析xml文件,到底从中得到了什么(context命名空间上)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- # 曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(12)-- Spring解析xml文件,到底从中得到了什么(context:component-scan完整解析)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(15)-- Spring从xml文件里到底得到了什么(context:load-time-weaver 完整解析)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- 曹工说Spring Boot源码(16)-- Spring从xml文件里到底得到了什么(aop:config完整解析【上】)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
随机推荐
- html书写行内元素时-tab和换行会在行内元素间引入间距
目录 html文本中的控制字符会被解析为文本节点 书写行内元素时,换行符LF与水平制表符HT会引入莫名的元素间间隔 其他控制字符是否会引入间距的验证 html文本中的控制字符会被解析为文本节点 举例: ...
- mysql 多条数据中,分组获取值最大的数据记录
摘要: 多条纪录中,几个字段相同,但是其中一个或者多个字段不同,则去该字段最大(这里只有一个不同) 源数据: 目的是移除:在同一天中只能存在一天数据,则取审核日期最大,数据库脚本如下: SELECT ...
- Hyper-v,装XP的时候没有驱动上不了网,装这个集成服务(vmguest.iso )就可以了
Win10自带的Hyper-v,装XP的时候没有驱动上不了网,装这个集成服务(vmguest.iso )就可以了 安装后无法识别显卡及网卡设备,不能与虚拟网络通讯,设备管理器中显示三个未知设备. 在X ...
- 简单mvc---模拟Springmvc
1.注解篇 Auwowrited package org.aaron.mvc.annaotation; import java.lang.annotation.Documented; import j ...
- windows开启PostgreSQL数据库远程访问
1.在PostgreSQL安装目录下data文件夹,打开pg_hba.conf文件,新增允许访问的ip 2.打开postgresql.conf,将listen_addresses = 'localho ...
- nginx实现tcp的反向代理
nginx不仅可以实现http的反向代理,同时也支持TCP的反向代理以SSH为例1.编译的时候需要加入--with-stream这个参数,以加载ngx_stream_core_module这个模块2 ...
- 【会话技术】Cookie技术 案例:访问时间
创建时间:6.30 代码: package cookie; import java.io.IOException; import java.text.SimpleDateFormat; import ...
- 使用Supervisor管理Django应用进程
官方文档 1.安装 pip install supervisor 2.使用说明 2.1 查看默认配置 echo_supervisord_conf 一般情况下,不需要去修改默认配置,而是将默认配置重定 ...
- 在windows下安装Superset
前言 最近想用一下Superset,这个是一个开源项目,可以直接通过写sql来生成图表,有时候对一些图表需求比较多的时候,可以用的上. Superset是由Airbnb(知名在线房屋短租公司)开源BI ...
- Layer获取iframe的dom元素及调用iframe页的js方法
1. 父页面点击第一个按钮触发,获取子页面中的body元素,调用子页面中定义的js方法 yes : function(index,layero){ //获取iframe的body元素 var body ...