spring boot 源码解析43-JmxMetricWriter详解

前言

本文我们来介绍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中的描述,如图:

      《spring boot 源码解析43-JmxMetricWriter详解》

      objectName = “bean:name=sumer” 中的bean 指明了其scope,name=test 指定了在jmx中的名字,如图:

      《spring boot 源码解析43-JmxMetricWriter详解》

    • @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. 获得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);
              }
          }
      }
      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 boot 源码解析43-JmxMetricWriter详解》

参考链接:

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.increment
                String 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. 获得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);
              }
              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:

      《spring boot 源码解析43-JmxMetricWriter详解》
      其Operations标签页如下:

      《spring boot 源码解析43-JmxMetricWriter详解》

      现在,测试吧,是不是很简单.

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/qq_26000415/article/details/79201066
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