Spring 与JMX

一、JMX

Spring对DI的支持是通过在应用中配置bean属性,这是一种非常不错的方法。不过,一旦应用已经部署并且正在运行,单独使用DI并不能帮助我们改变应用的配置。假设我们希望深入了解正在运行的应用并要在运行时改变应用的配置,此时,就可以使用Java管理扩展(JavaManage- ment Extensions,JMX)了。

JMX这项技术能够让我们管理、监视和配置应用。这项技术最初作为Java的独立扩展,从Java 5开始,JMX已经成为标准的组件。

使用JMX管理应用的核心组件是托管bean(managed bean,MBean)。所谓的MBean就是暴露特定方法的JavaBean,这些方法定义了管理接口。JMX规范定义了如下4种类型的MBean:

  • 标准MBean:标准MBean的管理接口是通过在固定的接口上执行反射确定的,bean类会实现这个接口;
  • 动态MBean:动态MBean的管理接口是在运行时通过调用DynamicMBean接口的方法来确定的。因为管理接口不是通过静态接口定义的,因此可以在运行时改变;
  • 开放MBean:开放MBean是一种特殊的动态MBean,其属性和方法只限定于原始类型、原始类型的包装类以及可以分解为原始类型或原始类型包装类的任意类型;
  • 模型MBean:模型MBean也是一种特殊的动态MBean,用于充当管理接口与受管资源的中介。模型Bean并不像它们所声明的那样来编写。它们通常通过工厂生成,工厂会使用元信息来组装管理接口。

Spring的JMX模块可以让我们将Spring bean导出为模型MBean,这样我们就可以查看应用程序的内部情况并且能够更改配置——甚至在应用的运行期。

二、将Spring bean导出为MBean

Spring的MBeanExporter是将Spring Bean转变为MBean的关键。MBeanExporter可以把一个或多个Spring bean导出为MBean服务器(MBean server)内的模型 MBean。MBean服务器(有时候也被称为MBean代理)是MBean生存的容器。对MBean的访问,也是通过MBean服务器来实现的。

如图所示,将Spring bean导出为JMX MBean之后,可以使用基于JMX的管理工具(例如JConsole或者VisualVM)查看正在运行的应用程序,显示bean的属性并调用bean的方法。
Spring 与JMX_第1张图片

下面的@Bean方法在Spring中声明了一个MBeanExporter,它会将spittleControllerbean导出为一个模型MBean:
Spring 与JMX_第2张图片
配置MBeanExporter的最简单方式是为它的beans属性配置一个Map集合,该集合中的元素是我们希望暴露为JMX MBean的一个或多个bean。每个Map条目的key就是MBean的名称(由管理域的名字和一个key-value对组成,在SpittleController MBean示例中是spitter:name=HomeController),而Map条目的值则是需要暴露的Spring bean引用。在这里,我们将输出spittleControllerbean,以便它的属性可以通过JMX在运行时进行管理。

通过MBeanExporter,spittleControllerbean将作为模型MBean以SpittleController的名称导出到MBean服务器中,以实现管理功能。下图展示了通过JConsole查看SpittleControllerMBean时的情况。

SpittleController所有的public成员都被导出为MBean的操作或属性。这可能并不是我们所希望看到的结果,我们真正需要的只是可以配置spittlesPerPage属性。我们不需要调用spittles()方法或SpittleController中的其他方法或属性。因此,我们需要一个方式来筛选所需要的属性或方法。

MBean服务器从何处而来

根据以上配置,MBeanExporter会假设它正在一个应用服务器中(例如Tomcat)或提供MBean服务器的其他上下文中运行。但是,如果Spring应用程序是独立的应用或运行的容器没有提供MBean服务器,我们就需要在Spring上下文中配置一个MBean服务器。

在XML配置中,元素可以为我们实现该功能。如果使用Java配置的话,我们需要更直接的方式,也就是配置类型为MBeanServerFactoryBean的bean(这也是在XML中元素所作的事情)。

MBeanServerFactoryBean会创建一个MBean服务器,并将其作为Spring应用上下文中的bean。默认情况下,这个bean的ID是mbeanServer。了解到这一点,我们就可以将它装配到MBeanExporter的server属性中用来指定MBean要暴露到哪个MBean服务器中。

