前言

本文我们来介绍JmxMetricWriter, JmxMetricWriter是依赖于spring jmx 来实现的. 因此本文的开头先介绍一下spring boot 与jmx的集成,然后分析JmxMetricWriter源码.

spring boot jmx

  1. 首先确保在pom文件中加入了如下依赖:

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

    只要加入如下依赖,就会自动装配如下bean:

    • AnnotationMBeanExporter
    • objectNamingStrategy
    • MBeanServer
    • EndpointMBeanExporter
    • 一系列的jmxEndpoint…

    以上的配置类在JmxAutoConfiguration,EndpointMBeanExportAutoConfiguration中进行了声明

  2. 在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);
    }

    1. 调用父类(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);}}
      }
      1. 获得ManagedResource
      2. 如果存在ManagedResource,并且ObjectName有值,则直接实例化ObjectName
      3. 否则,根据beanKey生成ObjectName
    2. 将name中的所有KeyProperty 放入到properties中

    3. 如果ensureUniqueRuntimeObjectNames等于true,则放入key–>identity,value–>该bean的hash值
    4. 如果在父Context中存在id为beanKey的bean,则存入key–>context,value–>当前上下文的hash值
    5. 生成ObjectName

      对于当前,我们在Sumer上声明了@ManagedResource,并且配置了objectName为bean:name=sumer,因此最终会在1.1.2 步返回,最后jmx中的规范生成ObjectName

  3. 打开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接口.

  1. 其声明了@ManagedResource注解,如下:

    @ManagedResource(description = "MetricWriter for pushing metrics to JMX MBeans.")
  2. 字段,构造器如下:

    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. 其有3个被@ManagedOperation注解的方法,如下:

    1. 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());
      }
      1. 调用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;
        }
        1. 从缓存中查找,如果有则直接返回,否则,进行第2步
        2. 如果缓存中不存在,则

          1. 实例化MetricValue,放入缓存中
          2. 生成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);
            }
            1. 进行拼接,如传入的是couter.test.increment,则返回org.springframework.metrics:type=MetricValue,name=couter.test.increment
            2. 调用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);
              }
              1. 获得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

              2. 获得ObjectName对应的domain
              3. 获得ObjectName对应的key properties
              4. 如果objectName中存在name的属性

                1. 删除name
                2. 将name通过点进行分割
                3. 将name通过点进行分割
                4. 将第1个设置为type
                5. 如果name存在1个点,则进行判断: 如果name存在2个点以上,则存入key–>name,value –>parts[1],否则,存入value–>name,value –>parts[1].如传入的name是couter.test.increment,则此时存入的是key–>name,value –> test
                6. 如果name存在2个点,则进行存入,key–> value,value–>name 对应的第2个点以后的值,如传入的name是couter.test.increment,则此时存入的是key–>value,value –> increment
              5. 返回ObjectName

          3. 进行注册
      2. 调用MetricValue#increment方法

      这里有必要说明一下 MetricValue:

      1. 字段,构造器如下:

        private double value;private long lastUpdated = 0;
      2. 声明了如下方法:

        1. setValue,代码如下:

          public void setValue(double value) {if (this.value != value) {this.lastUpdated = System.currentTimeMillis();}this.value = value;
          }
        2. increment,代码如下:

          public void increment(long value) {this.lastUpdated = System.currentTimeMillis();this.value += value;
          }
        3. getValue,代码如下:

          @ManagedAttribute
          public double getValue() {return this.value;
          }
        4. getLastUpdated,代码如下:

          @ManagedAttribute
          public Date getLastUpdated() {return new Date(this.lastUpdated);
          }
    2. 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());
      }
      1. 通过 getValue方法获得对应的 MetricValue
      2. 调用MetricValue对应的setValue方法.
    3. 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);}}}
      1. 从缓存中删除
      2. 从MBeanExporter中删除
  4. 配置:

    注意,该类不支持自动装配.

    需要在配置类做如下配置即可:

    @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详解相关推荐

  1. spring boot 源码解析23-actuate使用及EndPoint解析

    前言 spring boot 中有个很诱人的组件–actuator,可以对spring boot应用做监控,只需在pom文件中加入如下配置即可: <dependency><group ...

  2. spring boot 源码解析15-spring mvc零配置

    前言 spring boot 是基于spring 4 的基础上的一个框架,spring 4 有一个新特效–>基于java config 实现零配置.而在企业的实际工作中,spring 都是和sp ...

  3. spring boot 源码解析52-actuate中MVCEndPoint解析

    前言 之前的几篇文章分析了spring boot 中有关endpoint的实现,细心的朋友可以发现,在org.springframework.boot.actuate.endpoint.mvc 包下也 ...

  4. spring boot 源码解析31-AuthenticationAuditListener,AuthorizationAuditListener

    前言 这篇文章我们来分析一下org.springframework.boot.actuate.security,org.springframework.boot.actuate.audit中的代码,这 ...

  5. spring boot 源码解析29-LogbackLoggingSystem

    前言 现在我们来分析一下LogbackLoggingSystem,spring boot 中默认生效的,该类也是继承自Slf4JLoggingSystem. 解析 LogbackLoggingSyst ...

  6. spring boot 源码解析8-SpringApplication#run第8步

    前言 这篇文章我们分析SpringApplication#run第8步.执行prepareContext方法.该方法的内容比较多.我们慢慢来. 分析 SpringApplication#run 第8步 ...

  7. ROS Navigation之amcl源码解析(完全详解)

    转载于:https://haoqchen.site/2018/05/06/amcl-code/ 0. 写在最前面 本文持续更新地址:https://haoqchen.site/2018/05/06/a ...

  8. log4j 源码解析_Log4j配置详解

    log4j.rootLogger=INFO,consoleAppender,logfile,errorlogfile log4j.addivity.org.apache=true #文件输出:Roll ...

  9. Redis源码解析:数据结构详解-skiplist

    跳表是个什么数据结构? 本文的很多内容参考自如下文章<Redis 为什么用跳表而不用平衡树?>,为了加深理解,所以用自己的话复述一遍. 如图所示,redis中的zset在元素少的时候用zi ...

