20-Spring JMX
基本概念
jmx(java management extensions,即java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。jmx可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。简介看上去不是很直观和明白,也可能我了解的太少,理解还不够深入。那么在本例中,主要是介绍通过使用spring jmx来管理和修改运行中的应用程序的配置。关于更多的jmx概念,大家可以自行搜索。
将spring bean导出为mbean
通过mbeanexporter将普通的spring bean导出为mbean,spring bean中的属性就会变成mbean的托管属性,因此,我们就可以在程序运行时,对该属性进行修改。如下代码
1 @controller 2 @requestmapping("/biz") 3 public class spittlecontroller { 4 private int pagesize = 10; 5 6 public int getpagesize() { 7 return pagesize; 8 } 9 10 public void setpagesize(int pagesize) { 11 this.pagesize = pagesize; 12 } 13 14 @requestmapping(value = "/test") 15 public string test() { 16 system.out.println("pagesize="+pagesize); 17 return "index"; 18 } 19 20 }
1 @configuration 2 @componentscan("spittle.controller") 3 public class mbeanconfig { 4 5 @bean 6 public mbeanexporter mbeanexporter(spittlecontroller spittlecontroller) { 7 system.out.println("mbeanconfig mbeanexporter"); 8 mbeanexporter exporter = new mbeanexporter(); 9 map<string, object> beans = new hashmap<>(); 10 beans.put("spitter:name=spittlecontroller", spittlecontroller); 11 exporter.setbeans(beans); 12 return exporter; 13 } 14 }
配置mbeanexporter最简单的方式为其它的beans属性设置一个map集合,集合中的元素就是我们希望导出为mbean的一个或多个spring bean。本例中,我们希望将spittlecontroller导出为mbean,并为它指定一个名字spitter:name=spittlecontroller,spitter是管理域的名称,接下来就是key=value一个键值对,最终在jmx管理工具中,看到的mbean名字就是spittlecontroller。然后我们启动tomcat服务,就可以使用jconsole来查看和修改spittlecontroller这个mbean了。
通过jconsole,可以看到spittlecontroller的属性和方法。为了看到动态修改属性的效果,我们把pagesize改为50,然后可以通过浏览器,访问spittlecontroller的test方法所对应的url,也可以通过jconsole操作界面来调用test方法,来查看pagesize属性的变化。
下面控制台中的pagesize值,是在jconsole中修改属性值前后的输出结果
有时候我们希望通过程序,来访问和操作远程服务器端的mbean,这时候我们就需要创建和访问远程mbean了。
暴露远程mbean
使mbean成为远程对象的最简单方式是配置spring的connectorserverfactorybean,并为其设置serviceurl属性。我们来看一下如下代码
1 @configuration 2 @componentscan("spittle.controller") 3 public class mbeanconfig { 4 5 @bean 6 public mbeanexporter mbeanexporter(spittlecontroller spittlecontroller) { 7 system.out.println("mbeanconfig mbeanexporter"); 8 mbeanexporter exporter = new mbeanexporter(); 9 map<string, object> beans = new hashmap<>(); 10 beans.put("spitter:name=spittlecontroller", spittlecontroller); 11 exporter.setbeans(beans); 12 return exporter; 13 } 14 15 @bean(name="rmiregistryfb") 16 public rmiregistryfactorybean rmiregistryfb() { 17 system.out.println("rmiregistryfb"); 18 rmiregistryfactorybean rmiregistryfb = new rmiregistryfactorybean(); 19 rmiregistryfb.setport(1098); 20 return rmiregistryfb; 21 } 22 23 @bean 24 @dependson("rmiregistryfb") 25 public connectorserverfactorybean connectorserverfactorybean() { 26 system.out.println("connectorserverfactorybean"); 27 connectorserverfactorybean csfb = new connectorserverfactorybean(); 28 string serviceurl = "service:jmx:rmi://localhost/jndi/rmi://localhost:1098/spitter"; 29 csfb.setserviceurl(serviceurl); 30 return csfb; 31 } 32 33 }
connectorserverfactorybean的serviceurl属性指明了通过rmi协议来访问远程mbean,并绑定到本机1098端口的一个rmi注册表。因此,我们还需要一个监听该端口的rmi注册表对象,这正是rmiregistryfb的作用。注意观察,就会发现connectorserverfactorybean多了一个@dependson("rmiregistryfb")注解,从字面意思来看,该bean依赖于rmiregistryfb,由于spring在初始化bean的时候是无序加载,如果先加载了connectorserverfactorybean就会报错,所以就要先加载rmiregistryfb,再加载connectorserverfactorybean。现在我们的mbean可以通过rmi进行远程访问了。接下来我们来看看如何访问远程mbean。
访问远程mbean
要想访问远程mbean服务器,我们需要在spring上下文中配置mbeanserverconnectionfactorybean。该bean用于访问我们在上一节中所创建的基于rmi的远程服务器。
1 public class mbeanclientconfig { 2 3 @bean 4 public mbeanserverconnectionfactorybean connectionfactorybean() { 5 system.out.println("connectionfactorybean"); 6 mbeanserverconnectionfactorybean mbscfb = new mbeanserverconnectionfactorybean(); 7 try { 8 string serviceurl = "service:jmx:rmi://localhost/jndi/rmi://localhost:1098/spitter"; 9 mbscfb.setserviceurl(serviceurl); 10 } catch (malformedurlexception e) { 11 e.printstacktrace(); 12 } 13 return mbscfb; 14 } 15 16 }
现在,让我们编写一个测试类,来访问远程mbean,并动态修改它的属性吧。
1 @runwith(springjunit4classrunner.class) 2 @contextconfiguration(classes=mbeanclientconfig.class) 3 public class mbeanclienttest { 4 5 @autowired 6 mbeanserverconnection mbeanserverconnection; 7 8 @test 9 public void mbeanservertest() { 10 objectname name = new objectname("spitter:name=spittlecontroller"); 11 set<objectname> mbeannames = mbeanserverconnection.querynames(name, null); 12 iterator<objectname> iter = mbeannames.iterator(); 13 while(iter.hasnext()) { 14 objectname objectname = iter.next(); 15 system.out.println(objectname.tostring()); 16 } 17 mbeanserverconnection.setattribute(name, new attribute("pagesize", 20)); 18 object cronexpression = mbeanserverconnection.getattribute(name,"pagesize"); 19 system.out.println("pagesize="+cronexpression.tostring()); 20 } 21 22 }
测试类中,我们注入了一个mbeanserverconnection,它是mbeanserverconnectionfactorybean的一个对象,我们通过该对象,查找并修改前面我们创建的spittlecontroller这个mbean的pagesize属性,该属性的首字母原本是小写pagesize,当我们需要对它进行修改的时候,就要使用首字母大写的形式pagesize。
处理消息通知
通过查询mbean获得信息只是查看应用状态的一种方法。但当应用发生重要事件时,如果希望能够及时告知我们,这通常不是最有效的方法。jmx通知(jmx notification)是mbean与外部世界主动通信的一种方法,而不是等待外部应用对mbean进行查询以获得信息。spring通过notificationpublisheraware接口提供了发送通知的支持。任何希望发送通知的mbean都必须实现这个接口。例如,请查看如下程序清单
1 @component 2 @managednotification(notificationtypes = "spittlenotifier.onemillionspittles",name="todo") 3 public class spittlenotifier implements notificationpublisheraware{ 4 5 private notificationpublisher notificationpublisher; 6 //注入notificationpublisher 7 @override 8 public void setnotificationpublisher(notificationpublisher notificationpublisher) { 9 this.notificationpublisher = notificationpublisher; 10 } 11 12 /** 13 * 发送消息通知 14 */ 15 public void millionthspittleposted() { 16 notificationpublisher.sendnotification( 17 new notification("spittlenotifier.onemillionspittles", this, 0, "this is test message")); 18 } 19 }
notificationpublisher的sendnotification方法用于发送消息通知,接下来我们还需要建立一个消息监听器。接收mbean通知的标准方法是实现notificationlistener接口,我们需要编写一个实现了该接口的类,并重写它的handlenotification方法。
1 public class spittlenotificationlistener implements notificationlistener { 2 3 /** 4 * 处理消息通知 5 * @param notification 6 * @param handback 7 */ 8 @override 9 public void handlenotification(notification notification, object handback) { 10 string message = notification.getmessage(); 11 system.out.println("receive message="+message); 12 } 13 }
消息通知和消息监听都已经创建好了。是时候给他们两个建立联系了,不然消息通知发出去之后,我们怎么知道谁来接收消息呢?
1 @configuration 2 @componentscan("spittle.notifier") 3 public class notifierconfig { 4 5 @bean 6 public mbeanexporter mbeanexporter2(spittlenotifier spittlenotifier) { 7 system.out.println("notifierconfig mbeanexporter"); 8 mbeanexporter exporter = new mbeanexporter(); 9 map<string, object> beans = new hashmap<>(); 10 beans.put("spitter:name=spitternotifier", spittlenotifier); 11 exporter.setbeans(beans); 12 map<string, notificationlistener> mappings = new hashmap<>(); 13 mappings.put("spitter:name=spitternotifier", new spittlenotificationlistener()); 14 exporter.setnotificationlistenermappings(mappings); 15 return exporter; 16 } 17 }
类似前面导出mbean的代码,这里我们把spitternotifier导出为mbean,并为该mbean添加一个监听器。这样,spitternotifier就和spittlenotificationlistener建立起联系了。注意mappings.put这一行,这里面的key要和beans.put这一行的key保持一致,beans.put是为mbean指定一个名字,mappings.put是为指定的mbean添加监听器,如果这两个名字对不上,程序就会报错。最后,我们将消息通知类注入到我们的测试类中,就可以测试消息通知了。
1 @controller 2 @requestmapping("/notifier") 3 public class notifiercontroller { 4 5 @autowired 6 private spittlenotifier spittlenotifier; 7 8 @requestmapping(value = "/test") 9 public string test() { 10 spittlenotifier.millionthspittleposted(); 11 return "index"; 12 } 13 14 }
加载mbean配置类
前面我们已经有了的远程mbean,消息通知mbean,但是当tomcat启动的时候,我们还需要去加载它们,不然这些mbean仍然是访问不了的。以往我们都在web.xml中去,现在我们通过web.xml的替代方案webinitializer来加载。
1 public class webinitializer extends abstractannotationconfigdispatcherservletinitializer { 2 3 @override 4 protected class<?>[] getrootconfigclasses() { 5 return new class<?>[] { }; 6 } 7 8 @override 9 protected class<?>[] getservletconfigclasses() { 10 return new class<?>[] { mbeanconfig.class, notifierconfig.class }; 11 } 12 13 @override 14 protected string[] getservletmappings() { 15 return new string[] { "/" }; 16 } 17 18 @override 19 protected void customizeregistration(dynamic registration) { 20 registration.setasyncsupported(true); 21 } 22 23 }
我们用这些代码替代原本要配在web.xml中配置的dispatcherservlet,然后再getservletconfigclasses方法中,去加载远程mbean,以及消息通知mbean。这样我们就可以访问mbean了。
可以在这里下载完整的代码:https://files.cnblogs.com/files/jkfd/springjmx-demo.zip