承接前文springboot情操陶冶-@Configuration注解解析,近期笔者接触的项目中有使用到了jmx的协议框架,遂在前文的基础上讲解下springboot中是如何整合jmx的

知识储备

JMX:Java Management Extension(Java管理应用扩展),这种机制可以方便的管理、监控正在运行的Java程序。常用于监控管理线程、内存、日志Level、服务重启、系统环境等等。

更多的知识点参考此篇文献:https://blog.csdn.net/u013256816/article/details/52800742。笔者此处引用其中的框架图方便理解

JmxAutoConfiguration

springboot通过在META-INF\spring.factories文件指定EnableAutoConfiguration属性值为JmxAutoConfiguration,便基本搭建了jmx的框架模子。听起来挺神奇的,笔者这就分析源码来一窥究竟

注解

首先看下JmxAutoConfiguration头上的注解

  1. @Configuration
  2. @ConditionalOnClass({ MBeanExporter.class })
  3. @ConditionalOnProperty(prefix = "spring.jmx", name = "enabled", havingValue = "true", matchIfMissing = true)
  4. public class JmxAutoConfiguration implements EnvironmentAware, BeanFactoryAware {

由上可知,要想使jmx环境生效,前提为

  • classpath环境得存在org.springframework.jmx.export.MBeanExporter

  • 环境变量spring.jmx.enabled设置为true,默认为true

一般引入springboot上述条件均是满足的,只是用户可通过spring.jmx.enabled属性来开关启jmx环境

@Bean方法

其下有三个方法,分别被@Bean@Conditional注解所修饰。笔者依次来进行解读


JmxAutoConfiguration#objectNamingStrategy()-获取ObjectName的生成策略

  1. @Bean
  2. @ConditionalOnMissingBean(value = ObjectNamingStrategy.class, search = SearchStrategy.CURRENT)
  3. public ParentAwareNamingStrategy objectNamingStrategy() {
  4. // create namingStrategy
  5. ParentAwareNamingStrategy namingStrategy = new ParentAwareNamingStrategy(
  6. new AnnotationJmxAttributeSource());
  7. // have a try to read environment property 'spring.jmx.default-domain'
  8. String defaultDomain = this.environment.getProperty("spring.jmx.default-domain");
  9. if (StringUtils.hasLength(defaultDomain)) {
  10. namingStrategy.setDefaultDomain(defaultDomain);
  11. }
  12. return namingStrategy;
  13. }

上述代码也很简单,其中环境变量spring.jmx.default-domain代表jmx默认的域挂载。

  • 如果@ManagedResource没有指定objectName属性或者beanName不符合jmx语法,则默认选取当前类的包名作为objectName

JmxAutoConfiguration#mbeanServer()-创建MBeanServer

  1. @Bean
  2. @ConditionalOnMissingBean
  3. public MBeanServer mbeanServer() {
  4. // 1.first to search classpath exsit 'weblogic.management.Helper'/'com.ibm.websphere.management.AdminServiceFactory' class if or not
  5. SpecificPlatform platform = SpecificPlatform.get();
  6. if (platform != null) {
  7. return platform.getMBeanServer();
  8. }
  9. // 2.via MBeanServerFactoryBean to create MBeanServer
  10. MBeanServerFactoryBean factory = new MBeanServerFactoryBean();
  11. factory.setLocateExistingServerIfPossible(true);
  12. factory.afterPropertiesSet();
  13. return factory.getObject();
  14. }

笔者此处只关注MBeanServerFactoryBean是如何创建mbeanserver的,直接去看下其实现的afterPropertiesSet()方法

  1. @Override
  2. public void afterPropertiesSet() throws MBeanServerNotFoundException {
  3. // 1.尝试去找寻已存在的mbeanserver
  4. if (this.locateExistingServerIfPossible || this.agentId != null) {
  5. try {
  6. this.server = locateMBeanServer(this.agentId);
  7. }
  8. catch (MBeanServerNotFoundException ex) {
  9. if (this.agentId != null) {
  10. throw ex;
  11. }
  12. logger.info("No existing MBeanServer found - creating new one");
  13. }
  14. }
  15. // 2.如果上述不存在mbeanserver,则调用jmx api生成mbeanserver
  16. if (this.server == null) {
  17. this.server = createMBeanServer(this.defaultDomain, this.registerWithFactory);
  18. this.newlyRegistered = this.registerWithFactory;
  19. }
  20. }

主要调用jmx api的MBeanServerFactory.createMBeanServer()方法创建mbeanserver,具体的创建过程笔者就不深究了,感兴趣的读者可自行分析


JmxAutoConfiguration#mbeanExporter()-创建mbeanExporter

源码如下

  1. @Bean
  2. @Primary
  3. @ConditionalOnMissingBean(value = MBeanExporter.class, search = SearchStrategy.CURRENT)
  4. public AnnotationMBeanExporter mbeanExporter(ObjectNamingStrategy namingStrategy) {
  5. // 1.创建注解类型的AnnotationMBeanExporter,表明采取注解方式加载mbean
  6. AnnotationMBeanExporter exporter = new AnnotationMBeanExporter();
  7. exporter.setRegistrationPolicy(RegistrationPolicy.FAIL_ON_EXISTING);
  8. // 2.set above namingStrategy
  9. exporter.setNamingStrategy(namingStrategy);
  10. // 3.set mbeanserver via spring applicationContext
  11. String serverBean = this.environment.getProperty("spring.jmx.server",
  12. "mbeanServer");
  13. if (StringUtils.hasLength(serverBean)) {
  14. exporter.setServer(this.beanFactory.getBean(serverBean, MBeanServer.class));
  15. }
  16. return exporter;
  17. }

创建AnnotationMBeanExporter类来读取注解方式的mbean,并优先从spring上下文读取mbeanserver。

  • 环境变量spring.jmx.server如果没有指定的话则默认读取beanName为'mbeanServer'的MBeanServer对象,这与JmxAutoConfiguration#mbeanServer()方法注册的bean不谋而合

通过上述的分析可得,笔者发现最终暴露给外界调用jmx协议是通过AnnotationMBeanExporter来完成的,其里面也蕴含了解析mbean相关注解的玄机

AnnotationMBeanExporter

其实现的常用接口有InitializingBean/SmartInitializingSingleton/DisposableBean以及MBeanExportOperations

构造函数

  1. public AnnotationMBeanExporter() {
  2. setNamingStrategy(this.metadataNamingStrategy);
  3. setAssembler(this.metadataAssembler);
  4. setAutodetectMode(AUTODETECT_ALL);
  5. }

主要是设置基础的属性

afterPropertiesSet()

InitializingBean接口实现类如下

  1. @Override
  2. public void afterPropertiesSet() {
  3. // have a try to find exsiting mbeanserver
  4. if (this.server == null) {
  5. this.server = JmxUtils.locateMBeanServer();
  6. }
  7. }

afterSingletonsInstantiated()

SmartInitializingSingleton接口实现类如下

  1. @Override
  2. public void afterSingletonsInstantiated() {
  3. try {
  4. logger.info("Registering beans for JMX exposure on startup");
  5. registerBeans();
  6. registerNotificationListeners();
  7. }
  8. catch (RuntimeException ex) {
  9. // Unregister beans already registered by this exporter.
  10. unregisterNotificationListeners();
  11. unregisterBeans();
  12. throw ex;
  13. }
  14. }

此处的registerBeans()方法便是mbeanserver去注册mbean的过程,可以继续跟踪下

  1. protected void registerBeans() {
  2. // The beans property may be null, for example if we are relying solely on autodetection.
  3. if (this.beans == null) {
  4. this.beans = new HashMap<>();
  5. // Use AUTODETECT_ALL as default in no beans specified explicitly.
  6. if (this.autodetectMode == null) {
  7. this.autodetectMode = AUTODETECT_ALL;
  8. }
  9. }
  10. // Perform autodetection, if desired.
  11. int mode = (this.autodetectMode != null ? this.autodetectMode : AUTODETECT_NONE);
  12. if (mode != AUTODETECT_NONE) {
  13. if (this.beanFactory == null) {
  14. throw new MBeanExportException("Cannot autodetect MBeans if not running in a BeanFactory");
  15. }
  16. if (mode == AUTODETECT_MBEAN || mode == AUTODETECT_ALL) {
  17. // Autodetect any beans that are already MBeans.
  18. logger.debug("Autodetecting user-defined JMX MBeans");
  19. autodetect(this.beans, (beanClass, beanName) -> isMBean(beanClass));
  20. }
  21. // Allow the assembler a chance to vote for bean inclusion.
  22. if ((mode == AUTODETECT_ASSEMBLER || mode == AUTODETECT_ALL) &&
  23. this.assembler instanceof AutodetectCapableMBeanInfoAssembler) {
  24. autodetect(this.beans, ((AutodetectCapableMBeanInfoAssembler) this.assembler)::includeBean);
  25. }
  26. }
  27. // mbeanserver register mbeans
  28. if (!this.beans.isEmpty()) {
  29. this.beans.forEach((beanName, instance) -> registerBeanNameOrInstance(instance, beanName));
  30. }
  31. }

避免代码过长带来的视觉疲劳,笔者此处对关键方法作下总结

  1. autodetect()方法的作用是遍历bean工厂上的所有beanDefinition,找寻符合条件的beans作为后续的mbeans注册。找寻条件归结如下

    • 携带@MBean注解的类
    • DynamicBean接口实现类
    • *MBean接口的实现类
    • 携带@ManagedResource注解的类
  2. registerBeanNameOrInstance()方法则会对符合条件的beans进行mbean的注册操作,操作步骤如下

    1). 根据类上的@ManagedResource注解的属性objectName生成ObjectName对象

    2). 如果符合条件的mbean是携带@ManagedResource注解的,则生成ModelBean对象并读取@ManagedOperation@ManagedAttribute等jmx注解信息

    3). 最后注册上述的mbean到mbeanserver上

