前言
本文我们来介绍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. 获得ManagedResource ManagedResource mr = this.attributeSource.getManagedResource(managedClass); // Check that an object name has been specified. if (mr != null && StringUtils.hasText(mr.getObjectName())) { // 2. 如果存在ManagedResource,并且ObjectName有值,则直接实例化ObjectName return ObjectNameManager.getInstance(mr.getObjectName()); } else { try { // 3. 否则,根据beanKey生成ObjectName return 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通过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.increment String 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. 获得ObjectName ObjectName objectName = this.namingStrategy.getObjectName(managedBean, beanKey); // 2. 获得ObjectName对应的domain String domain = objectName.getDomain(); // 3. 获得ObjectName对应的key properties Hashtable<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个设置为type table.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 --> test if (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 --> increment if (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标签页如下:现在,测试吧,是不是很简单.