欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

20-Spring JMX

程序员文章站 2022-04-06 11:58:13
基本概念 JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。简介看上去不是很直观和明白,也可能我了解的太少 ......

基本概念

jmxjava management extensions,即java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。jmx可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。简介看上去不是很直观和明白,也可能我了解的太少,理解还不够深入。那么在本例中,主要是介绍通过使用spring jmx来管理和修改运行中的应用程序的配置。关于更多的jmx概念,大家可以自行搜索。

将spring bean导出为mbean

通过mbeanexporter将普通的spring bean导出为mbeanspring 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=spittlecontrollerspitter是管理域的名称,接下来就是key=value一个键值对,最终在jmx管理工具中,看到的mbean名字就是spittlecontroller。然后我们启动tomcat服务,就可以使用jconsole来查看和修改spittlecontroller这个mbean了。

20-Spring JMX

 通过jconsole,可以看到spittlecontroller的属性和方法。为了看到动态修改属性的效果,我们把pagesize改为50,然后可以通过浏览器,访问spittlecontrollertest方法所对应的url,也可以通过jconsole操作界面来调用test方法,来查看pagesize属性的变化。

20-Spring JMX

下面控制台中的pagesize值,是在jconsole中修改属性值前后的输出结果

20-Spring JMX

有时候我们希望通过程序,来访问和操作远程服务器端的mbean,这时候我们就需要创建和访问远程mbean了。

暴露远程mbean

使mbean成为远程对象的最简单方式是配置springconnectorserverfactorybean,并为其设置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 }

connectorserverfactorybeanserviceurl属性指明了通过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这个mbeanpagesize属性,该属性的首字母原本是小写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 }

notificationpublishersendnotification方法用于发送消息通知,接下来我们还需要建立一个消息监听器。接收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