通过上述的操作便可以将搜索到的mbean注册至mbeanserver上了,只要用户使用@ManagedOperation@ManagedAttribute@ManagedResource注解搭配即可

附例


pom内容

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <groupId>com.example</groupId>
  6. <artifactId>demo</artifactId>
  7. <version>0.0.1-SNAPSHOT</version>
  8. <packaging>jar</packaging>
  9. <name>demo-springboot</name>
  10. <description>Demo project for Spring Boot</description>
  11. <parent>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-parent</artifactId>
  14. <version>2.0.3.RELEASE</version>
  15. <relativePath/> <!-- lookup parent from repository -->
  16. </parent>
  17. <properties>
  18. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19. <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
  20. <java.version>1.8</java.version>
  21. </properties>
  22. <dependencies>
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter</artifactId>
  26. </dependency>
  27. <dependency>
  28. <groupId>org.springframework.boot</groupId>
  29. <artifactId>spring-boot-starter-test</artifactId>
  30. <scope>test</scope>
  31. </dependency>
  32. <dependency>
  33. <groupId>com.google.code.gson</groupId>
  34. <artifactId>gson</artifactId>
  35. <version>2.8.0</version>
  36. </dependency>
  37. </dependencies>
  38. <build>
  39. <plugins>
  40. <plugin>
  41. <groupId>org.springframework.boot</groupId>
  42. <artifactId>spring-boot-maven-plugin</artifactId>
  43. </plugin>
  44. </plugins>
  45. </build>
  46. </project>

