Spring对DI的支持是通过在应用中配置Bean属性,如果应用已经部署并且正在运行,单独使用DI并不改变应用的配置。对于正在运行的应用并要在运行时改变应用的配置,可以使用java管理扩展(Java Management Extension
,即JMX
)。JMX管理应用的重要组件是MBean服务器(MBean Server
)和MBean(Managed bean
,译为托管bean),MBean服务器
充当着MBean的容器和注册表。MBean
是暴露特定方法的JavaBean,这些特定方法定义了管理接口。JMX规范定义了四种类型的MBean:
MBean类型 | 英文全称 | 描述 |
---|---|---|
标准MBean | Standard MBean | 标准MBean的管理接口是通过在固定的接口上执行反射确定的,bean类会实现这个接口 |
动态MBean | Dynamic MBean | 动态MBean的管理接口是在运行时通过调用DynamicMBean接口的方法来确定的。因为管理接口不是通过静态接口定义的,因此可以在运行时改变 |
开放MBean | Open MBean | 开放MBean是一种特殊的Dynamic MBean,其属性和方法只限定于原始类型,原始类型的包装类以及可以分解为原始类型或者原始类型包装类的任意类型 |
模型MBean | Model MBean | 模型MBean也可以是一种特殊的Dynamic MBean,用于充当管理接口与受管理资源的中介。模型Bean并不像他们所声明的那样来编写。他们通过工厂生成,工厂会使用元信息来组装管理接口 |
具体可以查看文章【JMX】-----JMX概述。
Spring的JMX模块可以将Spring bean导出为模型MBean
,此时可以查看应用程序内部情况并且更改配置,甚至是在应用程序的运行期。以下将介绍通过Spring JMX管理Spring应用上下文中的bean。
Spring的MBeanExporter
是将Spring Bean转变为MBean的关键。MBeanExporter
可以把一个或者多个Spring bean导出为MBean服务器(MBean Server
)内的模型MBean
,MBean服务器是MBean生存的容器和注册表。对MBean访问是通过MBean服务器来实现的。下面编写一个简单的Spring bean,并通过MBeanExporter导出为MBean。
【步骤一】:编写一个Spring bean。
public class SpittleController {
public static final int DEFAULT_SPITTLES_PER_PAGE=25;
private int spittlesPerPage = DEFAULT_SPITTLES_PER_PAGE;
public int getSpittlesPerPage() {
return spittlesPerPage;
}
public void setSpittlesPerPage(int spittlesPerPage) {
this.spittlesPerPage = spittlesPerPage;
}
@RequestMapping("/home")
public String home() {
String result = "spittlesPerPage--"+spittlesPerPage;
System.out.println(result);
return result;
}
}
【步骤二】:使用Java类配置,通过MBeanExporter将Spring bean导出为MBean。
@Configuration
public class JavaConfig {
@Bean
public MBeanExporter mbeanExporter(SpittleController spittleController) {
MBeanExporter exporter = new MBeanExporter();
Map<String,Object> beans = new HashMap<>();
//每个MBean有唯一的ObjectName名称;格式是域名:key-value
beans.put("spitter:name=SpittleController", spittleController);
exporter.setBeans(beans);
return exporter;
}
}
【步骤三】:SpringBoot项目的启动类。
@SpringBootApplication
@ComponentScan(basePackages = "com.corp")
//@ImportResource("classpath:applicationContext.xml")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
【步骤四】:使用jdk自带工具jconsole访问MBean。
jconsole
工具的的位置是D:\jdk1.8\bin
,新建连接,本地进程访问MBean。
通过jsoncole
工具修改属性SpittlesPerPage的值,页面访问http://localhost:8080/home
时,后台打印SpittlesPerPage值变成了30。这意味着在程序运行时,可以修改MBean公开的属性和方法。
spittlesPerPage--30
从jconsole
工具类上可以看到SpittleController
所有的public成员都被导出为MBean的操作或者属性。实际上真正需要的公开的是属性spittlesPerPage
,不需要公开SpittleController
中的其他方法或属性,如home()
方法。因此为了对MBean的属性和操作进行更细粒度的控制,Spring提供了几种选择。包括:
上面使用MBeanExporter
简单地将一个Spring Bean导出为MBean,MBeanExporter
需要运行一个应用服务器上(例如Tomcat
)或者提供MBean服务器的其他上下文中运行。但是如果Spring应用程序是独立的应用或者运行的容器是没有提供MBean服务器,需要在Spring上下文中配置一个MBean服务器。
如果使用的是XML配置,可以使用
元素实现此功能。如果使用的是java类配置,可以配置MBeanServerFactoryBean
的bean。MBeanServerFactoryBean
会创建一个MBean服务器,并将其作为Spring应用上下文中的bean。默认情况下,这个bean的ID是mbeanServer
,可以将它装配到MBeanExporter
的server
属性中用来指定MBean要暴露到哪个MBean服务器中。
@Bean
public MBeanServerFactoryBean mbeanServer() {
MBeanServerFactoryBean mbeanServer = new MBeanServerFactoryBean();
//search for the MBeanServer instance with the given agentId
mbeanServer.setAgentId("MBeanServer_instance_agentID");
//indicate to first look for a server
mbeanServer.setLocateExistingServerIfPossible(true);
return mbeanServer;
}
———通过名称来声明需要暴露或者忽略的bean方法。
MBean信息装配器(MBeanInfoAssembler
)是限制哪些方法和属性将在MBean上暴露的关键,MBeanInfoAssembler
其中一个是实现类MethodNameBasedMBeanInfoAssembler
,这个装配器指定了需要暴露为MBean操作的方法名称列表。在JavaBean中,属性的存取值是通过setter
和getter
方法实现的(其中setter表示可写,getter表示可读)。因此为了公开spittlesPerPage属性,需要在MethodNameBasedMBeanInfoAssembler
中配置属性对应存取方法名称。
@Configuraion
public class JavaConfig {
public MethodNameBasedMBeanInfoAssembler assembler() {
MethodNameBasedMBeanInfoAssembler assembler = new MethodNameBasedMBeanInfoAssembler();
//配置spittlePerPage属性的对应的存取方法的名称
assembler.setManagedMethods(new String[] {"getSpittlesPerPage", "setSpittlesPerPage"});
return assembler;
}
@Bean
public MBeanExporter mbeanExporter(SpittleController spittleController, MBeanInfoAssembler assembler) {
MBeanExporter exporter = new MBeanExporter();
Map<String, Object> beans = new HashMap<String, Object>();
beans.put("spitter:name=SpittleController", spittleController);
exporter.setBeans(beans);
exporter.setAssembler(assembler);
return exporter;
}
}
其中managedMethods
属性可以接收一个方法名称的列表,指定哪些方法将暴露为MBean的操作。本例所配置的是spittlePerPage属性的存取方法,所以spittlesPerPage属性成为了Mbean的托管属性。
另外一个基于方法名称的装配器是MethodExclusionMBeanInfoAssembler
,这个装配器与MethodNameBaseMBeanInfoAssembler
操作相反。它不是指定了哪些方法需要暴露为MBean的托管操作,而是指定了不需要暴露为MBean托管操作的方法名称列表。如不需要公开SpittleController中的home()方法,可以配置为如下:
@Configuration
public class JavaConfig {
@Bean
public MethodExclusionMBeanInfoAssembler assembler() {
MethodExclusionMBeanInfoAssembler assembler = new MethodExclusionMBeanInfoAssembler();
assembler.setIgnoredMethods(new String[] {"home"});
return assembler;
}
}
使用jconsole
工具访问MBean,结果如下:
缺点:基于方法名称的装配器,当需要把多个Spring bean导出为MBean时,装配器配置的方法名称清单将会变的非常庞大,因此可以通过使用专门接口暴露MBean的操作和属性。
———通过为bean增加接口来选择要暴露的方法。
Spring的InterfaceBasedMBeanInfoAssembler
是另外一种MBeanInfoAssembler
接口的实现,此类可以通过使用接口来选择bean的哪些方法需要暴露为MBean的托管操作。InterfaceBasedMBeanInfoAssembler
与基于方法名称的装配器MethodNameBasedMBeanInfoAssembler
相似,只不过不再通过罗列方法名称来确定暴露哪些方法,而是通过列出接口来声明哪些方法需要暴露。
public interface SpittleControllerManagedOperations {
public int getSpittlesPerPage();
public void setSpittlesPerPage(int spittlesPerPage);
}
@Configuration
public class JavaConfig {
@Bean
public MBeanExporter mbeanExporter(SpittleController spittleController,MBeanInfoAssembler assembler) {
MBeanExporter exporter = new MBeanExporter();
Map<String,Object> beans = new HashMap<>();
beans.put("spitter:name=SpittleController", spittleController);
exporter.setAssembler(assembler);
exporter.setBeans(beans);
return exporter;
}
//基于接口进行暴露
@Bean
public InterfaceBasedMBeanInfoAssembler assembler() {
InterfaceBasedMBeanInfoAssembler assembler = new InterfaceBasedMBeanInfoAssembler();
assembler.setManagedInterfaces(new Class<?>[]{SpittleControllerManagedOperations.class});
return assembler;
}
}
上面使用java类配置了一个基于接口名称的装配器InterfaceBasedMBeanInfoAssembler
,其managedInterfaces
属性接收一个或者多个接口组成的列表作为MBean的管理接口,本例中配置的是SpittleControllerManagedOperations
接口。SpittleController
不需要实现接口SpittleControllerManagedOperations
,此接口只是为了标识导出的内容。但在MBean和实现类之间有一个一致的协议,因此SpittleController
应该要实现SpittleControllerManagedOperations
。通过接口来选择MBean操作,可以将需要很多方法放在少量接口中,从而确保InterfaceBasedMBeanInfoAssembler
的配置尽量简洁。输出多个MBean时,基于接口的方法可以帮助保持Spring配置的简洁。
使用jconsole
工具访问MBean,结果如下:
缺点:托管操作的声明是一种重复,在接口或者spring上下文中声明的方法名称与实现中所声明的方法名称存在重复,重复的原因仅仅是为了满足MBeanExporter
的需要产生。可以通过java注解的方法来消除重复。
———通过注解标注bean来标识托管的属性和操作。
除了以上的MBeanInfoAssembler
实现类,Spring还提供了另外一个MBeanInfoAssmebler
实现类MetadataMBeanInfoAssembler
,此类可以使用注解标识哪些bean的方法需要暴露为MBean的托管操作和属性,配置此类非常复杂。可以使用Spring context配置命名空间中
元素,这个便捷的元素装配了MBeanExporter以及为了在Spring启用注解驱动的MBean所需要的装配器。或者使用注解@EnableMBeanExport
。
现在要将任意一个Spring bean转变为MBean,需要做的是使用@ManagedResource
注解标注bean并使用@ManagedOperation
或者@ManaedAttribute
注解标注bean的方法。并且需要在类上使用@ManagedResource
注解标识这个bean应该导出为MBean,ObjectName
属性标识了域名和MBean的名称。需要注意的是使用@ManagedOperation
注解标注方法是严格限制方法的,并不会把它作为JavaBean的存取方法(即setter和getter)。因此@ManagedOperation
可以用来把bean的方法暴露为MBean托管操作,而使用@ManagedAttribute
可以把bean的属性暴露为MBean托管属性。
注解 | 注解类型 | 描述 |
---|---|---|
@ManagedResource | 类级别 | 将类的所有是实例标记为JMX的托管资源 |
@ManagedOperation | 方法级别 | 将方法标记为JMX操作 |
@ManagedAttribute | 方法(仅settter和getter方法) | 将setter或者getter方法标记为属性的可写或可读操作 |
@ManagedOperationParameter @ManagedOperationParameters | 方法 | 定义操作参数的描述 |
@Controller
@ManagedResource(objectName ="spitter:name=SpittleController")
// @EnableMBeanExport(registration=RegistrationPolicy.FAIL_ON_EXISTING,)
public class SpittleController {
public static final int DEFAULT_SPITTLES_PER_PAGE = 25;
private int spittlesPerPage = DEFAULT_SPITTLES_PER_PAGE;
@ManagedAttribute
public int getSpittlesPerPage() {
return spittlesPerPage;
}
@ManagedAttribute
public void setSpittlesPerPage(int spittlesPerPage) {
this.spittlesPerPage = spittlesPerPage;
}
@ManagedOperation
public int printSpittlesPerPage() {
System.out.println("spittlesPerPage--" + spittlesPerPage);
return spittlesPerPage;
}
}
上面可以看到将Spring bean导出为MBean时,需要指定一个有管理域名和key-value组成的ObjectName
,如果MBean Server中不存在与将要注册的MBean名字相同的已注册的MBean,此次要注册的MBean不会出现问题。如果名字存在冲突,默认情况下,MBeanExporter
将会抛出InstanceAlreadyExistsException
异常,该异常表明MBean服务器中已经存在相同名字的MBean。可以通过MBeanExporter
的RegistrationPolicy
属性或者
的registration
属性(注解@EnableMBeanExport
的registration
属性)来指定冲突解决机制改变默认行为。
Spring提供3种借助RegistrationPolicy
属性来处理MBean名字冲突的机制:
解决机制 | 描述 |
---|---|
FAIL_ON_EXISTING | 如果已存在相同名字的MBean,则失败(默认行为) |
IGNORE_EXISTING | 忽略冲突,同时也不注册新的MBean |
REPLACE_EXISTING | 用新的MBean覆盖已存在的MBean |
例如下面Java类配置中将MBeanExporter的属性RegistrationPolicy
设置为RegistrationPolicy.FAIL_ON_EXISTING
,或者通过注解@EnableMBeanExport
,设置属性registration,当要注册的MBean的ObjectName名称已经存在时,将会失败。
@Configuration
//@EnableMBeanExport(registration=RegistrationPolicy.FAIL_ON_EXISTING)
public class JavaConfig {
@Bean
public MBeanExporter mbeanExporter(SpittleController3 spittleController) {
MBeanExporter exporter = new MBeanExporter();
Map<String, Object> beans = new HashMap<String, Object>();
beans.put("spitter:name=SpittleController", spittleController);
exporter.setBeans(beans);
exporter.setRegistrationPolicy(RegistrationPolicy.FAIL_ON_EXISTING);
return exporter;
}
}
1.Spring官方文档
2.《Spring IN ACTION》
3.《JMX IN ACTION》