2.1 通过名称暴露方法

MBean信息装配器(MBean info assembler)是限制哪些方法和属性将在MBean上暴露的关键。其中有一个MBean信息装配器是MethodNameBasedMBean-InfoAssembler。这个装配器指定了需要暴露为MBean操作的方法名称列表。
Spring 与JMX_第3张图片
managedMethods属性可以接受一个方法名称的列表,指定了哪些方法将暴露为MBean的操作。
Spring 与JMX_第4张图片

另一个基于方法名称的装配器是MethodExclusionMBeanInfoAssembler。这个MBean信息装
配器是MethodNameBaseMBeanInfoAssembler的反操作。它不是指定哪些方法需要暴露为MBean的托管操作,MethodExclusionMBeanInfoAssembler指定了不需要暴露为MBean托管操作的方法名称列表
Spring 与JMX_第5张图片

2.1 使用接口定义MBean的操作和属性

Spring的InterfaceBasedMBeanInfoAssembler是另一种MBean信息装配器,可以让我们通过使用接口来选择bean的哪些方法需要暴露为MBean的托管操作。InterfaceBasedMBeanInfoAssembler与基于方法名称的装配器很相似,只不过不再通过罗列方法名称来确定暴露哪些方法,而是通过列出接口来声明哪些方法需要暴露。

例如,假设我们定义了一个名为SpittleControllerManagedOperations的接口,如下所
示:
Spring 与JMX_第6张图片
在这里,我们选择了setSpittlesPerPage()方法和getSpittlesPerPage()方法作为需要暴露的方法。再次提醒,这一对存取器方法间接暴露了spittlesPerPage属性作为MBean的托管属性。为了应用此装配器,我们只需要使用如下的assemblerbean替换之前基于方法名称的装配器即可:
Spring 与JMX_第7张图片

2.3 使用注解驱动的MBean

Spring context配置命名空间中的元素。这个便捷的元素装配了MBean导出器以及为了在Spring启用注解驱动的MBean所需要的装配器。我们所需要做的就是使用它来替换我们之前所使用的MBeanExporterbean:
在这里插入图片描述
现在,要把任意一个Spring bean转变为MBean,我们所需要做的仅仅是使用@ManagedResource注解标注bean并使用@ManagedOperation或@ManagedAttribute注解标注bean的方法。例如,如下的程序清单展示了如何使用注解把SpittleController导出为MBean。
Spring 与JMX_第8张图片
在类级别使用了@ManagedResource注解来标识这个bean应该被导出为MBean。objectName属性标识了域(Spitter)和MBean的名称(SpittleController)。

们还可以使用@ManagedOperation注解替换@ManagedAttribute注解来标注存取器方法。如下所示:
Spring 与JMX_第9张图片

2.4 处理MBean冲突

如果MBean服务器中不存在与我们MBean名字相同的已注册的MBean,那我们的MBean注册时就不会有任何问题。但是如果名字冲突时,将会发生什么呢?
默认情况下,MBeanExporter将抛出InstanceAlreadyExistsException异常,该异常表明MBean服务器中已经存在相同名字的MBean。不过,我们可以通过MBeanExporter的registrationBehaviorName属性或者的registration属性指定冲突处理机制来改变默认行为。

Spring提供了3种借助registrationBehaviorName属性来处理MBean名字冲突的机制:

  • FAIL_ON_EXISTING:如果已存在相同名字的MBean,则失败(默认行为);
  • IGNORE_EXISTING:忽略冲突,同时也不注册新的MBean;
  • REPLACING_EXISTING:用新的MBean覆盖已存在的MBean;

例如,如果我们使用MBeanExporter,我们可以通过设置registration-BehaviorName属性为RegistrationPolicy.IGNORE_EXISTING来忽略冲突,如下所示:
Spring 与JMX_第10张图片
registrationBehaviorName属性可以接受RegistrationPolicy中所定义的枚举值,每一个取值分别对应3种冲突处理机制的一种。

三、远程MBean

3.1 暴露远程MBean

Spring 与JMX_第11张图片

3.2 访问远程MBean