mbeans创建

  1. package com.example.demo.jmx;
  2. import com.google.gson.Gson;
  3. import org.springframework.context.annotation.Configuration;
  4. import org.springframework.jmx.export.annotation.ManagedAttribute;
  5. import org.springframework.jmx.export.annotation.ManagedOperation;
  6. import org.springframework.jmx.export.annotation.ManagedResource;
  7. import java.util.HashMap;
  8. import java.util.Map;
  9. /**
  10. * system common monitor
  11. *
  12. * @author nanco
  13. * @create 2018/8/8
  14. **/
  15. @Configuration
  16. @ManagedResource(objectName = "monitor:name=SystemCommonMonitor")
  17. public class SystemCommonMonitorMBean {
  18. private String systemName;
  19. private Gson gsonTool = new Gson();
  20. @ManagedAttribute
  21. public String getSystemName() {
  22. return this.systemName;
  23. }
  24. @ManagedAttribute(description = "system_name", defaultValue = "demo")
  25. public void setSystemName(String name) {
  26. this.systemName = name;
  27. }
  28. @ManagedOperation(description = "systemInfo")
  29. public String systemInfo() {
  30. Map<String, String> system = new HashMap(8);
  31. system.put("cpuCoreSize", "4");
  32. system.put("memorySize", "8G");
  33. system.put("cpuRatio", "20%");
  34. system.put("memoryRatio", "2%");
  35. system.put("totalDisk", "200G");
  36. system.put("usedDisk", "120G");
  37. system.put("freeDisk", "80G");
  38. return gsonTool.toJson(system);
  39. }
  40. }

