基本概念 JMX(Java Management Extensions,即Java管理擴展)是一個為應用程式、設備、系統等植入管理功能的框架。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