spring boot 源码解析43-JmxMetricWriter详解
前言
本文我们来介绍JmxMetricWriter, JmxMetricWriter是依赖于spring jmx 来实现的. 因此本文的开头先介绍一下spring boot 与jmx的集成,然后分析JmxMetricWriter源码.
spring boot jmx
首先确保在pom文件中加入了如下依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId> </dependency>
只要加入如下依赖,就会自动装配如下bean:
- AnnotationMBeanExporter
- objectNamingStrategy
- MBeanServer
- EndpointMBeanExporter
- 一系列的jmxEndpoint…
以上的配置类在JmxAutoConfiguration,EndpointMBeanExportAutoConfiguration中进行了声明
在com.example.demo.jmx包下创建Sumer类,代码如下:
package com.example.demo.jmx; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedOperationParameter; import org.springframework.jmx.export.annotation.ManagedOperationParameters; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.stereotype.Component;@Component @ManagedResource(description = "spring boot jmx demo", objectName = "bean:name=sumer") public class Sumer {@ManagedOperation(description = "Add two numbers")@ManagedOperationParameters({ @ManagedOperationParameter(name = "x", description = "The first number"),@ManagedOperationParameter(name = "y", description = "The second number") })public int add(int x, int y) {return x + y;} }
其中:
@ManagedResource(description = “spring boot jmx demo”, objectName = “bean:name=sumer”)–> 指定该bean要进行jmx暴露, description是在jmx中的描述,如图:
objectName = “bean:name=sumer” 中的bean 指明了其scope,name=test 指定了在jmx中的名字,如图:
@ManagedOperation –> 指定在jmx中Operation的名字
- @ManagedAttribute–> 指定在jmx中attribute的属性
- @ManagedOperationParameter and @ManagedOperationParameters–> 指明ManagedOperation中的参数.
此处为何scope是bean,name是test?
原因: 自动装配了ParentAwareNamingStrategy.其getObjectName代码如下:
public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {
ObjectName name = super.getObjectName(managedBean, beanKey);
Hashtable<String, String> properties = new Hashtable<String, String>();
properties.putAll(name.getKeyPropertyList());
if (this.ensureUniqueRuntimeObjectNames) {
properties.put("identity", ObjectUtils.getIdentityHexString(managedBean));
}
else if (parentContextContainsSameBean(this.applicationContext, beanKey)) {
properties.put("context",
ObjectUtils.getIdentityHexString(this.applicationContext));
}
return ObjectNameManager.getInstance(name.getDomain(), properties);
}调用父类(MetadataNamingStrategy)的getObjectName方法,获得ObjectName.代码如下:
public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {Class<?> managedClass = AopUtils.getTargetClass(managedBean);// 1. 获得ManagedResourceManagedResource mr = this.attributeSource.getManagedResource(managedClass);// Check that an object name has been specified.if (mr != null && StringUtils.hasText(mr.getObjectName())) {// 2. 如果存在ManagedResource,并且ObjectName有值,则直接实例化ObjectNamereturn ObjectNameManager.getInstance(mr.getObjectName());}else {try {// 3. 否则,根据beanKey生成ObjectNamereturn ObjectNameManager.getInstance(beanKey);}catch (MalformedObjectNameException ex) {String domain = this.defaultDomain;if (domain == null) {domain = ClassUtils.getPackageName(managedClass);}Hashtable<String, String> properties = new Hashtable<String, String>();properties.put("type", ClassUtils.getShortName(managedClass));properties.put("name", beanKey);return ObjectNameManager.getInstance(domain, properties);}} }
- 获得ManagedResource
- 如果存在ManagedResource,并且ObjectName有值,则直接实例化ObjectName
- 否则,根据beanKey生成ObjectName
将name中的所有KeyProperty 放入到properties中
- 如果ensureUniqueRuntimeObjectNames等于true,则放入key–>identity,value–>该bean的hash值
- 如果在父Context中存在id为beanKey的bean,则存入key–>context,value–>当前上下文的hash值
生成ObjectName
对于当前,我们在Sumer上声明了@ManagedResource,并且配置了objectName为bean:name=sumer,因此最终会在1.1.2 步返回,最后jmx中的规范生成ObjectName
打开jvisualvm,链接上我们的spring boot 程序,在Mbeans中的页面可以看到我们配置的Sumer,在其中的Operations中可以看到我们声明的方法add,输入1,2后,可以看到其返回值为3,如图:
参考链接:
spring-jmx
第20章-使用JMX管理Spring Bean
Spring与JMX集成
spring通过annotation注解注册MBean到JMX实现监控java运行状态
JmxMetricWriter
JmxMetricWriter实现了MetricWriter接口.
其声明了@ManagedResource注解,如下:
@ManagedResource(description = "MetricWriter for pushing metrics to JMX MBeans.")
字段,构造器如下:
private static final Log logger = LogFactory.getLog(JmxMetricWriter.class);// string -->Metric 名称,value --> MetricValue private final ConcurrentMap<String, MetricValue> values = new ConcurrentHashMap<String, MetricValue>();private final MBeanExporter exporter;private ObjectNamingStrategy namingStrategy = new DefaultMetricNamingStrategy();private String domain = "org.springframework.metrics";public JmxMetricWriter(MBeanExporter exporter) {this.exporter = exporter; }
其有3个被@ManagedOperation注解的方法,如下:
increment(String name, long value),代码如下:
@ManagedOperation public void increment(String name, long value) {increment(new Delta<Long>(name, value)); }
调用
public void increment(Delta<?> delta) {MetricValue counter = getValue(delta.getName());counter.increment(delta.getValue().longValue()); }
调用getValue方法获得对应的MetricValue.代码如下:
private MetricValue getValue(String name) {// 1. 从缓存中查找,如果有则直接返回,否则,进行第2步MetricValue value = this.values.get(name);if (value == null) {// 2.1 实例化MetricValue,放入缓存中value = new MetricValue();MetricValue oldValue = this.values.putIfAbsent(name, value);if (oldValue != null) {value = oldValue;}try {// 2.2 生成ObjectName// 2.3 进行注册this.exporter.registerManagedResource(value, getName(name, value));}catch (Exception ex) {// Could not register mbean, maybe just a race condition}}return value; }
- 从缓存中查找,如果有则直接返回,否则,进行第2步
如果缓存中不存在,则
- 实例化MetricValue,放入缓存中
生成ObjectName.代码如下:
private ObjectName getName(String name, MetricValue value)throws MalformedObjectNameException {// 1. 进行拼接,如传入的是couter.test.increment,则返回org.springframework.metrics:type=MetricValue,name=couter.test.incrementString key = String.format(this.domain + ":type=MetricValue,name=%s", name);return this.namingStrategy.getObjectName(value, key); }
- 进行拼接,如传入的是couter.test.increment,则返回org.springframework.metrics:type=MetricValue,name=couter.test.increment
调用DefaultMetricNamingStrategy#getObjectName方法.代码如下:
public ObjectName getObjectName(Object managedBean, String beanKey)throws MalformedObjectNameException {// 1. 获得ObjectNameObjectName objectName = this.namingStrategy.getObjectName(managedBean, beanKey);// 2. 获得ObjectName对应的domainString domain = objectName.getDomain();// 3. 获得ObjectName对应的key propertiesHashtable<String, String> table = new Hashtable<String, String>(objectName.getKeyPropertyList());String name = objectName.getKeyProperty("name");if (name != null) {// 4. 如果ObjectName中存在name的属性,则进行删除table.remove("name");// 5. 将name通过点进行分割String[] parts = StringUtils.delimitedListToStringArray(name, ".");// 5.1 将第1个设置为typetable.put("type", parts[0]);// 5.2 如果name存在1个点,则进行判断: 如果name存在2个点以上,则存入key-->name,value -->parts[1],否则,存入value-->name,value -->parts[1]// 如传入的name是couter.test.increment,则此时存入的是key-->name,value --> testif (parts.length > 1) {table.put(parts.length > 2 ? "name" : "value", parts[1]);}// 5.2 如果name存在2个点,则进行存入,key--> value,value-->name 对应的第2个点以后的值,如传入的name是couter.test.increment,则此时存入的是key-->value,value --> incrementif (parts.length > 2) {table.put("value",name.substring(parts[0].length() + parts[1].length() + 2));}}return new ObjectName(domain, table); }
获得ObjectName.代码如下:
public ObjectName getObjectName(Object managedBean, String beanKey) throws MalformedObjectNameException {String objectName = null;if (this.mergedMappings != null) {objectName = this.mergedMappings.getProperty(beanKey);}if (objectName == null) {objectName = beanKey;}return ObjectNameManager.getInstance(objectName); }
如果缓存存在,则从缓存中获取否则使用beanKey来作为ObjectName.由于此时mergedMappings等于null,因此,此时使用的是传入的beanKey来作为ObjectName
- 获得ObjectName对应的domain
- 获得ObjectName对应的key properties
如果objectName中存在name的属性
- 删除name
- 将name通过点进行分割
- 将name通过点进行分割
- 将第1个设置为type
- 如果name存在1个点,则进行判断: 如果name存在2个点以上,则存入key–>name,value –>parts[1],否则,存入value–>name,value –>parts[1].如传入的name是couter.test.increment,则此时存入的是key–>name,value –> test
- 如果name存在2个点,则进行存入,key–> value,value–>name 对应的第2个点以后的值,如传入的name是couter.test.increment,则此时存入的是key–>value,value –> increment
返回ObjectName
- 进行注册
- 调用MetricValue#increment方法
这里有必要说明一下 MetricValue:
字段,构造器如下:
private double value;private long lastUpdated = 0;
声明了如下方法:
setValue,代码如下:
public void setValue(double value) {if (this.value != value) {this.lastUpdated = System.currentTimeMillis();}this.value = value; }
increment,代码如下:
public void increment(long value) {this.lastUpdated = System.currentTimeMillis();this.value += value; }
getValue,代码如下:
@ManagedAttribute public double getValue() {return this.value; }
getLastUpdated,代码如下:
@ManagedAttribute public Date getLastUpdated() {return new Date(this.lastUpdated); }
set,代码如下:
@ManagedOperation public void set(String name, double value) {set(new Metric<Double>(name, value)); }
调用:
public void set(Metric<?> value) {MetricValue metric = getValue(value.getName());metric.setValue(value.getValue().doubleValue()); }
- 通过 getValue方法获得对应的 MetricValue
- 调用MetricValue对应的setValue方法.
reset,代码如下:
@ManagedOperation public void reset(String name) {// 1. 从缓存中删除MetricValue value = this.values.remove(name);if (value != null) {try {// We can unregister the MBean, but if this writer is on the end of an// Exporter the chances are it will be re-registered almost immediately.// 2.从MBeanExporter中删除this.exporter.unregisterManagedResource(getName(name, value));}catch (MalformedObjectNameException ex) {logger.warn("Could not unregister MBean for " + name);}}}
- 从缓存中删除
- 从MBeanExporter中删除
配置:
注意,该类不支持自动装配.
需要在配置类做如下配置即可:
@Bean @ExportMetricWriter public JmxMetricWriter jmxMetricWriter(@Qualifier("mbeanExporter") MBeanExporter exporter) {return new JmxMetricWriter(exporter); }
- @Bean–> 注册1个id为jmxMetricWriter,类型为JmxMetricWriter的bean
- @ExportMetricWriter–>@Qualifier注解,表明该类是用来暴露metrics数据的
@Qualifier(“mbeanExporter”) MBeanExporter exporter–> spring boot 中会自动装配1个id为mbeanExporter,类型为MBeanExporter的bean,在JmxAutoConfiguration中进行了声明,如下:
@Bean @Primary @ConditionalOnMissingBean(value = MBeanExporter.class, search = SearchStrategy.CURRENT) public AnnotationMBeanExporter mbeanExporter(ObjectNamingStrategy namingStrategy) {AnnotationMBeanExporter exporter = new AnnotationMBeanExporter();exporter.setRegistrationPolicy(RegistrationPolicy.FAIL_ON_EXISTING);exporter.setNamingStrategy(namingStrategy);String server = this.propertyResolver.getProperty("server", "mbeanServer");if (StringUtils.hasLength(server)) {exporter.setServer(this.beanFactory.getBean(server, MBeanServer.class));}return exporter; }
启用spring boot 应用后,打开jvisualvm,链接spring boot 应用后,会发现有如下mBean:
其Operations标签页如下:现在,测试吧,是不是很简单.
spring boot 源码解析43-JmxMetricWriter详解相关推荐
- spring boot 源码解析23-actuate使用及EndPoint解析
前言 spring boot 中有个很诱人的组件–actuator,可以对spring boot应用做监控,只需在pom文件中加入如下配置即可: <dependency><group ...
- spring boot 源码解析15-spring mvc零配置
前言 spring boot 是基于spring 4 的基础上的一个框架,spring 4 有一个新特效–>基于java config 实现零配置.而在企业的实际工作中,spring 都是和sp ...
- spring boot 源码解析52-actuate中MVCEndPoint解析
前言 之前的几篇文章分析了spring boot 中有关endpoint的实现,细心的朋友可以发现,在org.springframework.boot.actuate.endpoint.mvc 包下也 ...
- spring boot 源码解析31-AuthenticationAuditListener,AuthorizationAuditListener
前言 这篇文章我们来分析一下org.springframework.boot.actuate.security,org.springframework.boot.actuate.audit中的代码,这 ...
- spring boot 源码解析29-LogbackLoggingSystem
前言 现在我们来分析一下LogbackLoggingSystem,spring boot 中默认生效的,该类也是继承自Slf4JLoggingSystem. 解析 LogbackLoggingSyst ...
- spring boot 源码解析8-SpringApplication#run第8步
前言 这篇文章我们分析SpringApplication#run第8步.执行prepareContext方法.该方法的内容比较多.我们慢慢来. 分析 SpringApplication#run 第8步 ...
- ROS Navigation之amcl源码解析(完全详解)
转载于:https://haoqchen.site/2018/05/06/amcl-code/ 0. 写在最前面 本文持续更新地址:https://haoqchen.site/2018/05/06/a ...
- log4j 源码解析_Log4j配置详解
log4j.rootLogger=INFO,consoleAppender,logfile,errorlogfile log4j.addivity.org.apache=true #文件输出:Roll ...
- Redis源码解析:数据结构详解-skiplist
跳表是个什么数据结构? 本文的很多内容参考自如下文章<Redis 为什么用跳表而不用平衡树?>,为了加深理解,所以用自己的话复述一遍. 如图所示,redis中的zset在元素少的时候用zi ...
最新文章
- SHELL脚本取系统当前年月日问题 (去0)
- MySQL查询select语句详解
- Java 程序员最爱 Kotlin?
- Swift之动画总结
- C语言程序设计:图书管理系统(超详细有登录系统,附代码和实验报告)
- paddlex,2.1.0识别预测代码(包含视频的)
- Springboot配置多个数据源
- 根据excel的链接下载到电脑上
- JAVA版opencv透明,opencv 替换纯色背景为透明背景
- MPLAB 安装 mmc 报错需要安装web相关插件
- 程序员的8个级别,你属于哪个级别?
- 白硕:区块链技术与数据隐私(附视频)
- 全新角度了解百度地图
- 中子物理思考题-中子探测
- 利用《死亡打字员》提高程序员的命根子技能——打字速度(附游戏下载)
- AIX系统月维护查什么(一)
- 先锋机器人学习笔记_1-4 Pioneer SDK与VS2010编程配置
- 音视频开发系列(19)玩转 WebRTC 安全通信:一文读懂 DTLS 协议
- 我的世界观(爱因斯坦)
- html前台截取/以后的字段,javascript如何截取字符串后几位?
热门文章
- 账号和权限管理——管理用户账号和组账号(一)
- Java 导出Excel利用模版导出
- QT 文本html显示格式的问题,如在QTextBrowser.setText用tr(),其中为html格式
- int不是默认为0吗?为什么会提示要初始化?
- 漫步华尔街——股市历久弥新的成功投资策略读书笔记
- 百度云加速CDN配置
- 树形DP入门(二叉苹果树+没有上司的舞会)
- 易协软件:workflow与BPM区别
- 【pycharm激活方式】pycharm2022.1.1最新专业版安装和激活
- Git将一个分支完全覆盖另一个分支的操作方法