jmx serviceUrl暴露

  1. package com.example.demo.jmx;
  2. import org.springframework.beans.factory.annotation.Value;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.context.annotation.DependsOn;
  6. import org.springframework.jmx.support.ConnectorServerFactoryBean;
  7. import org.springframework.remoting.rmi.RmiRegistryFactoryBean;
  8. /**
  9. * @author nanco
  10. * @create 2018/8/8
  11. **/
  12. @Configuration
  13. public class JmxAutoConfiguration {
  14. @Value("${jmx.rmi.host:localhost}")
  15. private String rmiHost;
  16. @Value("${jmx.rmi.port:7099}")
  17. private int rmiPort;
  18. @Value("${jmx.service.domain:jmxrmi}")
  19. private String jmxDomain;
  20. // 指定特定端口可以开放命名服务
  21. @Bean
  22. public RmiRegistryFactoryBean rmiRegistry() {
  23. RmiRegistryFactoryBean factoryBean = new RmiRegistryFactoryBean();
  24. factoryBean.setPort(rmiPort);
  25. factoryBean.setAlwaysCreate(true);
  26. return factoryBean;
  27. }
  28. @DependsOn("rmiRegistry")
  29. @Bean
  30. public ConnectorServerFactoryBean jmxConnector() {
  31. ConnectorServerFactoryBean serverFactoryBean = new ConnectorServerFactoryBean();
  32. serverFactoryBean.setServiceUrl(String.format("service:jmx:rmi://%s:%s/jndi/rmi://%s:%s/%s", rmiHost, rmiPort, rmiHost, rmiPort, jmxDomain));
  33. return serverFactoryBean;
  34. }
  35. }

jconsole访问,直接远程连接至service:jmx:rmi://localhost:7099/jndi/rmi://localhost:7099/jmxrmi即可(默认)









结束语

读者在阅读本博文的时候,建议首先按照笔者上述给出的文献链接查阅jmx相关知识点,再结合此文便会对springboot整合jmx框架有一定的了解