最新文章

  1. SHELL脚本取系统当前年月日问题 (去0)
  2. MySQL查询select语句详解
  3. Java 程序员最爱 Kotlin?
  4. Swift之动画总结
  5. C语言程序设计:图书管理系统(超详细有登录系统,附代码和实验报告)
  6. paddlex,2.1.0识别预测代码(包含视频的)
  7. Springboot配置多个数据源
  8. 根据excel的链接下载到电脑上
  9. JAVA版opencv透明,opencv 替换纯色背景为透明背景
  10. MPLAB 安装 mmc 报错需要安装web相关插件
  11. 程序员的8个级别,你属于哪个级别?
  12. 白硕:区块链技术与数据隐私(附视频)
  13. 全新角度了解百度地图
  14. 中子物理思考题-中子探测
  15. 利用《死亡打字员》提高程序员的命根子技能——打字速度(附游戏下载)
  16. AIX系统月维护查什么(一)
  17. 先锋机器人学习笔记_1-4 Pioneer SDK与VS2010编程配置
  18. 音视频开发系列(19)玩转 WebRTC 安全通信:一文读懂 DTLS 协议
  19. 我的世界观(爱因斯坦)
  20. html前台截取/以后的字段,javascript如何截取字符串后几位?

热门文章

  1. 账号和权限管理——管理用户账号和组账号(一)
  2. Java 导出Excel利用模版导出
  3. QT 文本html显示格式的问题,如在QTextBrowser.setText用tr(),其中为html格式
  4. int不是默认为0吗?为什么会提示要初始化?
  5. 漫步华尔街——股市历久弥新的成功投资策略读书笔记
  6. 百度云加速CDN配置
  7. 树形DP入门(二叉苹果树+没有上司的舞会)
  8. 易协软件:workflow与BPM区别
  9. 【pycharm激活方式】pycharm2022.1.1最新专业版安装和激活
  10. Git将一个分支完全覆盖另一个分支的操作方法