转自:http://blog.sina.com.cn/s/blog_6151984a0100mjia.html
RMI(Remote Method Invocation)
RMI是
不同JVM之间
的对象通信的协议
1 启动MBeanServer 2 建立并启动 NamingService MBean,实际就是rmiregistry 3 为MBeanServer建立160 JMX RMIConnector,此RMI连接器提供: service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxconnecto r 4 客户端连接
4.1 通用客户端(如jconsole) 4.2 自定义客户端 生成 JMXConnector ,与JMI server建立 RMI连接 建立与MBean server的连接 MBeanServerInvocationHan dler.newProxyInstance()获取代理MBean
1. RMI Server
package com.machome.jmx.appTest; import javax.management.MBeanServer; import javax.management.MBeanServerFactory; import javax.management.MalformedObjectNameExcep tion; import javax.management.ObjectName; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactor y; import javax.management.remote.JMXServiceURL; import com.machome.bean.Hello; import mx4j.tools.naming.NamingService; public class RmiServer { private MBeanServer mBeanServer; private JMXConnectorServer connectorServer; private JMXServiceURL jmxUrl; public RmiServer() throws Exception{ mBeanServer = MBeanServerFactory.createMBeanServer(); createRmiregistry(); CreateJMXConnector(); } public static void main(String[] args) throws Exception { RmiServer rmiServer = new RmiServer(); // 加一个测试MBean Hello对象 rmiServer.addMbean(new Hello(), "mbean", "name", "hello"); rmiServer.startJMXConnector(); System.out.println("Server up and running"); } public ObjectName addMbean(Object o,String domain,String key,String value) throws Exception{ ObjectName oName = new ObjectName(domain, key, value); mBeanServer.registerMBean(o, oName); return oName; } public void createRmiregistry() throws Exception{ // 1.注册NamingService MBean ObjectName namingName = ObjectName .getInstance("naming:type=rmiregistry"); // 这里采用MC4J的mx4j.tools.naming.NamingService NamingService ns = new NamingService(); mBeanServer.registerMBean(ns, namingName); // 2.启动NamingService MBean mBeanServer.invoke(namingName, "start", null, null); } public void CreateJMXConnector() throws Exception{ // 1.nammingPort,从NamingService获得Port,缺省是1099 ObjectName namingName = ObjectName.getInstance("naming:type=rmiregistry"); int namingPort = ((Integer)mBeanServer.getAttribute(namingName, "Port")).intValue(); // 2. jndiPath String jndiPath = "/jmxconnector "; // 3. JMXServiceURL ,为: // service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxconnector jmxUrl = new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:" + namingPort + jndiPath); // 4.Create and start the RMIConnectorServer // 中间设为null的参数,是针对认证的,我们这里没打开,设为null connectorServer = JMXConnectorServerFactor y .newJMXConnectorServer(jmxUrl, null, mBeanServer); } public void startJMXConnector() throws Exception{ connectorServer.start(); } public void stopJMXConnector() throws Exception{ connectorServer.stop(); } } 执行此RmiServer: Server up and running 可以看到TCP 1099端口被listen D:\Documents and Settings\mac>netstat -an Active Connections Proto Local Address Foreign Address State TCP 0.0.0.0:135 0.0.0.0:0 LISTENING TCP 0.0.0.0:445 0.0.0.0:0 LISTENING TCP 0.0.0.0:1025 0.0.0.0:0 LISTENING TCP 0.0.0.0:1099 0.0.0.0:0 LISTENING TCP 0.0.0.0:2737 0.0.0.0:0 LISTENING TCP 0.0.0.0:3306 0.0.0.0:0 LISTENING TCP 0.0.0.0:6059 0.0.0.0:0 LISTENING TCP 0.0.0.0:6648 0.0.0.0:0 LISTENING
2.通用客户端(典型的比如jconsole)
这里用的是jdk 1.6 jconsole 远程进程: service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxconnector 用户名: 口令: 用户名和口令都可以空着,不必输入 点"连接",可以进入jconsole界面 可以在mbean下看到hello对象
3. 自定义client 端通过
RMI连接 Server
package com.machome.jmx.appTest; import javax.management.JMX; import javax.management.MBeanServerConnection; import javax.management.MBeanServerDelegateMBean ; import javax.management.MBeanServerInvocationHan dler; import javax.management.ObjectName; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; import com.machome.bean.Hello; import com.machome.bean.HelloMBean; public class RmiClient { private JMXServiceURL jmxUrl; private JMXConnector connector; private MBeanServerConnection mBeanServerconnection; public RmiClient()throws Exception{ createJMXRmiConnector(); createMBeanServerConnect ion(); } public static void main(String[] args) throws Exception { new RmiClient().test(); } private void createJMXRmiConnector() throws Exception{ // 1.The JMXConnectorServer protocol String serverProtocol = "rmi"; // 2.The RMI server's host // this is actually ignored by JSR 160 String serverHost = "localhost"; // 3.The host, port and path where the rmiregistry runs. String namingHost = "localhost"; int namingPort = 1099; String jndiPath = "/jmxconnector"; // 4. connector server url jmxUrl = new JMXServiceURL("service:jmx:" + serverProtocol + "://" + serverHost + "/jndi/rmi://" + namingHost + ":" + namingPort + jndiPath); // 5. 生成 JMXConnector,连接到url一端 // Connect a JSR 160 JMXConnector to the server side connector = JMXConnectorFactory.connect(jmxUrl); } private void createMBeanServerConnect ion() throws Exception{ mBeanServerconnection = connector.getMBeanServerConnection (); } public void test()throws Exception{ ObjectName oName = new ObjectName("mbean", "name", "hello"); // 获取代理对象 Object proxy = MBeanServerInvocationHan dler .newProxyInstance(mBeanServerconnection,oName, HelloMBean.class, true) ; // 获取测试MBean,并执行它的(暴露出来被管理监控的)方法 HelloMBean helloMBean = (HelloMBean)proxy; helloMBean.setName("mac"); helloMBean.printHello(); helloMBean.printHello("haha"); } } 执行: Server up and running Hello, mac Hello, haha
上面例子中RMI中的rmiregistry MBean,我们采用的mc4j-tools.jar中的 NamingService,代码如下,代码并不复杂,其实我们也可以自定义一个代替它:
public class NamingService implements NamingServiceMBean { public NamingService() { this(1099 ); } public NamingService(int port) { setPort(port); } public void setPort(int port) { if(isRunning()) { throw new IllegalStateException("NamingService is running, cannot change the port"); } else { m_port = port; return; } } public int getPort() { return m_port; } public boolean isRunning() { return m_running; } public void start() throws RemoteException { if(!isRunning()) { m_registry = LocateRegistry.createRegistry(getPort()); m_running = true; } } public void stop() throws NoSuchObjectException { if(isRunning()) m_running = !UnicastRemoteObject.unexportObject(m_registry, true); } public String[] list() throws RemoteException { if(!isRunning()) throw new IllegalStateException("NamingService is not running"); else return m_registry.list(); } public void unbind(String name) throws RemoteException, NotBoundException { if(!isRunning()) { throw new IllegalStateException("NamingService is not running"); } else { m_registry.unbind(name); return; } } private int m_port; private Registry m_registry; private boolean m_running; } MBean接口: public interface NamingServiceMBean { public abstract void setPort(int i); public abstract int getPort(); public abstract boolean isRunning(); public abstract void start() throws RemoteException; public abstract void stop() throws NoSuchObjectException; public abstract String[] list() throws RemoteException; public abstract void unbind(String s) throws RemoteException, NotBoundException; }
1.spring 配置如下:
<!-- 1.定义自己的bean --> <bean id="hello" class="com.machome.bean.Hello" /> <!-- JMX configuration --> <!-- 2.创建一个mbeanServer bean--> <!-- 如果你的spring是应用在容器中,则不需要此步骤,此步骤用于单独建立独立的MBeanServer中 <bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"> </bean> --> <!-- 3.定义assembler bean ,装配bean MetadataMBeanInfoAssembl er是AutodetectCapableMBeanIn foAssembler 唯一实现 spring文档中有专门介绍AutodetectCapableMBeanIn foAssembler的章节 --> <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembl er"> <!-- 注入下面定义的jmxAttributeSource--> <property name="attributeSource" ref="jmxAttributeSource"/> </bean> <!-- 4.定义 解释mbean中Annotation的bean--> <bean id="jmxAttributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSo urce"/> <!-- 5.定义 RMI连接器,注意需要先定义一个naming 注册 bean--> <bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean" destroy-method="destroy"> <property name="port" value="1099" /> </bean> <bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBe an" depends-on="registry"> <property name="objectName" value="connector:name=rmi"/> <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxconnector"/> <!-- 如果上面步骤2.你单独定义一个mbeanServer,这里需要指定,否则rmi connector缺省是去寻找runtime环境内的MBeanserver(通常是容器的MBeanServer) <property name="server" ref="mbeanServer"/> --> <!-- 多线程启动connector,一个线程一个connector --> <property name="threaded" value="true" /> <!-- 多线程启动connector,thread daemon --> <property name="daemon" value="true" /> </bean> <!-- 6.定义MBeanExporter bean, 这是spring jmx最核心的类 --> <bean id="mBeanExporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false" > <!-- 注入上面定义的assembler 两个bean注入--> <property name="assembler" ref="assembler"/> <!-- 如果上面步骤2.你单独定义一个mbeanServer,这里需要指定,否则export bean缺省是去寻找runtime环境内的MBeanserver(通常是容器的MBeanServer) <property name="server" ref="mbeanServer"/> --> <!-- 将1.里面定义的mbean注册到mBeanExporter bean中 --> <property name="beans"> <map> <entry key="mbean:name=hello" value-ref="hello"/> </map> </property> </bean>
2. web.xml装载spring 配置
<!-- ##################### 初始化spring容器 ###########--> <context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath:ssh-mysql.xml </param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
3.将项目部署入Tomcat
启动Tomcat 不需要执行任何servlet,action,只要spring随Tomcat启动,就会看到TCP 1099端口被listen D:\Documents and Settings\mac>netstat -an Active Connections Proto Local Address Foreign Address State TCP 0.0.0.0:135 0.0.0.0:0 LISTENING TCP 0.0.0.0:445 0.0.0.0:0 LISTENING TCP 0.0.0.0:1025 0.0.0.0:0 LISTENING TCP 0.0.0.0:1099 0.0.0.0:0 LISTENING TCP 0.0.0.0:2737 0.0.0.0:0 LISTENING
4. 执行通用客户端(典型的比如jconsole)
这里用的是jdk 1.6 jconsole 远程进程: service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxconnector 用户名: 口令: 用户名和口令都可以空着,不必输入 点"连接",可以进入jconsole界面 与前面application RMI的例子不同,这里的不仅可以看到hello MBean对象,还能看到很多MBean对象,如Catalina目录下的很多MBean,因为spring没有定义新的MBeanServer,而是用 tomcat容器自己的MBeanServer,所以能看到很多Tomcat容器自己的service MBean
5.执行前面application RMI的例子的RmiClient
(我在spring rmi 例子和application rmi 例子
用的相同的serviceUrl
,都是
service:jmx:rmi://localhost/jndi/rmi: //localhost:1099/jmxconnector
因此客户端可以访问这两种例子的Server)
其实spring 例子里的spring 配置的no.2,no.3.no.4都可以不用
MBeanServer可以不用建立,spring会自动搜索容器自己的MBeanServer,
MBeanInfoAssembler和AnnotationJmxAttributeSo
urce 则只是提供注释MBean功能,以代替implement MBean接口,完全可以不用这种注释,而用传统的implement MBean接口
<!-- 1.定义自己的bean --> <bean id="hello" class="com.machome.bean.Hello" /> <!-- 5.定义 RMI连接器,注意需要先定义一个naming 注册 bean--> <bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean" destroy-method="destroy"> <property name="port" value="1099" /> </bean> <bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBe an" depends-on="registry"> <property name="objectName" value="connector:name=rmi"/> <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxconnector"/> <!-- 多线程启动connector,一个线程一个connector --> <property name="threaded" value="true" /> <!-- 多线程启动connector,thread daemon --> <property name="daemon" value="true" /> </bean> <!-- 6.定义MBeanExporter bean, 这是spring jmx最核心的类 --> <bean id="mBeanExporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false" > <!-- 将1.里面定义的mbean注册到mBeanExporter bean中 --> <property name="beans"> <map> <entry key="mbean:name=hello" value-ref="hello"/> </map> </property> </bean>