springboot情操陶冶-jmx解析的更多相关文章

  1. springboot情操陶冶-@SpringBootApplication注解解析

    承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上对@SpringBootApplication注解作下简单的分析 @SpringBootApplicat ...

  2. springboot情操陶冶-@ConfigurationProperties注解解析

    承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上阐述@ConfigurationProperties注解的使用 @ConfigurationProper ...

  3. springboot情操陶冶-@Conditional和@AutoConfigureAfter注解解析

    承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上阐述@AutoConfigureAfter和@Conditional注解的作用与解析 1.@Condit ...

  4. springboot情操陶冶-@Configuration注解解析

    承接前文springboot情操陶冶-SpringApplication(二),本文将在前文的基础上分析下@Configuration注解是如何一步一步被解析的 @Configuration 如果要了 ...

  5. springboot情操陶冶-web配置(七)

    参数校验通常是OpenApi必做的操作,其会对不合法的输入做统一的校验以防止恶意的请求.本文则对参数校验这方面作下简单的分析 spring.factories 读者应该对此文件加以深刻的印象,很多sp ...

  6. springboot情操陶冶-web配置(四)

    承接前文springboot情操陶冶-web配置(三),本文将在DispatcherServlet应用的基础上谈下websocket的使用 websocket websocket的简单了解可见维基百科 ...

  7. springboot情操陶冶-web配置(二)

    承接前文springboot情操陶冶-web配置(一),在分析mvc的配置之前先了解下其默认的错误界面是如何显示的 404界面 springboot有个比较有趣的配置server.error.whit ...

  8. springboot情操陶冶-web配置(三)

    承接前文springboot情操陶冶-web配置(二),本文将在前文的基础上分析下mvc的相关应用 MVC简单例子 直接编写一个Controller层的代码,返回格式为json package com ...

  9. springboot情操陶冶-web配置(一)

    承接前文springboot情操陶冶-@SpringBootApplication注解解析,在前文讲解的基础上依次看下web方面的相关配置 环境包依赖 在pom.xml文件中引入web依赖,炒鸡简单, ...

随机推荐

  1. Python(day1)

    一.Python的属于解释型语言. 编译型:一次性,将全部的程序编译成二进制文件,然后再运行. 优点:运行速度快. 缺点:开发效率低,不能跨平台. 解释型:当你的程序运行时,一行一行的解释,并运行. ...

  2. Android Architecture Components--项目实战

    转载请注明出处,谢谢! 上个月Google Android Architecture Components 1.0稳定版发布,抽工作间隙写了个demo,仅供参考 Github地址:https://gi ...

  3. Python3小知识点

    1. 是转义字符,\可以输出.在一行的末尾单独的一个\表示这行没有结束,下一行接着写 2.可以用"'或"""把一大段话引起来(可以换行)然后赋值,输出. 3.要 ...

  4. [error] eclipse编写spring等xml配置文件时只有部分提示,tx无提示

    eclipse编写spring等xml配置文件时只有<bean>.<context>等有提示,其他标签都没有提示 这时就需要做以下两步操作(下面以事务管理标签为例) 1,添加命 ...

  5. 用 Java 解密 C# 加密的数据(DES)(转)

    今天遇到java解密url的问题.我们的系统要获取外部传过来的URL,URL是采用 DES 算法对消息进行加密,再用 BASE64 编码.不过对方系统是用 C# 写的. 在网上搜了几篇文章终于找到一篇 ...

  6. 构建一个 预装 pm2 的 node 项目 docker 底包

    Dockerfile: 创建 dockerfile 文件, 命名为 dockerfile-yourProject-node.8.12.0-pm2 # MAGE: yourGroup/yourProje ...

  7. NeuChar 平台使用及开发教程(五):使用 NeuChar 的关键字回复服务

    在上一篇<NeuChar 平台使用及开发教程(四):使用 NeuChar 的素材服务>中,我们已经完成了素材的添加,下面,让我们来设置一个关键字回复,并同步到应设置好Neural Endi ...

  8. emWin万年历,含uCOS-III和FreeRTOS两个版本

    第8期:万年历配套例子:V6-914_STemWin提高篇实验_万年历(uCOS-III)V6-915_STemWin提高篇实验_万年历(FreeRTOS) 例程下载地址: http://forum. ...

  9. NIO类库

    NIO概述 从JDK1.4开始,引入了新的I/O类库,它们位于java.nio包中,其目的在于提高I/O的操作效率.nio是new io的缩写. 参考文章:NIO BIO AIO区别 java.nio ...

  10. [Swift]LeetCode309. 最佳买卖股票时机含冷冻期 | Best Time to Buy and Sell Stock with Cooldown

    Say you have an array for which the ith element is the price of a given stock on day i. Design an al ...