系列文章目录
Java管理扩展JMX入门学习
Spring JMX编程学习(一)- 手动注册bean到MBeanServer
Spring JMX编程学习(二)- 以Bean的方式注册MbeanServer
Spring JMX编程学习(三)- 自定义JMX客户端
Spring JMX编程学习(四)- MBeans自动探测与注解
Spring JMX编程学习(五)- SpringBoot自动注册
文章目录
前言
在前面的项目当中,我们通过Spring创建了MBeanServer并且将Spring的Bean作为MBean注册到其中,并且通过JConsole客户端查看MBean的属性和操作,其实这些都还只是属于本地操作,使用的是一个JVM(JConsole和运行的Server小程序),本章将学习通过Spring创建自定义的JMX客户端,通过客户端调用服务端的MBean,此时客户端和服务端其实是运行在不同的JVM当中的,属于远程调用
提示:以下是本篇文章正文内容,下面案例可供参考
一、暴露MBeanServer
如果想MBeanServer被远程服务访问到,首先需要通过JMXServerConnector进行暴露,然后在客户端通过MBeanServerConnection进行连接。
在服务端的配置文件spring-jmx-server.xml中添加一个ConnectorServerFactoryBean类型的Bean,这个类型的bean会在服务端创建一个符合JSR-160标准的JMXConnectorServer并把它注册到指定的MBeanServer(所谓的attach),然后开启连接。
<bean id="serverConnector"
class="org.springframework.jmx.support.ConnectorServerFactoryBean" depends-on="registry">
<property name="serviceUrl"
value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>
<!--通过rmi方式-->
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
<property name="port" value="1099"/>
</bean>
这里有两点需要注意,第一个是serviceUrl参数必须配置,因为如果不配置的话,默认值为service:jmx:jmxmp://localhost:9875
,这个是一个jmxmp协议的连接。它会通过jmxmp协议在9875端口上暴露本地的MBeanServer,但是这个协议在JSR-160标准中是标识为可选择的,当前的JDK8并不支持这个协议。如果使用这个协议,会抛出如下的异常
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverConnector' defined in class path resource [spring-jmx-server.xml]: Invocation of init method failed; nested exception is java.net.MalformedURLException: Unsupported protocol: jmxmp
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:828)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
at com.example.spring.jmx.JmxStartServerMain.main(JmxStartServerMain.java:12)
Caused by: java.net.MalformedURLException: Unsupported protocol: jmxmp
at javax.management.remote.JMXConnectorServerFactory.newJMXConnectorServer(JMXConnectorServerFactory.java:344)
at org.springframework.jmx.support.ConnectorServerFactoryBean.afterPropertiesSet(ConnectorServerFactoryBean.java:159)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1837)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1774)
... 12 more
此处我们选用了RMI协议,使用了RMI协议有个前提,这也是我们需要注意的第二点。当使用RMI协议的连接器时,我们需要响应的查找服务(tnameserv or rmiregistry)已经开启了,在Spring当中可以通过RmiRegistryFactoryBean来完成,这也是上面引入这个registry的原因,通过RMI协议,我们定义的serviceUrl格式为service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi
,在本地1099端口暴露服务,另外这里需要registry这个Bean首先初始化,然后才是serverConnector,否则无法建立连接,抛出以下异常
17:08:42.599 [main] WARN org.springframework.context.support.ClassPathXmlApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverConnector' defined in class path resource [spring-jmx-server.xml]: Invocation of init method failed; nested exception is java.io.IOException: Cannot bind to URL [rmi://localhost:1099/jmxrmi]: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is:
java.net.ConnectException: Connection refused: connect]
17:08:42.599 [main] DEBUG org.springframework.jmx.export.MBeanExporter - Unregistering JMX-exposed beans on shutdown
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverConnector' defined in class path resource [spring-jmx-server.xml]: Invocation of init method failed; nested exception is java.io.IOException: Cannot bind to URL [rmi://localhost:1099/jmxrmi]: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is:
java.net.ConnectException: Connection refused: connect]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1778)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:593)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:828)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
at com.example.spring.jmx.JmxStartServerMain.main(JmxStartServerMain.java:12)
Caused by: java.io.IOException: Cannot bind to URL [rmi://localhost:1099/jmxrmi]: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is:
java.net.ConnectException: Connection refused: connect]
at javax.management.remote.rmi.RMIConnectorServer.newIOException(RMIConnectorServer.java:827)
at javax.management.remote.rmi.RMIConnectorServer.start(RMIConnectorServer.java:432)
at org.springframework.jmx.support.ConnectorServerFactoryBean.afterPropertiesSet(ConnectorServerFactoryBean.java:193)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1837)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1774)
... 12 more
Caused by: javax.naming.ServiceUnavailableException [Root exception is java.rmi.ConnectException: Connection refused to host: localhost; nested exception is:
java.net.ConnectException: Connection refused: connect]
at com.sun.jndi.rmi.registry.RegistryContext.bind(RegistryContext.java:161)
at com.sun.jndi.toolkit.url.GenericURLContext.bind(GenericURLContext.java:228)
at javax.naming.InitialContext.bind(InitialContext.java:425)
at javax.management.remote.rmi.RMIConnectorServer.bind(RMIConnectorServer.java:644)
at javax.management.remote.rmi.RMIConnectorServer.start(RMIConnectorServer.java:427)
... 15 more
Caused by: java.rmi.ConnectException: Connection refused to host: localhost; nested exception is:
java.net.ConnectException: Connection refused: connect
at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:619)
at sun.rmi.transport.tcp.TCPChannel.createConnection(TCPChannel.java:216)
at sun.rmi.transport.tcp.TCPChannel.newConnection(TCPChannel.java:202)
at sun.rmi.server.UnicastRef.newCall(UnicastRef.java:342)
at sun.rmi.registry.RegistryImpl_Stub.bind(Unknown Source)
at com.sun.jndi.rmi.registry.RegistryContext.bind(RegistryContext.java:155)
... 19 more
Caused by: java.net.ConnectException: Connection refused: connect
at java.net.DualStackPlainSocketImpl.connect0(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
at java.net.Socket.connect(Socket.java:538)
at java.net.Socket.<init>(Socket.java:434)
at java.net.Socket.<init>(Socket.java:211)
at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket(RMIDirectSocketFactory.java:40)
at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket(RMIMasterSocketFactory.java:148)
at sun.rmi.transport.tcp.TCPEndpoint.newSocket(TCPEndpoint.java:613)
... 24 more
我们可以通过将这两个bean在xml中换个位置来保证顺序,但是这样既不优雅也可能为以后修改导致bug,我们可以通过在serverConnector配置中添加depends-on="registry"
来解决这个问题。这属于Spring的基础知识了,此处不详述原理。
启动主类,打印日志如下
"C:\Program Files\Java\jdk1.8.0_121\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\lib\idea_rt.jar=58496:D:\Program Files\JetBrains\IntelliJ IDEA 2019.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_121\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_121\jre\lib\rt.jar;D:\app\embrace\target\classes;D:\maven\repo\org\springframework\boot\spring-boot-starter-web\2.1.9.RELEASE\spring-boot-starter-web-2.1.9.RELEASE.jar;D:\maven\repo\org\springframework\boot\spring-boot-starter\2.1.9.RELEASE\spring-boot-starter-2.1.9.RELEASE.jar;D:\maven\repo\org\springframework\boot\spring-boot\2.1.9.RELEASE\spring-boot-2.1.9.RELEASE.jar;D:\maven\repo\org\springframework\boot\spring-boot-autoconfigure\2.1.9.RELEASE\spring-boot-autoconfigure-2.1.9.RELEASE.jar;D:\maven\repo\org\springframework\boot\spring-boot-starter-logging\2.1.9.RELEASE\spring-boot-starter-logging-2.1.9.RELEASE.jar;D:\maven\repo\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;D:\maven\repo\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;D:\maven\repo\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;D:\maven\repo\org\apache\logging\log4j\log4j-to-slf4j\2.11.2\log4j-to-slf4j-2.11.2.jar;D:\maven\repo\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;D:\maven\repo\org\slf4j\jul-to-slf4j\1.7.28\jul-to-slf4j-1.7.28.jar;D:\maven\repo\javax\annotation\javax.annotation-api\1.3.2\javax.annotation-api-1.3.2.jar;D:\maven\repo\org\springframework\spring-core\5.1.10.RELEASE\spring-core-5.1.10.RELEASE.jar;D:\maven\repo\org\springframework\spring-jcl\5.1.10.RELEASE\spring-jcl-5.1.10.RELEASE.jar;D:\maven\repo\org\yaml\snakeyaml\1.23\snakeyaml-1.23.jar;D:\maven\repo\org\springframework\boot\spring-boot-starter-json\2.1.9.RELEASE\spring-boot-starter-json-2.1.9.RELEASE.jar;D:\maven\repo\com\fasterxml\jackson\core\jackson-databind\2.9.9.3\jackson-databind-2.9.9.3.jar;D:\maven\repo\com\fasterxml\jackson\core\jackson-annotations\2.9.0\jackson-annotations-2.9.0.jar;D:\maven\repo\com\fasterxml\jackson\core\jackson-core\2.9.9\jackson-core-2.9.9.jar;D:\maven\repo\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.9.9\jackson-datatype-jdk8-2.9.9.jar;D:\maven\repo\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.9.9\jackson-datatype-jsr310-2.9.9.jar;D:\maven\repo\com\fasterxml\jackson\module\jackson-module-parameter-names\2.9.9\jackson-module-parameter-names-2.9.9.jar;D:\maven\repo\org\springframework\boot\spring-boot-starter-tomcat\2.1.9.RELEASE\spring-boot-starter-tomcat-2.1.9.RELEASE.jar;D:\maven\repo\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.26\tomcat-embed-websocket-9.0.26.jar;D:\maven\repo\org\hibernate\validator\hibernate-validator\6.0.17.Final\hibernate-validator-6.0.17.Final.jar;D:\maven\repo\javax\validation\validation-api\2.0.1.Final\validation-api-2.0.1.Final.jar;D:\maven\repo\org\jboss\logging\jboss-logging\3.3.2.Final\jboss-logging-3.3.2.Final.jar;D:\maven\repo\com\fasterxml\classmate\1.3.4\classmate-1.3.4.jar;D:\maven\repo\org\springframework\spring-web\5.1.10.RELEASE\spring-web-5.1.10.RELEASE.jar;D:\maven\repo\org\springframework\spring-beans\5.1.10.RELEASE\spring-beans-5.1.10.RELEASE.jar;D:\maven\repo\org\springframework\spring-webmvc\5.1.10.RELEASE\spring-webmvc-5.1.10.RELEASE.jar;D:\maven\repo\org\springframework\spring-aop\5.1.10.RELEASE\spring-aop-5.1.10.RELEASE.jar;D:\maven\repo\org\springframework\spring-context\5.1.10.RELEASE\spring-context-5.1.10.RELEASE.jar;D:\maven\repo\org\springframework\spring-expression\5.1.10.RELEASE\spring-expression-5.1.10.RELEASE.jar;D:\maven\repo\javax\servlet\javax.servlet-api\3.1.0\javax.servlet-api-3.1.0.jar;D:\maven\repo\org\apache\tomcat\embed\tomcat-embed-core\8.5.23\tomcat-embed-core-8.5.23.jar;D:\maven\repo\org\apache\tomcat\tomcat-annotations-api\8.5.23\tomcat-annotations-api-8.5.23.jar;D:\maven\repo\org\apache\tomcat\embed\tomcat-embed-jasper\8.5.16\tomcat-embed-jasper-8.5.16.jar;D:\maven\repo\org\apache\tomcat\embed\tomcat-embed-el\8.5.16\tomcat-embed-el-8.5.16.jar;D:\maven\repo\org\eclipse\jdt\ecj\3.12.3\ecj-3.12.3.jar" com.example.spring.jmx.JmxStartServerMain
17:19:07.169 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@12bb4df8
17:19:07.453 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 5 bean definitions from class path resource [spring-jmx-server.xml]
17:19:07.564 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'mbeanServer'
17:19:07.743 [main] DEBUG org.springframework.jmx.support.JmxUtils - Found MBeanServer: com.sun.jmx.mbeanserver.JmxMBeanServer@553a3d88
17:19:07.747 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'exporter'
17:19:07.761 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'testBean'
17:19:07.796 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'registry'
17:19:07.802 [main] DEBUG org.springframework.remoting.rmi.RmiRegistryFactoryBean - Looking for RMI registry at port '1099'
17:19:09.447 [main] DEBUG org.springframework.remoting.rmi.RmiRegistryFactoryBean - Could not detect RMI registry - creating new one
17:19:09.455 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'serverConnector'
17:19:09.459 [main] DEBUG org.springframework.jmx.support.JmxUtils - Found MBeanServer: com.sun.jmx.mbeanserver.JmxMBeanServer@553a3d88
17:19:09.553 [main] INFO org.springframework.jmx.support.ConnectorServerFactoryBean - JMX connector server started: javax.management.remote.rmi.RMIConnectorServer@1dd02175
17:19:09.553 [main] DEBUG org.springframework.jmx.export.MBeanExporter - Registering beans for JMX exposure on startup
17:19:09.556 [main] DEBUG org.springframework.jmx.export.MBeanExporter - Located managed bean 'com.example.spring.jmx:name=testBean': registering with JMX server as MBean [com.example.spring.jmx:name=testBean]
二、自定义客户端
1. 创建客户端连接clientConnector
这里需要首先创建一个项目,依赖配置相同,另外还需要共享com.example.spring.jmx.IJmxTestBean
接口,然后创建spring-jmx-client.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="clientConnector"
class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
<property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>
</beans>
通过MBeanServerConnectionFactoryBean创建一个MBeanServerConnection去连接远程的JMXServerConnector。这里的serviceUrl与服务端是一样的,因为虽然是两个项目(两个虚拟机),但还是在同一台主机之上,如果不是,其中的localhost需要改为指定的IP地址。
2. 注册MBean代理Bean
当创建好了客户端与服务端的连接之后,我们需要通过代理的方式引用MBeanServer中的MBean了,我们通过Spring的MBeanProxyFactoryBean就可以了。在spring-jmx-client.xml中添加如下bean配置
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="com.example.spring.jmx:name=testBean"/>
<property name="proxyInterface" value="com.example.spring.jmx.IJmxTestBean"/>
<property name="server" ref="clientConnector"/>
</bean>
3. 创建客户端启动类
创建如下的启动类,启动Spring容器,根据beanName获取代理类并调用。
package com.example.spring.jmx;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class JmxStartClientMain {
public static void main(String[] args) {
// 启动Spring容器
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-jmx-client.xml");
IJmxTestBean proxy = (IJmxTestBean) applicationContext.getBean("proxy");
System.out.println(proxy.getName());
System.out.println(proxy.add(2, 3));
}
}
执行结果如下,说明我们的自定义客户端是没有问题的。
三、源码分析
1. 服务端
首先查看org.springframework.remoting.rmi.RmiRegistryFactoryBean这个类的继承结构,实现了FactoryBean和InitializingBean这两个接口,老套路,主要的逻辑还是在afterPropertiesSet这个方法当中
@Override
public void afterPropertiesSet() throws Exception {
// Check socket factories for registry.
1. 默认情况下clientSocketFactory、serverSocketFactory都是没有配置的
if (this.clientSocketFactory instanceof RMIServerSocketFactory) {
this.serverSocketFactory = (RMIServerSocketFactory) this.clientSocketFactory;
}
if ((this.clientSocketFactory != null && this.serverSocketFactory == null) ||
(this.clientSocketFactory == null && this.serverSocketFactory != null)) {
throw new IllegalArgumentException(
"Both RMIClientSocketFactory and RMIServerSocketFactory or none required");
}
2 获取RMI注册进行暴露
// Fetch RMI registry to expose.
this.registry = getRegistry(this.host, this.port, this.clientSocketFactory, this.serverSocketFactory);
}
如果配置了registryHost则说明要获取远程的Registry,如果不是则在本地获取
protected Registry getRegistry(String registryHost, int registryPort,
@Nullable RMIClientSocketFactory clientSocketFactory, @Nullable RMIServerSocketFactory serverSocketFactory)
throws RemoteException {
if (registryHost != null) {
// Host explicitly specified: only lookup possible.
if (logger.isDebugEnabled()) {
logger.debug("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
}
Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
testRegistry(reg);
return reg;
}
else {
return getRegistry(registryPort, clientSocketFactory, serverSocketFactory);
}
}
最后会调用到如下的方法,最后创建一个Registry用于RMI服务注册。
protected Registry getRegistry(int registryPort) throws RemoteException {
1. alwaysCreate属性默认为false 不需要每次都创建
if (this.alwaysCreate) {
logger.debug("Creating new RMI registry");
this.created = true;
return LocateRegistry.createRegistry(registryPort);
}
if (logger.isDebugEnabled()) {
logger.debug("Looking for RMI registry at port '" + registryPort + "'");
}
synchronized (LocateRegistry.class) {
try {
// Retrieve existing registry.
2 尝试获取已经存在的registry
Registry reg = LocateRegistry.getRegistry(registryPort);
3 获取的registry不一定真实有用 此处进行测试 如果有问题 就会抛出异常
testRegistry(reg);
return reg;
}
catch (RemoteException ex) {
logger.trace("RMI registry access threw exception", ex);
logger.debug("Could not detect RMI registry - creating new one");
// Assume no registry found -> create new one.
this.created = true;
4. 从本地获取的registry存在的问题 需要再次重新创建一个
return LocateRegistry.createRegistry(registryPort);
}
}
}
对于ConnectorServerFactoryBean这个类与上面的类继承结构是一样的,都是套路呀,查看afterPropertiesSet方法即可
@Override
public void afterPropertiesSet() throws JMException, IOException {
1. 获取本地的MBeanServer
if (this.server == null) {
this.server = JmxUtils.locateMBeanServer();
}
2. 根据serviceUrl创建JMXConnectorServer并且附着到MBeanServer当中
// Create the JMX service URL.
JMXServiceURL url = new JMXServiceURL(this.serviceUrl);
// Create the connector server now.
this.connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, this.environment, this.server);
// Set the given MBeanServerForwarder, if any.
if (this.forwarder != null) {
this.connectorServer.setMBeanServerForwarder(this.forwarder);
}
3. 如果设置了objectName也会将当前bean注册到MBeanServer当中,此处没有设置,不会注册
// Do we want to register the connector with the MBean server?
if (this.objectName != null) {
doRegister(this.connectorServer, this.objectName);
}
try {
4. 开启一个新的线程用于JMX Connector,通过设置threaded参数开启,通过daemon设置是否为守护线程,默认这两个参数都是false
if (this.threaded) {
// Start the connector server asynchronously (in a separate thread).
final JMXConnectorServer serverToStart = this.connectorServer;
Thread connectorThread = new Thread() {
@Override
public void run() {
try {
serverToStart.start();
}
catch (IOException ex) {
throw new JmxException("Could not start JMX connector server after delay", ex);
}
}
};
connectorThread.setName("JMX Connector Thread [" + this.serviceUrl + "]");
connectorThread.setDaemon(this.daemon);
connectorThread.start();
}
else {
5. 开启JMXConnectorServer通道,此时客户端才能发起连接,前面仅仅是创建了而已
// Start the connector server in the same thread.
this.connectorServer.start();
}
if (logger.isInfoEnabled()) {
logger.info("JMX connector server started: " + this.connectorServer);
}
}
catch (IOException ex) {
// Unregister the connector server if startup failed.
unregisterBeans();
throw ex;
}
}
以上代码非常简单。
2. 客户端
对于客户端来说首先是通过MBeanServerConnectionFactoryBean创建MBeanServerConnection,然后通过这个连接到服务端已经创建好的JMXServerConnector。看一下类的继承结构,依然很简单,主要的逻辑还是在afterPropertiesSet方法中创建所需的连接。
private boolean connectOnStartup = true;
/**
* Creates a {@code JMXConnector} for the given settings
* and exposes the associated {@code MBeanServerConnection}.
*/
@Override
public void afterPropertiesSet() throws IOException {
if (this.serviceUrl == null) {
throw new IllegalArgumentException("Property 'serviceUrl' is required");
}
是否在启动时创建连接,默认为true
if (this.connectOnStartup) {
启动时(也就是当前)创建连接
connect();
}
else {
在第一次请求的时候创建连接
createLazyConnection();
}
}
对于在当前创建连接来说,非常简单,就是直接调用JDK的方法,没啥好说的。
private Map<String, Object> environment = new HashMap<>();
@Nullable
private JMXConnector connector;
@Nullable
private MBeanServerConnection connection;
/**
* Connects to the remote {@code MBeanServer} using the configured service URL and
* environment properties.
*/
private void connect() throws IOException {
Assert.state(this.serviceUrl != null, "No JMXServiceURL set");
this.connector = JMXConnectorFactory.connect(this.serviceUrl, this.environment);
this.connection = this.connector.getMBeanServerConnection();
}
而对于懒加载的模式,这里使用了代理设置模式,通过创建代理的方式当前仅仅创建一个代理返回,而实际的连接在第一次请求时创建。
@Nullable
private JMXConnectorLazyInitTargetSource connectorTargetSource;
/**
* Creates lazy proxies for the {@code JMXConnector} and {@code MBeanServerConnection}.
*/
private void createLazyConnection() {
this.connectorTargetSource = new JMXConnectorLazyInitTargetSource();
TargetSource connectionTargetSource = new MBeanServerConnectionLazyInitTargetSource();
this.connector = (JMXConnector)
new ProxyFactory(JMXConnector.class, this.connectorTargetSource).getProxy(this.beanClassLoader);
this.connection = (MBeanServerConnection)
new ProxyFactory(MBeanServerConnection.class, connectionTargetSource).getProxy(this.beanClassLoader);
}
这里出现了两个TargetSource
实现类。它们会在第一次调用代理的时候才会通过createObject方法创建真实的对象,当前返回的connector和connection都是代理对象,这样就实现了懒加载的创建连接对象。
调用情况如下所示
/**
* Lazily creates a {@code JMXConnector} using the configured service URL
* and environment properties.
* @see MBeanServerConnectionFactoryBean#setServiceUrl(String)
* @see MBeanServerConnectionFactoryBean#setEnvironment(java.util.Properties)
*/
private class JMXConnectorLazyInitTargetSource extends AbstractLazyCreationTargetSource {
@Override
protected Object createObject() throws Exception {
Assert.state(serviceUrl != null, "No JMXServiceURL set");
return JMXConnectorFactory.connect(serviceUrl, environment);
}
@Override
public Class<?> getTargetClass() {
return JMXConnector.class;
}
}
/**
* Lazily creates an {@code MBeanServerConnection}.
*/
private class MBeanServerConnectionLazyInitTargetSource extends AbstractLazyCreationTargetSource {
@Override
protected Object createObject() throws Exception {
Assert.state(connector != null, "JMXConnector not initialized");
return connector.getMBeanServerConnection();
}
@Override
public Class<?> getTargetClass() {
return MBeanServerConnection.class;
}
}
创建好了连接之后,就是在客户端调用MBean的操作了,因为实际的MBean对象是在服务端,此处需要调用必须通过网络请求,通常为了方便使用(面向接口编程),在本地创建一个对应接口的代理对象,然后在方法调用时发起网络请求获取结果,看起来更本地调用一样。而这里创建对象使用的是org.springframework.jmx.access.MBeanProxyFactoryBean这个类。
我们可以看到这个类除了我们之前见过的FactoryBean
和InitializingBean
类之外,还继承了MBeanClientInterceptor
这样一个类,其实如果熟悉Spring AOP源码的话,看到MethodInterceptor
的时候也没法好奇怪的。在上面我们创建连接代理时是通过传入TargetSource
对象(用于代理执行时获取真实对象),此处创建代理传入的是Interceptor
对象,也就是MBeanProxyFactoryBean
自己了,这样在调用代理方法的时候其实需要执行org.aopalliance.intercept.MethodInterceptor#invoke方法。
创建代理的逻辑如下
@Nullable
private Object mbeanProxy;
@Override
public void afterPropertiesSet() throws MBeanServerNotFoundException, MBeanInfoRetrievalException {
1. 保证父类的初始化逻辑
super.afterPropertiesSet();
2. 初始化必须包含proxyInterface不能为空 这里也可以看出配置managementInterface也是可以的
if (this.proxyInterface == null) {
this.proxyInterface = getManagementInterface();
if (this.proxyInterface == null) {
throw new IllegalArgumentException("Property 'proxyInterface' or 'managementInterface' is required");
}
}
else {
3. 如果设置了参数proxyInterface而没有设置managementInterface参数,则取proxyInterface作为managementInterface参数
if (getManagementInterface() == null) {
setManagementInterface(this.proxyInterface);
}
}
4. 创建MBean代理对象 实现的接口为proxyInterface,增强逻辑为当前对象逻辑,其实就是MethodInteceptor的invoke方法的逻辑
this.mbeanProxy = new ProxyFactory(this.proxyInterface, this).getProxy(this.beanClassLoader);
}
对于在父类中的初始化方法逻辑如下,主要还是关于懒加载的问题
public void afterPropertiesSet() {
if (this.server != null && this.refreshOnConnectFailure) {
throw new IllegalArgumentException("'refreshOnConnectFailure' does not work when setting " +
"a 'server' reference. Prefer 'serviceUrl' etc instead.");
}
// 是否在启动过程中进行连接 这个参数默认为true
if (this.connectOnStartup) {
prepare();
}
}
/**
* Route the invocation to the configured managed resource..
* @param invocation the {@code MethodInvocation} to re-route
* @return the value returned as a result of the re-routed invocation
* @throws Throwable an invocation error propagated to the user
* @see #doInvoke
* @see #handleConnectFailure
*/
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Lazily connect to MBeanServer if necessary.
synchronized (this.preparationMonitor) {
if (!isPrepared()) {
prepare();
}
}
try {
return doInvoke(invocation);
}
catch (MBeanConnectFailureException | IOException ex) {
return handleConnectFailure(invocation, ex);
}
}
/**
* Ensures that an {@code MBeanServerConnection} is configured and attempts
* to detect a local connection if one is not supplied.
*/
public void prepare() {
synchronized (this.preparationMonitor) {
1. 如果配置了server参数,则通过server获取连接
if (this.server != null) {
this.serverToUse = this.server;
}
else {
1.b 如果没有配置server参数,也可以通过serviceUrl来获取连接
this.serverToUse = null;
this.serverToUse = this.connector.connect(this.serviceUrl, this.environment, this.agentId);
}
以下为配置serviceUrl的方式获取连接
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
<property name="objectName" value="com.example.spring.jmx:name=testBean"/>
<property name="proxyInterface" value="com.example.spring.jmx.IJmxTestBean"/>
<!--server参数和serviceUrl参数二选一-->
<!-- <property name="server" ref="clientConnector"/>-->
<property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>
以下通过创建JDK中的MBeanServerInvocationHandler对象
@Nullable
private MBeanServerInvocationHandler invocationHandler;
this.invocationHandler = null;
2. useStrictCasing参数默认为true
if (this.useStrictCasing) {
Assert.state(this.objectName != null, "No ObjectName set");
// Use the JDK's own MBeanServerInvocationHandler, in particular for native MXBean support.
this.invocationHandler = new MBeanServerInvocationHandler(this.serverToUse, this.objectName,
(this.managementInterface != null && JMX.isMXBeanInterface(this.managementInterface)));
}
else {
// Non-strict casing can only be achieved through custom invocation handling.
// Only partial MXBean support available!
3. useStrictCasing设置为false的话,则根据MBeanInfo填充allowedAttributes和allowedOperations属性值,然后在调用的时候,通过javax.management.MBeanServerConnection#getAttribute方法获取属性或者javax.management.MBeanServerConnection#invoke调用MBean操作
retrieveMBeanInfo(this.serverToUse);
}
}
}
初始化完成以后,当客户端调用MBean代理的时候,会调用代理的invoke方法,同样也会进入到MBeanProxyFactoryBean的invoke方法(AOP增强逻辑),如下所示:
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Lazily connect to MBeanServer if necessary.
1. 如果连接还没有创建的话,再次获取连接
synchronized (this.preparationMonitor) {
if (!isPrepared()) {
prepare();
}
}
try {
2. 调用方法
return doInvoke(invocation);
}
catch (MBeanConnectFailureException | IOException ex) {
return handleConnectFailure(invocation, ex);
}
}
在默认情况下还是比较简单的。
总结
本章通过自定义JMX客户端与MBeanServer进行了连接,并调用MBean的方法