20-Spring JMX

2018-10-06 08:06:05来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

基本概念

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了。

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

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

有时候我们希望通过程序,来访问和操作远程服务器端的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

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:Spring学习手札(三)理解IoC 拯救不开心

下一篇:java10.0.2配置环境变量