• springboot情操陶冶-jmx解析


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

    知识储备

    JMX:Java Management Extension(Java管理应用扩展),这种机制可以方便的管理、监控正在运行的Java程序。常用于监控管理线程、内存、日志Level、服务重启、系统环境等等。
    更多的知识点参考此篇文献:https://blog.csdn.net/u013256816/article/details/52800742。笔者此处引用其中的框架图方便理解
    jmx_frame

    JmxAutoConfiguration

    springboot通过在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协议是通过AnnotationMBeanExporter来完成的,其里面也蕴含了解析mbean相关注解的玄机

    AnnotationMBeanExporter

    其实现的常用接口有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));
    		}
    	}
    

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

    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内容

    <?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_1
    jconsole_2
    jconsole_3
    jconsole_4
    jconsole_5

    结束语

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

  • 相关阅读:
    JAVA设计模式之桥接模式
    Pycharm新建模板默认添加作者时间等信息
    Handler机制(2)转载
    内部类学习
    设计模式-1依赖倒置原则示例
    正则表达式
    Service原理及例子
    Serializable接口
    设计模式之静态工厂模式
    Handler机制post方法使用
  • 原文地址:https://www.cnblogs.com/question-sky/p/9437050.html
Copyright © 2020-2023  润新知