要想访问远程MBean服务器,我们需要在Spring上下文中配MbeanServerConnectionFactoryBean。下面的bean声明装配了一个MbeanServerConnectionFactoryBean,
Spring 与JMX_第12张图片
顾名思义,MBeanServerConnectionFactoryBean是一个可用于创建MbeanServer-Connection的工厂bean。由MBeanServerConnectionFactoryBean所生成的MBeanServerConnection实际上是作为远程MBean服务器的本地代理。它能够以MBeanServerConnection的形式注入到其他bean的属性中:
Spring 与JMX_第13张图片
MBeanServerConnection提供了多种方法,我们可以使用这些方法查询远程MBean服务器并调用MBean服务器内所注册的MBean的方法。例如,如果我们希望知道在远程MBean服务器中有多少已注册的MBean,可以用如下的代码片段打印这些信息:
在这里插入图片描述

3.3 代理MBean

BcanProxyFactoryBean可以让我们可以直接访问远程的MBean(就如同配置在本地的其他bean一样)。
下图展示了它的工作原理。
Spring 与JMX_第14张图片
例如,考虑如下的MBeanProxyFactoryBean声明:
Spring 与JMX_第15张图片
objectName属性指定了远程MBean的对象名称。在这里是引用我们之前导出的SpittleControllerMBean。
server属性引用了MBeanServerConnection,通过它实现MBean所有通信的路由。在这里,我们注入了之前配置的MBeanServerConnectionFactoryBean。
最后,proxyInterface属性指定了代理需要实现的接口。
对于上面声明的remoteSpittleControllerMBean,我们现在可以把它注入到类型为SpittleControllerManagedOperations的bean属性中,并使用它来访问远程的MBean。这样,我们就可以调用setSpittlesPerPage()和getSpittlesPerPage()方法了。

四、处理通知

通过查询MBean获得信息只是查看应用状态的一种方法。但当应用发生重要事件时,如果希望能够及时告知我们,这通常不是最有效的方法。

例如,假设Spittr应用保存了已发布的Spittle数量,而我们希望知道每发布一百万Spittle时的精确时间(例如一百万、两百万、三百万等)。一种解决方法是编写代码定期查询数据库,计算Spittle的数量。但是执行这种查询会让应用和数据库都很繁忙,因为它需要不断的检查Spittle的数量。

与重复查询数据库获得Spittle的数量相比,更好的方式是当这类事件发生时让MBean通知我们。JMX通知(JMX notification,如下图 所示)是MBean与外部世界主动通信的一种方法,而不是等待外部应用对MBean进行查询以获得信息。
Spring 与JMX_第16张图片
Spring通过NotificationPublisherAware接口提供了发送通知的支持。任何希望发送通知的MBean都必须实现这个接口。
Spring 与JMX_第17张图片
正如我们所看到的,SpittleNotifierImpl实现了NotificationPublisherAware接口。这并不是一个要求苛刻的接口,它仅要求实现一个方法:setNotificationPublisher。
SpittleNotificationImpl也实现了SpittleNotifier接口的方法:millionthSpittlePosted()。这个方法使用了setNotificationPublisher()方法所注入的NotificationPublisher来发送通知:我们的Spittle数量又到了一个新的百万级别。
一旦sendNotification()方法被调用,就会发出通知。

4.1 监听通知

接收MBean通知的标准方法是实现javax.management.NotificationListener接口。例如,考虑一下PagingNotificationListener:
Spring 与JMX_第18张图片
PagingNotificationListener是一个典型的JMX通知监听器。当接收到通知时,将会调用handleNotification()方法处理通知。大概的逻辑可能是,PagingNotification-Listener的handleNotification()方法将向寻呼机或手机上发送消息来告知Spittle数量又到了一个新的百万级别(我把实际的实现留给读者自己完成)。
剩下的工作只需要使用MBeanExporter注册PagingNotificationListener:
Spring 与JMX_第19张图片
MBeanExporter的notificationListenerMappings属性用于在监听器和监听器所希望监听的MBean之间建立映射。在本示例中,我们建立了PagingNotificationListener来监听由SpittleNotifier MBean所发布的通知。

你可能感兴趣的:(Java)