springboot情操陶冶-jmx解析
承接前文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头上的注解
@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));
}
}
避免代码过长带来的视觉疲劳,笔者此处对关键方法作下总结
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即可(默认)
结束语
读者在阅读本博文的时候,建议首先按照笔者上述给出的文献链接查阅jmx相关知识点,再结合此文便会对springboot整合jmx框架有一定的了解
springboot情操陶冶-jmx解析的更多相关文章
- springboot情操陶冶-@SpringBootApplication注解解析
承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上对@SpringBootApplication注解作下简单的分析 @SpringBootApplicat ...
- springboot情操陶冶-@ConfigurationProperties注解解析
承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上阐述@ConfigurationProperties注解的使用 @ConfigurationProper ...
- springboot情操陶冶-@Conditional和@AutoConfigureAfter注解解析
承接前文springboot情操陶冶-@Configuration注解解析,本文将在前文的基础上阐述@AutoConfigureAfter和@Conditional注解的作用与解析 1.@Condit ...
- springboot情操陶冶-@Configuration注解解析
承接前文springboot情操陶冶-SpringApplication(二),本文将在前文的基础上分析下@Configuration注解是如何一步一步被解析的 @Configuration 如果要了 ...
- springboot情操陶冶-web配置(七)
参数校验通常是OpenApi必做的操作,其会对不合法的输入做统一的校验以防止恶意的请求.本文则对参数校验这方面作下简单的分析 spring.factories 读者应该对此文件加以深刻的印象,很多sp ...
- springboot情操陶冶-web配置(四)
承接前文springboot情操陶冶-web配置(三),本文将在DispatcherServlet应用的基础上谈下websocket的使用 websocket websocket的简单了解可见维基百科 ...
- springboot情操陶冶-web配置(二)
承接前文springboot情操陶冶-web配置(一),在分析mvc的配置之前先了解下其默认的错误界面是如何显示的 404界面 springboot有个比较有趣的配置server.error.whit ...
- springboot情操陶冶-web配置(三)
承接前文springboot情操陶冶-web配置(二),本文将在前文的基础上分析下mvc的相关应用 MVC简单例子 直接编写一个Controller层的代码,返回格式为json package com ...
- springboot情操陶冶-web配置(一)
承接前文springboot情操陶冶-@SpringBootApplication注解解析,在前文讲解的基础上依次看下web方面的相关配置 环境包依赖 在pom.xml文件中引入web依赖,炒鸡简单, ...
随机推荐
- windows下输入git用户名和密码错误,重新输入用户名和密码
git clone https://YOUR_USERNAME@gitee.com.xxx.git将YOUR_USERNAME替换为该代码线的用户名,会弹出提示重新输入密码 每次windows提示输入 ...
- 使用mongodb的一些笔记
show dbs # 从结果中发现有cmb_demo_23_hackeruse cmb_demo_23_hacker db.all_in_one.find({"_id":15480 ...
- 检查对象是否为NULL或者为Empty
不管是在Winform开发,还是在asp.net 开发中当从一个数据源中获取数据时你总是不知道这个数据的状态,这个时候总要对她进行一次判断,不过每次进行一次判断总是要写怎么一堆代码,时间长了,总感觉不 ...
- H5 _拖放使用
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- JdbcTemplate实体映射
JdbcTemplate实体映射 如果你需要使用JdbcTemplate将查询的数据映射成Java POJO,那么这篇文章适合你. 一个例子入门 下面是一个将表中一行记录映射成Map的例子,也是Jdb ...
- Java_Object
说一下java中的Object类. 1.Object: Object是java类库中的一个特殊类,也是所有类的父类. Object类定义了一些有用的方法,由于是根类,这些方法在其他类中都存在,一般是进 ...
- git如何避免push/pull时输入密码
今天在搭建git服务器的时候,一切顺利,但是就是在git push的时候老是要输入密码,太烦了,然后百度搜索了一下,总结了主要有如下三种方法: 方法1 git config --global cred ...
- 如何在mysql客户端即mysql提示符下执行操作系统命令
环境描述: mysql版本:5.5.57-log 操作系统版本:Red Hat Enterprise Linux Server release 6.6 (Santiago) 需求描述: 在mysql的 ...
- cadence网络表解读及导入
绘制完成原理图,并且通过了DRC检验之后,需要创建和导入网络表,下面网络表内容做简单总结.
- 为不具有change事件的html标签设置监听事件
change事件会在文本内容或选项被更改时触发. 该事件仅适用于<input type="text">和<textarea>以及<select> ...