標籤:and nap hash 4.0 free spring trade any ppc
承接前文springboot情操陶冶[email protected]註解解析,近期筆者接觸的項目中有使用到了jmx的協議架構,遂在前文的基礎上講解下springboot中是如何整合jmx的
知識儲備
JMX:Java Management Extension(Java管理應用擴充),這種機制可以方便的管理、監控正在啟動並執行Java程式。常用於監控管理線程、記憶體、日誌Level、服務重啟、系統內容等等。
更多的知識點參考此篇文獻: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環境生效,前提為
一般引入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的註冊操作,操作步驟如下
- 根據類上的
@ManagedResource註解的屬性objectName產生ObjectName對象
- 如果合格mbean是攜帶
@ManagedResource註解的,則產生ModelBean對象並讀取@ManagedOperation、@ManagedAttribute等jmx註解
- 最後註冊上述的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> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build></project>
mbean建立
package com.hundsun.quote.server.monitor;...import java.util.HashMap;import java.util.Map;/** * common monitor MBean for system and application * * @author nanco * @create 2018/8/6 **/@Configuration@ManagedResource(value = "monitor:name=SystemCommonMonitor")public class SysCommonMonitorMBean implements InitializingBean { private static AbstractMonitorService commonMonitorService; private Gson gson = new Gson(); @Autowired private ApplicationContext springApplicationContext; @Override public void afterPropertiesSet() throws Exception { Assert.notNull(springApplicationContext, "spring applicationContext is not autowired"); commonMonitorService = springApplicationContext.getAutowireCapableBeanFactory().createBean(CommonMonitorService.class); } @ManagedAttribute public String getSystemCommonInfo() { Map<String, String> systemMap = new HashMap<>(8); systemMap.put("sysCpuRatio", commonMonitorService.getSysCpuRatio()); systemMap.put("sysCurrentTime", commonMonitorService.getSysCurrentTime()); systemMap.put("sysStartupTime", commonMonitorService.getSysStartupTime()); systemMap.put("sysMemoryRatio", commonMonitorService.getSysMemoryRatio()); systemMap.put("sysDiskTotal", commonMonitorService.getSysDiskTotal()); systemMap.put("sysDiskFree", commonMonitorService.getSysDiskFree()); return gson.toJson(systemMap); } @ManagedAttribute public String getAppCommonInfo() { Map<String, String> applicationMap = new HashMap<>(4); applicationMap.put("appCpuRatio", commonMonitorService.getAppCpuRatio()); applicationMap.put("appMemoryRatio", commonMonitorService.getAppMemoryRatio()); applicationMap.put("appStartupTime", commonMonitorService.getAppStartupTime()); return gson.toJson(applicationMap); }}
package com.jing.quote.server.monitor;....import org.springframework.context.annotation.Configuration;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 javax.annotation.Resource;import java.util.List;/** * trade date Mbean * * @author nanco * @create 2018/8/6 **/@Configuration@ManagedResource(value = "monitor:name=TradeDateMonitor")public class TradeDateMonitorMBean { private static final String OK_RESP = "Execute Success!"; private static final String FAIL_RESP = "Execute Fail!"; private Gson gson = new Gson(); @Resource private DsTradeDateService tradeDateService; @Resource private DsBaseService baseService; @ManagedOperation(description = "select_all_trade_date_excl") public String selectAllTradeDateExcl() { List<TradeDateExcl> tradeDateExcls = tradeDateService.selectAllTradeDateExcl(); return gson.toJson(tradeDateExcls); } @ManagedOperation(description = "insert_trade_date_excl") @ManagedOperationParameters(value = { @ManagedOperationParameter(name = "financeMic", description = "financeMic"), @ManagedOperationParameter(name = "exclDate", description = "exclDate"), @ManagedOperationParameter(name = "dayOfWeek", description = "dayOfWeek"), @ManagedOperationParameter(name = "exclDesc", description = "exclDesc") }) public String insertTradeDateExcl(String financeMic, int exclDate, int dayOfWeek, String exclDesc) { TradeDateExcl tradeDateExcl = new TradeDateExcl(); tradeDateExcl.setFinanceMic(financeMic); tradeDateExcl.setExclDesc(exclDesc); tradeDateExcl.setExclDate(exclDate); tradeDateExcl.setDayOfWeek(dayOfWeek); boolean executeFlag = tradeDateService.insertTradeDateExcl(tradeDateExcl); if (executeFlag) { return TradeDateMonitorMBean.OK_RESP; } else { return TradeDateMonitorMBean.FAIL_RESP; } } @ManagedOperation(description = "delete_trade_date_excl") @ManagedOperationParameters(value = { @ManagedOperationParameter(name = "financeMic", description = "financeMic"), @ManagedOperationParameter(name = "exclDate", description = "exclDate") }) public String deleteTradeDateExcl(String financeMic, int exclDate) { TradeDateExclKey exclKey = new TradeDateExclKey(); exclKey.setExclDate(exclDate); exclKey.setFinanceMic(financeMic); boolean executeFlag = tradeDateService.deleteTradeDateExcl(exclKey); if (executeFlag) { return TradeDateMonitorMBean.OK_RESP; } else { return TradeDateMonitorMBean.FAIL_RESP; } }}
jmx serviceUrl暴露
package com.jing.quote.server.config;import org.apache.commons.logging.Log;import org.apache.commons.logging.LogFactory;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;@Configurationpublic class JmxAutoConfiguration { private static final Log LOGGER = LogFactory.getLog(JmxAutoConfiguration.class); @Value("${jmx.rmi.host:localhost}") private String rmiHost; @Value("${jmx.rmi.port:9888}") private Integer rmiPort; @Bean public RmiRegistryFactoryBean rmiRegistry() { // 指定連接埠可以開放建立名字服務 final RmiRegistryFactoryBean rmiRegistryFactoryBean = new RmiRegistryFactoryBean(); rmiRegistryFactoryBean.setPort(rmiPort); rmiRegistryFactoryBean.setAlwaysCreate(true); LOGGER.info("RmiRegistryFactoryBean create success !!"); return rmiRegistryFactoryBean; } @Bean @DependsOn("rmiRegistry") public ConnectorServerFactoryBean connectorServerFactoryBean() throws Exception { final ConnectorServerFactoryBean connectorServerFactoryBean = new ConnectorServerFactoryBean(); connectorServerFactoryBean.setObjectName("connector:name=rmi"); connectorServerFactoryBean.setServiceUrl( String.format("service:jmx:rmi://%s:%s/jndi/rmi://%s:%s/jmxrmi", rmiHost, rmiPort, rmiHost, rmiPort)); LOGGER.info("ConnectorServerFactoryBean create success !!"); return connectorServerFactoryBean; }}
jconsole訪問,直接遠端連線至service:jmx:rmi://localhost:9888/jndi/rmi://localhost:9888/jmxrmi即可(預設)
結束語
讀者在閱讀本博文的時候,建議首先按照筆者上述給出的文獻連結查閱jmx相關知識點,再結合此文便會對springboot整合jmx架構有一定的瞭解
springboot情操陶冶-jmx解析