springboot情操陶冶-jmx解析

知识储备JMX:Java Management Extension(Java管理应用扩展),这种机制可以方便的管理、监控正在运行的Java程序。常用于监控管理线程、内存、日志Level、服务重启、系统环境等等。
更多的知识点参考此篇文献:。笔者此处引用其中的框架图方便理解
JmxAutoConfigurationspringboot通过在META-INFspring.factories文件指定EnableAutoConfiguration属性值为JmxAutoConfiguration,便基本搭建了jmx的框架模子。听起来挺神奇的,笔者这就分析源码来一窥究竟注解首先看下JmxAutoConfiguration头上的注解@Configuration @ConditionalOnClass({ MBeanExporter.class }) @ConditionalOnProperty(prefix = "spring.jmx", name = "enabled", havingValue = "true", matchIfMissing = true) 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的生成策略 @Bean @ConditionalOnMissingBean(value = ObjectNamingStrategy.class, search = SearchStrategy.CURRENT) public ParentAwareNamingStrategy objectNamingStrategy() { // create namingStrategy ParentAwareNamingStrategy namingStrategy = new ParentAwareNamingStrategy( new AnnotationJmxAttributeSource()); // have a try to read environment property 'spring.jmx.default-domain' String defaultDomain = this.environment.getProperty("spring.jmx.default-domain"); if (StringUtils.hasLength(defaultDomain)) { namingStrategy.setDefaultDomain(defaultDomain); } return namingStrategy; }上述代码也很简单,其中环境变量spring.jmx.default-domain代表jmx默认的域挂载。
  • 如果@ManagedResource没有指定objectName属性或者beanName不符合jmx语法,则默认选取当前类的包名作为objectName
  • JmxAutoConfiguration#mbeanServer()-创建MBeanServer @Bean @ConditionalOnMissingBean public MBeanServer mbeanServer() { // 1.first to search classpath exsit 'weblogic.management.Helper'/'com.ibm.websphere.management.AdminServiceFactory' class if or not SpecificPlatform platform = SpecificPlatform.get(); if (platform != null) { return platform.getMBeanServer(); } // 2.via MBeanServerFactoryBean to create MBeanServer MBeanServerFactoryBean factory = new MBeanServerFactoryBean(); factory.setLocateExistingServerIfPossible(true); factory.afterPropertiesSet(); return factory.getObject(); }MBeanServerFactoryBean是如何创建mbeanserver的,直接去看下其实现的afterPropertiesSet()方法 @Override public void afterPropertiesSet() throws MBeanServerNotFoundException { // 1.尝试去找寻已存在的mbeanserver if (this.locateExistingServerIfPossible || this.agentId != null) { try { this.server = locateMBeanServer(this.agentId); } catch (MBeanServerNotFoundException ex) { if (this.agentId != null) { throw ex; } logger.info("No existing MBeanServer found - creating new one"); } } // 2.如果上述不存在mbeanserver,则调用jmx api生成mbeanserver if (this.server == null) { this.server = createMBeanServer(this.defaultDomain, this.registerWithFactory); this.newlyRegistered = this.registerWithFactory; } }主要调用jmx api的MBeanServerFactory.createMBeanServer()方法创建mbeanserver,具体的创建过程笔者就不深究了,感兴趣的读者可自行分析JmxAutoConfiguration#mbeanExporter()-创建mbeanExporter
    源码如下 @Bean @Primary @ConditionalOnMissingBean(value = MBeanExporter.class, search = SearchStrategy.CURRENT) public AnnotationMBeanExporter mbeanExporter(ObjectNamingStrategy namingStrategy) { // 1.创建注解类型的AnnotationMBeanExporter,表明采取注解方式加载mbean AnnotationMBeanExporter exporter = new AnnotationMBeanExporter(); exporter.setRegistrationPolicy(RegistrationPolicy.FAIL_ON_EXISTING); // 2.set above namingStrategy exporter.setNamingStrategy(namingStrategy); // 3.set mbeanserver via spring applicationContext String serverBean = this.environment.getProperty("spring.jmx.server", "mbeanServer"); if (StringUtils.hasLength(serverBean)) { exporter.setServer(this.beanFactory.getBean(serverBean, MBeanServer.class)); } return exporter; }创建AnnotationMBeanExporter类来读取注解方式的mbean,并优先从spring上下文读取mbeanserver。
  • 环境变量spring.jmx.server如果没有指定的话则默认读取beanName为'mbeanServer'的MBeanServer对象,这与JmxAutoConfiguration#mbeanServer()方法注册的bean不谋而合
  • 通过上述的分析可得,笔者发现最终暴露给外界调用jmx协议是通过AnnotationMBeanExporterAnnotationMBeanExporter其实现的常用接口有InitializingBean/SmartInitializingSingleton/DisposableBean以及MBeanExportOperations构造函数 public AnnotationMBeanExporter() { setNamingStrategy(this.metadataNamingStrategy); setAssembler(this.metadataAssembler); setAutodetectMode(AUTODETECT_ALL); }主要是设置基础的属性afterPropertiesSet()InitializingBean接口实现类如下 @Override public void afterPropertiesSet() { // have a try to find exsiting mbeanserver if (this.server == null) { this.server = JmxUtils.locateMBeanServer(); } }afterSingletonsInstantiated()SmartInitializingSingleton接口实现类如下 @Override public void afterSingletonsInstantiated() { try { logger.info("Registering beans for JMX exposure on startup"); registerBeans(); registerNotificationListeners(); } catch (RuntimeException ex) { // Unregister beans already registered by this exporter. unregisterNotificationListeners(); unregisterBeans(); throw ex; } }此处的registerBeans()方法便是mbeanserver去注册mbean的过程,可以继续跟踪下 protected void registerBeans() { // The beans property may be null, for example if we are relying solely on autodetection. if (this.beans == null) { this.beans = new HashMap<>(); // Use AUTODETECT_ALL as default in no beans specified explicitly. if (this.autodetectMode == null) { this.autodetectMode = AUTODETECT_ALL; } } // Perform autodetection, if desired. int mode = (this.autodetectMode != null ? this.autodetectMode : AUTODETECT_NONE); if (mode != AUTODETECT_NONE) { if (this.beanFactory == null) { throw new MBeanExportException("Cannot autodetect MBeans if not running in a BeanFactory"); } if (mode == AUTODETECT_MBEAN || mode == AUTODETECT_ALL) { // Autodetect any beans that are already MBeans. logger.debug("Autodetecting user-defined JMX MBeans"); autodetect(this.beans, (beanClass, beanName) -> isMBean(beanClass)); } // Allow the assembler a chance to vote for bean inclusion. if ((mode == AUTODETECT_ASSEMBLER || mode == AUTODETECT_ALL) && this.assembler instanceof AutodetectCapableMBeanInfoAssembler) { autodetect(this.beans, ((AutodetectCapableMBeanInfoAssembler) this.assembler)::includeBean); } } // mbeanserver register mbeans if (!this.beans.isEmpty()) { this.beans.forEach((beanName, instance) -> registerBeanNameOrInstance(instance, beanName)); } }避免代码过长带来的视觉疲劳,笔者此处对关键方法作下总结
  • autodetect()方法的作用是遍历bean工厂上的所有beanDefinition,找寻符合条件的beans作为后续的mbeans注册。找寻条件归结如下
    • 携带@MBean注解的类
    • DynamicBean接口实现类
    • *MBean接口的实现类
    • 携带@ManagedResource注解的类
  • registerBeanNameOrInstance()方法则会对符合条件的beans进行mbean的注册操作,操作步骤如下
    1). 根据类上的@ManagedResource注解的属性objectName生成ObjectName对象
    2). 如果符合条件的mbean是携带@ManagedResource注解的,则生成ModelBean对象并读取@ManagedOperation@ManagedAttribute等jmx注解信息
    3). 最后注册上述的mbean到mbeanserver上
  • 通过上述的操作便可以将搜索到的mbean注册至mbeanserver上了,只要用户使用@ManagedOperation@ManagedAttribute@ManagedResource注解搭配即可附例pom内容<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>demo-springboot</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.3.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.8.0</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>mbeans创建package com.example.demo.jmx; import com.google.gson.Gson; import org.springframework.context.annotation.Configuration; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; import java.util.HashMap; import java.util.Map; /** * system common monitor * * @author nanco * @create 2018/8/8 **/ @Configuration @ManagedResource(objectName = "monitor:name=SystemCommonMonitor") public class SystemCommonMonitorMBean { private String systemName; private Gson gsonTool = new Gson(); @ManagedAttribute public String getSystemName() { return this.systemName; } @ManagedAttribute(description = "system_name", defaultValue = "demo") public void setSystemName(String name) { this.systemName = name; } @ManagedOperation(description = "systemInfo") public String systemInfo() { Map<String, String> system = new HashMap(8); system.put("cpuCoreSize", "4"); system.put("memorySize", "8G"); system.put("cpuRatio", "20%"); system.put("memoryRatio", "2%"); system.put("totalDisk", "200G"); system.put("usedDisk", "120G"); system.put("freeDisk", "80G"); return gsonTool.toJson(system); } } jmx serviceUrl暴露package com.example.demo.jmx; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.jmx.support.ConnectorServerFactoryBean; import org.springframework.remoting.rmi.RmiRegistryFactoryBean; /** * @author nanco * @create 2018/8/8 **/ @Configuration public class JmxAutoConfiguration { @Value("${jmx.rmi.host:localhost}") private String rmiHost; @Value("${jmx.rmi.port:7099}") private int rmiPort; @Value("${jmx.service.domain:jmxrmi}") private String jmxDomain; // 指定特定端口可以开放命名服务 @Bean public RmiRegistryFactoryBean rmiRegistry() { RmiRegistryFactoryBean factoryBean = new RmiRegistryFactoryBean(); factoryBean.setPort(rmiPort); factoryBean.setAlwaysCreate(true); return factoryBean; } @DependsOn("rmiRegistry") @Bean public ConnectorServerFactoryBean jmxConnector() { ConnectorServerFactoryBean serverFactoryBean = new ConnectorServerFactoryBean(); serverFactoryBean.setServiceUrl(String.format("service:jmx:rmi://%s:%s/jndi/rmi://%s:%s/%s", rmiHost, rmiPort, rmiHost, rmiPort, jmxDomain)); return serverFactoryBean; } } jconsole访问,直接远程连接至service:jmx:rmi://localhost:7099/jndi/rmi://localhost:7099/jmxrmi即可(默认)




    jconsole_5结束语

    相关内容推荐