Spring的JMX支持提供了一些特性,使你能够简单透明地将你的Spring应用程序集成到一个JMX基础设施中去。
确切的讲,Spring的JMX支持提供了四种核心特性:
自动将任一Spring bean注册为JMX MBean
使用灵活的机制来控制bean的管理接口
通过远程的JSR-160连接器对外声明式暴露MBean
对本地和远程MBean资源的简单代理
这些特性被设计成不管是Spring还是JMX的接口和类都和你的应用程序组件不耦合。实际上,为了利用Spring的JMX特性,大部分应用程序的类都不必去关心Spring或JMX。
在Spring的JMX框架中,核心类是 MBeanExporter。这个类负责获取Spring的bean,并用一个JMX MBeanServer 类来注册它们。举个例子,考虑下面的类:
package org.springframework.jmx; public class JmxTestBean implements IJmxTestBean { private String name; private int age; private boolean isSuperman; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public void setName(String name) { this.name = name; } public String getName() { return name; } public int add(int x, int y) { return x + y; } public void dontExposeMe() { throw new RuntimeException(); } }
你只需要在配置文件里简单地配置一个 MBeanExporter 的实例,并以如下所示的方法将这个bean传入,就可以将这个bean的属性和方法作为一个MBean的属性和操作暴露出去。
<beans> <!-- this bean must not be lazily initialized if the exporting is to happen --> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false"> <property name="beans"> <map> <entry key="bean:name=testBean1" value-ref="testBean"/> </map> </property> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> </beans>
上面的配置片段中,与bean定义相关的是 exporter bean,属性 beans 是告诉类 MBeanExporter 必须将哪些bean输出到JMX的 MBeanServer 去。 缺省配置中,在 beans 中,Map 用到的每个条目的key被用做相应条目值所引用的bean的 ObjectName。 在 Section 20.4, “控制bean的 ObjectName ” 中描述的情况下,可以改变这个行为。
在这项配置中,testBean 这个bean在 ObjectName值为 bean:name=testBean1 的情况下作为MBean暴露出去的。缺省情况下,这个bean所有的 public 的属性都作为对应MBean的属性, 这个bean所有的 public 的方法(除了那些继承自类 Object 的方法)都作为对应MBean的操作暴露出去的。
上述配置是假定应用程序运行在一个(仅有一个)MBeanServer 运行的环境中的。 这种情况下,Spring会试着查找运行中的 MBeanServer 并用这个server(如果有的话)来注册自己的bean。 在一个拥有自己的 MBeanServer 的容器中(如Tomcat或IBM WebSphere),这种行为是非常有用。
然而,在一个单一的环境,或运行在一个没有提供任何 MBeanServer 的容器里的情况下,这种方法毫无用处。 要处理这类问题,你可以在配置文件里添加一个类 org.springframework.jmx.support.MBeanServerFactoryBean 的实例来声明创建一个 MBeanServer 的实例。你也可以通过设置类 MBeanExporter 的 server 属性的值来确保使用一个特殊的 MBeanServer, 这个 MBeanServer 值是由一个 MBeanServerFactoryBean 返回的;例如:
<beans>
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean"/>
<!--
this bean needs to be eagerly pre-instantiated in order for the exporting to occur;
this means that it must not be marked as lazily initialized
-->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="beans">
<map>
<entry key="bean:name=testBean1" value-ref="testBean"/>
</map>
</property>
<property name="server" ref="mbeanServer"/>
</bean>
<bean id="testBean" class="org.springframework.jmx.JmxTestBean">
<property name="name" value="TEST"/>
<property name="age" value="100"/>
</bean>
</beans>
这里,通过 MBeanServerFactoryBean 创建一个 MBeanServer 的实例,并通过属性server将这个实例提供给MBeanExporter。 在提供你自己的MBeanServer实例的时候,MBeanExporter 将不会去查找运行中的MBeanServer,而是使用这个提供的MBeanServer 实例。为了让它正确的工作,必须在你的类路径上有一个JMX的实现。
如果服务器没有指定,MBeanExporter会尝试自动检测运行中的MBeanServer。 这在大多数只有一个MBeanServer实例的环境中可以奏效,但当存在多个实例时,可能会选错服务器。 在这种情况下,应该用MBeanServer agentId来说明究竟用哪个实例:
<beans>
<bean id="mbeanServer" class="org.springframework.jmx.support.MBeanServerFactoryBean">
<!-- indicate to first look for a server -->
<property name="locateExistingServerIfPossible" value="true"/>
<!-- search the MbeanServer instance with the given agentId -->
<property name="agentId" value="<MBeanServer instance agentId>"/>
</bean>
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
<property name="server" ref="mbeanServer"/>
...
</bean>
</beans>
在某些平台/情况中,MBeanServer通过查询方法来获得有动态/未知的agentId, 这时应该用factory-method:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="server"> <!-- Custom MBeanServerLocator --> <bean class="platform.package.MBeanServerLocator" factory-method="locateMBeanServer"/> </property> ... </bean> </beans>
如果你用 MBeanExporter 来配置的一个bean,同时也配置了惰性初始化,那么 MBeanExporter 不会 破坏这个约定,将避免初始化相应的bean。 而是会给 MBeanServer 注册一个代理,推迟从容器中获取这个bean,直到在代理侧发生对它的第一次调用。
所有通过 MBeanExporter 输出,并已经是有效MBean的bean,会在没有Spring干涉的情况下向 MBeanServer 注册。 通过设置 autodetect 属性为 true,MBeanExporter能自动的发现MBean:
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="autodetect" value="true"/> </bean> <bean name="spring:mbean=true" class="org.springframework.jmx.export.TestDynamicMBean"/>
这里,被称做 spring:mbean=true 的bean已经是一个有效的MBean了,将由Spring自动的注册。 缺省情况下,为JMX注册而自动发现的bean有它们自己的名字,用 ObjectName 来设置。 在 Section 20.4, “控制bean的 ObjectName ” 章节里,更详细的描述了如何覆盖(override)这种行为。
考虑这样一个场景,一个Spring的 MBeanExporter 试着用 ObjectName 'bean:name=testBean1' 往一个 MBeanServer 里注册一个MBean。 如果之前已经有一个 MBean 实例在同一个 ObjectName 下注册了,则缺省的行为是失败(并抛出一个 InstanceAlreadyExistsException 异常)。
当向 MBeanServer 注册一个 MBean 的时候,可以控制发生哪些行为。 Spring对JMX的支持三种不同的注册行为,当注册进程找到一个已经在同一个 ObjectName 下注册过的MBean时,以此来控制注册行为。这些注册行为总结在下面的表中:
Table 20.1. 注册行为
注册行为 | 解释 |
---|---|
REGISTRATION_FAIL_ON_EXISTING | 这是缺省的注册行为。如果一个 MBean 实例已经在同一个 ObjectName 下进行注册了,则正在注册中的这个 MBean 将不会注册,同时抛出 InstanceAlreadyExistsException 异常,已经存在的 MBean 不会受到影响。 |
REGISTRATION_IGNORE_EXISTING | 如果一个 MBean 实例已经在同一个 ObjectName 下进行注册了,正在注册的 MBean 将 不 被注册,已经存在的 MBean 不受影响,不会有 Exception 抛出。 当多个应用程序想在一个共享 MBeanServer 中共享一个公共 MBean 时,这个行为很有用。 |
REGISTRATION_REPLACE_EXISTING | 如果一个 MBean 实例已经在同一个 ObjectName 下进行注册了,现存的已经注册过的MBean将被注销掉,新的 MBean 将代替原来的进行注册(新的 MBean 有效的替换之前的那个实例)。 |
上述各值(分别是 REGISTRATION_FAIL_ON_EXISTING、 REGISTRATION_IGNORE_EXISTING 和 REGISTRATION_REPLACE_EXISTING)作为常数定义在类 MBeanRegistrationSupport 中(类 MBeanExporter 继承自这个超类)。如果你想改变缺省注册行为,只需要在你的 MBeanExporter 的定义中简单的把属性 registrationBehaviorName 设置成这些值中的一个就可以了。
下面的例子说明了如何从缺省的注册行为改变为 REGISTRATION_REPLACE_EXISTING 行为。
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="bean:name=testBean1" value-ref="testBean"/> </map> </property> <property name="registrationBehaviorName" value="REGISTRATION_REPLACE_EXISTING"/> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> </beans>
在上一个例子中,并没有对bean的管理接口进行控制;每个bean的 所有 的 public属性和方法分别作为JMX的属性和操作来输出。 为了更细粒度的对那些输出bean的属性和方法进行控制,这些属性和方法实际是作为JMX的属性和操作输出的,Spring JMX提供了一个全面的可扩展的机制来控制你那些bean的管理接口。
在后台,MBeanExporter 委派接口 org.springframework.jmx.export.assembler.MBeanInfoAssembler 的一个实现,这个接口负责定义每个输出bean的管理接口。 类 org.springframework.jmx.export.assembler.SimpleReflectiveMBeanInfoAssembler 是它的缺省实现,简单的定义了一个管理接口,输出所有public的属性和方法(如你在前面的例子见到的那样)。 Spring为接口 MBeanInfoAssembler 提供了两个另外的实现,允许你用源代码级元数据或任意接口来控制产生管理接口。
类 MetadataMBeanInfoAssembler 使你可以用源码级元数据来定义bean的管理接口。 元数据的读取由接口 org.springframework.jmx.export.metadata.JmxAttributeSource 封装。 Spring JMX提供了这个接口的两种实现,即开即用: 类 org.springframework.jmx.export.metadata.AttributesJmxAttributeSource针对公用属性(Commons Attributes), 而类 org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource 针对JDK5.0注解。 为了功能正常,类 MetadataMBeanInfoAssembler必须 和接口 JmxAttributeSource 的一个实现一起配置(这里 没有 缺省)。 在接下来的例子中,我们会用到公用属性元数据的方法。
要对一个输出到JMX的bean作标记,应该用属性 ManagedResource 来注解这个bean类。 在使用公用属性元数据方法的情况下,要能在包 org.springframework.jmx.metadata 中 找到它。 你希望作为操作输出的每个方法必须用属性 ManagedOperation 打上标记, 你希望输出的每个属性必须用属性ManagedAttribute打上标记。 在标记属性的时候,可以忽略getter的注解,或忽略分别创建一个只写或只读属性的setter。
下例展示了用公用属性元数据对前文见过的类JmxTestBean进行标记的情况:
package org.springframework.jmx; /** * @@org.springframework.jmx.export.metadata.ManagedResource * (description="My Managed Bean", objectName="spring:bean=test", * log=true, logFile="jmx.log", currencyTimeLimit=15, persistPolicy="OnUpdate", * persistPeriod=200, persistLocation="foo", persistName="bar") */ public class JmxTestBean implements IJmxTestBean { private String name; private int age; /** * @@org.springframework.jmx.export.metadata.ManagedAttribute * (description="The Age Attribute", currencyTimeLimit=15) */ public int getAge() { return age; } public void setAge(int age) { this.age = age; } /** * @@org.springframework.jmx.export.metadata.ManagedAttribute * (description="The Name Attribute", currencyTimeLimit=20, * defaultValue="bar", persistPolicy="OnUpdate") */ public void setName(String name) { this.name = name; } /** * @@org.springframework.jmx.export.metadata.ManagedAttribute * (defaultValue="foo", persistPeriod=300) */ public String getName() { return name; } /** * @@org.springframework.jmx.export.metadata.ManagedOperation * (description="Add Two Numbers Together") */ public int add(int x, int y) { return x + y; } public void dontExposeMe() { throw new RuntimeException(); } }
这里你可以看到,用属性 ManagedResource 来标记类 JmxTestBean, 这个 ManagedResource 是用一系列属性来配置的。 这些属性用于配置由 MBeanExporter 产生的MBean的不同方面。 在后续章节 Section 20.3.4, “源代码级的元数据类型” 中,将有更详细的说明。
你将会注意到属性 age 和 name 是用属性 ManagedAttribute注解的, 但是在属性 age 这种情况下,只标记了getter。 这将使得这两个属性作为属性包括到管理接口中去,但属性 age 将是只读的。
最后,你会注意到方法 add(int, int) 是用 ManagedOperation 标记的, 而方法 dontExposeMe() 却不是。 这使得管理接口在使用 MetadataMBeanInfoAssembler 的时候只包含一个操作:add(int, int)。
下列代码显示了如何用 MetadataMBeanInfoAssembler 配置 MBeanExporter:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="bean:name=testBean1" value-ref="testBean"/> </map> </property> <property name="assembler" ref="assembler"/> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> <bean id="attributeSource" class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource"> <property name="attributes"> <bean class="org.springframework.metadata.commons.CommonsAttributes"/> </property> </bean> <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler"> <property name="attributeSource" ref="attributeSource"/> </bean> </beans>
这里你可以看到,用类 AttributesJmxAttributeSource 的一个实例来配置一个MetadataMBeanInfoAssembler bean,并通过装配属性将它传递给 MBeanExporter。如果Spring输出MBean是利用元数据驱动管理接口,则所有这些都是必需的。
为了激活JDK5.0注解,用它来进行管理接口定义,Spring提供了一套相当于Commons Attribut属性类的注解和一个策略接口 JmxAttributeSource 的实现类 AnnotationsJmxAttributeSource, 这个类允许 MBeanInfoAssembler 来读这些注解。
下例是一个用JDK5.0注解定义管理接口的bean:
package org.springframework.jmx; import org.springframework.jmx.export.annotation.ManagedResource; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedAttribute; @ManagedResource(objectName="bean:name=testBean4", description="My Managed Bean", log=true, logFile="jmx.log", currencyTimeLimit=15, persistPolicy="OnUpdate", persistPeriod=200, persistLocation="foo", persistName="bar") public class AnnotationTestBean implements IJmxTestBean { private String name; private int age; @ManagedAttribute(description="The Age Attribute", currencyTimeLimit=15) public int getAge() { return age; } public void setAge(int age) { this.age = age; } @ManagedAttribute(description="The Name Attribute", currencyTimeLimit=20, defaultValue="bar", persistPolicy="OnUpdate") public void setName(String name) { this.name = name; } @ManagedAttribute(defaultValue="foo", persistPeriod=300) public String getName() { return name; } @ManagedOperation(description="Add two numbers") @ManagedOperationParameters({ @ManagedOperationParameter(name = "x", description = "The first number"), @ManagedOperationParameter(name = "y", description = "The second number")}) public int add(int x, int y) { return x + y; } public void dontExposeMe() { throw new RuntimeException(); } }
如你所见,跟元数据定义的基本语法相比,改变很少。这个方法在后台启动的时候稍微有点慢,因为JDK5.0注解被转成了Commons Attributes使用的类。 但是,这仅只是一次性的消耗,JDK5.0注解对编译期的检查更有好处。
与上述被注解的类有关的XML配置如下所示:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="assembler" ref="assembler"/> <property name="namingStrategy" ref="namingStrategy"/> <property name="autodetect" value="true"/> </bean> <bean id="jmxAttributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/> <!-- will create management interface using annotation metadata --> <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler"> <property name="attributeSource" ref="jmxAttributeSource"/> </bean> <!-- will pick up ObjectName from annotation --> <bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy"> <property name="attributeSource" ref="jmxAttributeSource"/> </bean> <bean id="testBean" class="org.springframework.jmx.AnnotationTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> </beans>
在Spring JMX中,可以使用下列源码级的元数据类型:
Table 20.2. 源代码级的元数据类型
目的 | Commons Attributes属性 | JDK 5.0 注解 | 属性 / 注解类型 | |
---|---|---|---|---|
把 Class 所有的实例标记为由JMX管理的资源 | ManagedResource | @ManagedResource | 类 | |
把方法标记为JMX的操作 | ManagedOperation | @ManagedOperation | 方法 | |
把getter或setter标记为JMX的半个属性 | ManagedAttribute | @ManagedAttribute | 方法(仅 getters 和 setters) | |
定义描述操作参数 | ManagedOperationParameter | @ManagedOperationParameter 和 @ManagedOperationParameters | @ManagedOperationParameter 和 @ManagedOperationParameters | 方法 |
接下来的配置参数可以用于这些源码级的元数据类型:
Table 20.3. 源码级的元数据参数
参数 | 描述 | 适用于 |
---|---|---|
ObjectName | 由类 MetadataNamingStrategy 使用,决定一个管理资源的 ObjectName。 | ManagedResource |
description | 设置资源、属性或操作友好的描述 | ManagedResource、 ManagedAttribute、 ManagedOperation、 ManagedOperationParameter |
currencyTimeLimit | 描述符字段,用于设置 currencyTimeLimit 的值 | ManagedResource、ManagedAttribute |
defaultValue | 描述符字段,用于设置 defaultValue 的值 | ManagedAttribute |
log | 描述符字段,用于设置 log 的值 | ManagedResource |
logFile | 描述符字段,用于设置 logFile 的值 | ManagedResource |
persistPolicy | 描述符字段,用于设置 persistPolicy 的值 | ManagedResource |
persistPeriod | 描述符字段,用于设置 persistPeriod 的值 | ManagedResource |
persistLocation | 描述符字段,用于设置 persistLocation 的值 | ManagedResource |
persistName | 描述符字段,用于设置 persistName 的值 | ManagedResource |
name | 设置一个操作参数的显示名字 | ManagedOperationParameter |
index | 设置操作参数的索引 | ManagedOperationParameter |
为了进一步简化配置,Srping引入了接口 AutodetectCapableMBeanInfoAssembler , 它扩展接口 MBeanInfoAssembler,增加了对自动检测MBean资源的支持。 如果你用 AutodetectCapableMBeanInfoAssembler 的一个实例来配置 MBeanExporter,则允许对将要暴露给JMX的所有bean进行“表决”。
即开既用,MetadataMBeanInfoAssembler 是接口 AutodetectCapableMBeanInfo 唯一的实现, 它“表决”将所有被属性 ManagedResource 标记过的bean包含在内。 这种情况下,缺省的方法是用bean的名字作为 ObjectName,在配置中结果如下:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <!-- notice how no 'beans' are explicitly configured here --> <property name="autodetect" value="true"/> <property name="assembler" ref="assembler"/> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> <!-- (for Commons Attributes-based metadata) --> <bean id="attributeSource" class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource"> <property name="attributes"> <bean class="org.springframework.metadata.commons.CommonsAttributes"/> </property> </bean> <!-- (for Java5+ annotations-based metadata) --> <!-- <bean id="attributeSource" class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/> --> <bean id="assembler" class="org.springframework.jmx.export.assembler.MetadataMBeanInfoAssembler"> <property name="attributeSource" ref="attributeSource"/> </bean> </beans>
注意,在这个配置中,没有传给 MBeanExporter 任何bean;但是,JmxTestBean将仍被注册, 因为属性 ManagedResource 为它做了标记,并且 MetadataMBeanInfoAssembler 发现了这一点,“表决”包括了它。 这种方法唯一的问题是,JmxTestBean 的名字有商业含义。 要解决这个问题,你可以修改创建ObjectName 的缺省行为,按照 Section 20.4, “控制bean的 ObjectName ” 章节中所讲的那样去定义。
除了 MetadataMBeanInfoAssembler,Spring还有 InterfaceBasedMBeanInfoAssembler, 它允许你在一系列方法的基础上约束将要输出的方法和属性,这一系列方法是由一组接口来定义的。
虽然输出MBeans的标准机制是使用接口和一个简单的命名策略,InterfaceBasedMBeanInfoAssembler 去掉了命名约定的需要而扩展了这一功能,允许你使用一个以上的接口,并且去掉了为了bean去实现MBean接口的需要。
考虑这个接口,用它为前面见过的类 JmxTestBean 定义一个管理接口:
public interface IJmxTestBean { public int add(int x, int y); public long myOperation(); public int getAge(); public void setAge(int age); public void setName(String name); public String getName(); }
这个接口定义了方法和属性,它们将作为JMX MBean的操作和属性输出。下面的代码展示了如何配置Spring JMX,用这个接口作为管理接口的定义:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="bean:name=testBean5" value-ref="testBean"/> </map> </property> <property name="assembler"> <bean class="org.springframework.jmx.export.assembler.InterfaceBasedMBeanInfoAssembler"> <property name="managedInterfaces"> <value>org.springframework.jmx.IJmxTestBean</value> </property> </bean> </property> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> </beans>
你可以看到,在为任一bean构造管理接口时,InterfaceBasedMBeanInfoAssembler 被配成使用接口 IJmxTestBean。由 InterfaceBasedMBeanInfoAssembler 处理的bean是 不 需要实现那些用于生成JMX管理接口的接口的,明白这一点非常重要。
在上面的例子中,接口 IJmxTestBean 用于构造所有bean的所有管理接口。 许多情况下,并不想这样,你可能想对不同的bean用不同的接口。 这种情况下,你可以通过属性 interfaceMappings 传一个 Properties 的实例给 InterfaceBasedMBeanInfoAssembler, 在这里,每个实体的键都是bean的名字,每个实体的值就是用逗号隔开的用于那个bean的接口的名字列表。
如果既没有通过属性 managedInterfaces 又没有通过属性 interfaceMappings 指定管理接口,那么 InterfaceBasedMBeanInfoAssembler 将反射到bean上,使用所有被该bean实现的接口来创建管理接口。
MethodNameBasedMBeanInfoAssembler 允许你指定一个将作为属性和操作输出到JMX的方法的名字列表。下面的代码展示了一个这种情况的配置样例:
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="bean:name=testBean5" value-ref="testBean"/> </map> </property> <property name="assembler"> <bean class="org.springframework.jmx.export.assembler.MethodNameBasedMBeanInfoAssembler"> <property name="managedMethods"> <value>add,myOperation,getName,setName,getAge</value> </property> </bean> </property> </bean>
可以看到,方法 add 和 myOperation 将作为JMX的操作输出,getName()、setName(String) 和 getAge() 将作为JMX的半个属性输出。 在上面的代码中,方法映射用于那些输出给JMX的bean。 要在一个bean接一个bean的基础上控制方法的暴露,使用 MethodNameMBeanInfoAssembler 的属性 methodMappings 把bean的名字映射到方法名字的列表上。
在后台,MBeanExporter 委派 ObjectNamingStrategy 的一个实现去获取正在注册的每个bean的ObjectName。 缺省的实现是 KeyNamingStrategy,它缺省用 beans Map 的键作为 ObjectName。 此外,KeyNamingStrategy 能把beans Map 的键映射为一个 Properties 文件中的实体,以此来决定 ObjectName。 除了 KeyNamingStrategy 之外,Spring提供了另外两个 ObjectNamingStrategy 的实现: IdentityNamingStrategy 构造一个 ObjectName, 这是基于JVM识别的bean;MetadataNamingStrategy 是用源代码级元数据获取 ObjectName。
可以配置你自己 KeyNamingStrategy 实例,配置它从一个 Properties 的实例中读取 ObjectName,而不是用bean的键去读。 KeyNamingStrategy 会试着用与bean键相应的键在 Properties 中查找一个实体。 如果没有发现任何实体或是 Properties 实例为 null,就用这个bean的键。
下面代码展示了一个 KeyNamingStrategy 配置的例子:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="testBean" value-ref="testBean"/> </map> </property> <property name="namingStrategy" ref="namingStrategy"/> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> <bean id="namingStrategy" class="org.springframework.jmx.export.naming.KeyNamingStrategy"> <property name="mappings"> <props> <prop key="testBean">bean:name=testBean1</prop> </props> </property> <property name="mappingLocations"> <value>names1.properties,names2.properties</value> </property> </bean </beans>
用一个 Properties 的实例来配置一个 KeyNamingStrategy 的实例, 这个 Properties 的实例是由映射属性定义的 Properties 实例和由映射属性定义的路径中的属性文件的内容合并起来的。 这个配置中,给bean testBean 的 ObjectName 值为 bean:name=testBean1 , 因为这个实体在 Properties 的实例中,这个实例有一个与bean的键相对应的键。
如果在 Properties 实例中没有找到实体,则bean的键名将用作 ObjectName 的值。
MetadataNamingStrategy 使用每个bean属性 ManagedResource 的 ObjectName 属性来创建 ObjectName。 下列代码展示了 MetadataNamingStrategy 的配置:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="testBean" value-ref="testBean"/> </map> </property> <property name="namingStrategy" ref="namingStrategy"/> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> <bean id="namingStrategy" class="org.springframework.jmx.export.naming.MetadataNamingStrategy"> <property name="attributeSource" ref="attributeSource"/> </bean> <bean id="attributeSource" class="org.springframework.jmx.export.metadata.AttributesJmxAttributeSource"/> </beans>
对远程访问,Spring JMX模块在包 org.springframework.jmx.support 中提供了两种 FactoryBean 实现来创建服务器端和客户端连接器。
要创建一个Spring JMX,启动暴露一个JSR-60 JMXConnectorServer,用下面的配置:
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"/>
缺省情况下,ConnectorServerFactoryBean 创建一个与 "service:jmx:jmxmp://localhost:9875" 绑定的 JMXConnectorServer。 bean serverConnector 因此通过本机端口为9875上的JMXMP协议把本地的 MBeanServer暴露给客户端。 注意在JSR 160规范中,JMXMP是标记为可选的: 当前,主要的开源JMX实现,MX4J和由J2SE5.0提供的那个都 不 支持JMXMP。
要指定其他的URL并用 MBeanServer 来注册 JMXConnectorServer, 分别用 serviceUrl 和 ObjectName 属性:
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"> <property name="objectName" value="connector:name=rmi"/> <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/myconnector"/> </bean>
如果设置属性 ObjectName,Spring会在这个 ObjectName 下用MBeanServer自动注册连接器。 下面的例子展示了一整套参数,在创建一个JMXConnector时,你可以把它们传递给 ConnectorServerFactoryBean。
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"> <property name="objectName" value="connector:name=iiop"/> <property name="serviceUrl" value="service:jmx:iiop://localhost/jndi/iiop://localhost:900/myconnector"/> <property name="threaded" value="true"/> <property name="daemon" value="true"/> <property name="environment"> <map> <entry key="someKey" value="someValue"/> </map> </property> </bean>
注意,在使用基于RMI的连接器时,你需要启动查找服务(tnameserv或rmiregistry)来完成名字注册。 如果你用Spring通过RMI输出远程服务,那么Spring将已经创建了一个RMI注册。 如果没有,你可以用下面的配置很容易就启动一个注册:
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean"> <property name="port" value="1099"/> </bean>
要创建一个 MBeanServerConnection 到远程, JSR-160用 MBeanServerConnectionFactoryBean 激活了 MBeanServer , 如下面所示:
<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean"> <property name="serviceUrl" value="service:jmx:rmi://localhost:9875"/> </bean>
JSR-160允许扩展客户端和服务器端之间的通信方式。上面的例子使用了强制的基于RMI的实现和JRMP(可选的), 这是由JSR-160规范(IIOP和JRMP)所要求的。通过使用其他的提供商或JMX实现(如 MX4J), 你可以从基于简单HTTP或SSL的诸如SOAP、Hessian、Burlap协议中获益。
<bean id="serverConnector" class="org.springframework.jmx.support.ConnectorServerFactoryBean"> <property name="objectName" value="connector:name=burlap"/> <property name="serviceUrl" value="service:jmx:burlap://localhost:9874"/> </bean>
在上面的例子中,使用了MX4J3.0.0,更多的信息参见官方的MX4J文档。
Spring JMX允许你创建代理,这个代理改变到注册到本地或远程 MBeanServer 的MBean的调用。 这些代理提供里一个标准的Java接口,通过它,你可以和MBean相合。 下面的代码展示了如何为一个运行在本地 MBeanServer 的配置一个代理:
<bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean"> <property name="objectName" value="bean:name=testBean"/> <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/> </bean>
可以看到在 ObjectName:bean:name=testBean 下为注册的MBean创建了一个代理。 代理要实现的接口由属性 proxyInterfaces 和将这些接口上的方法和属性映射到MBean的操作和属性上的规则来控制,这些规则和 InterfaceBasedMBeanInfoAssembler 使用的规则是一样的。
MBeanProxyFactoryBean 能创建一个到任何MBean的代理,可以通过一个 MBeanServerConnection来访问。 缺省情况下,查找和使用本地 MBeanServer,但是你可以重写(override)它, 提供一个指向远程MBeanServer的MBeanServerConnection到指向远程MBean的代理。
<bean id="clientConnector" class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean"> <property name="serviceUrl" value="service:jmx:rmi://remotehost:9875"/> </bean> <bean id="proxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean"> <property name="objectName" value="bean:name=testBean"/> <property name="proxyInterface" value="org.springframework.jmx.IJmxTestBean"/> <property name="server" ref="clientConnector"/> </bean>
你可以看到,我们创建了一个 MBeanServerConnection,它指向一个远程的, 使用了 MBeanServerConnectionFactoryBean 的机器。然后通过属性 server 将这个 MBeanServerConnection 传给 MBeanProxyFactoryBean 。 创建的代理将通过这个 MBeanServerConnection 转发所有到 MBeanServer的调用。
Spring的JMX提供的内容包括了对JMX通知的全面的支持。
Spring的JMX支持使得用任意数量的MBean(这包括由Spring的 MBeanExporter 输出的MBean和通过其他机制注册的MBean)注册任意数量的 NotificationListeners非常容易。 例子最能阐明影响 NotificationListeners的注册有多么简单。 考虑一个场景,任何时候一个目标MBean的属性改变了,每个都会得到通知(通过一个Notification)。
package com.example; import javax.management.AttributeChangeNotification; import javax.management.Notification; import javax.management.NotificationFilter; import javax.management.NotificationListener; public class ConsoleLoggingNotificationListener implements NotificationListener, NotificationFilter { public void handleNotification(Notification notification, Object handback) { System.out.println(notification); System.out.println(handback); } public boolean isNotificationEnabled(Notification notification) { return AttributeChangeNotification.class.isAssignableFrom(notification.getClass()); } }
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="bean:name=testBean1" value-ref="testBean"/> </map> </property> <property name="notificationListenerMappings"> <map> <entry key="bean:name=testBean1"> <bean class="com.example.ConsoleLoggingNotificationListener"/> </entry> </map> </property> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> </beans>
有了上面的配置,每次来自目标MBean(bean:name=testBean1)的一个JMX的 Notification 都会被广播, 通过属性 notificationListenerMappings 注册的作为监听器的 ConsoleLoggingNotificationListener bean将得到通知。 然后 ConsoleLoggingNotificationListener bean可以采取任何它认为合适的行动来响应这个 Notification。
如果想给所有的正在输出的已经装入MBeanExporter的bean注册单个 NotificationListener实例,可以用特殊的通配符'*'(没有引号) 作为属性映射notificationListenerMappings的一个实体的键,例如:
<property name="notificationListenerMappings"> <map> <entry key="*"> <bean class="com.example.ConsoleLoggingNotificationListener"/> </entry> </map> </property>
如果想与上面相反(即,为一个MBean注册多个不同的监听器),那么就必须使用 notificationListeners 这个列表属性来代替(优先于属性notificationListenerMappings)。 这次,要配置多个 NotificationListenerBean 实例,而不是简单的为单个MBean配置一个 NotificationListener……一个 NotificationListenerBean 封装了一个 NotificationListener 和 ObjectName(或ObjectNames) 这样,它就在一个 MBeanServer 里进行注册。 NotificationListenerBean 也封装了许多其他的属性, 如NotificationFilter,可以用于高级JMX通知场景的任意用于的回传对象等。
使用 NotificationListenerBean 实例的时的配置跟之前的配置并没有很大的不同:
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="bean:name=testBean1" value-ref="testBean"/> </map> </property> <property name="notificationListeners"> <list> <bean class="org.springframework.jmx.export.NotificationListenerBean"> <constructor-arg> <bean class="com.example.ConsoleLoggingNotificationListener"/> </constructor-arg> <property name="mappedObjectNames"> <list> <bean class="javax.management.ObjectName"> <constructor-arg value="bean:name=testBean1"/> </bean> </list> </property> </bean> </list> </property> </bean> <bean id="testBean" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> </beans>
上面的例子跟第一个通知的例子差不多。假设每次产生一个 Notification 我们都想得到一个回传对象(handback object), 此外,我们想通过提供一个 NotificationFilter 来过滤掉无关的Notifications。 (要全面的讨论什么是一个回传对象,NotificationFilter 实际上是什么,请参考JMX规范(1.2)相应的章节 'The JMX Notification Model'。)
<beans> <bean id="exporter" class="org.springframework.jmx.export.MBeanExporter"> <property name="beans"> <map> <entry key="bean:name=testBean1" value-ref="testBean1"/> <entry key="bean:name=testBean2" value-ref="testBean2"/> </map> </property> <property name="notificationListeners"> <list> <bean class="org.springframework.jmx.export.NotificationListenerBean"> <constructor-arg ref="customerNotificationListener"/> <property name="mappedObjectNames"> <list> <!-- let's handle notifications from two distinct MBeans --> <bean class="javax.management.ObjectName"> <constructor-arg value="bean:name=testBean1"/> </bean> <bean class="javax.management.ObjectName"> <constructor-arg value="bean:name=testBean2"/> </bean> </list> </property> <property name="handback"> <bean class="java.lang.String"> <constructor-arg value="This could be anything..."/> </bean> </property> <property name="notificationFilter" ref="customerNotificationListener"/> </bean> </list> </property> </bean> <!-- implements both the 'NotificationListener' and 'NotificationFilter' interfaces --> <bean id="customerNotificationListener" class="com.example.ConsoleLoggingNotificationListener"/> <bean id="testBean1" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="TEST"/> <property name="age" value="100"/> </bean> <bean id="testBean2" class="org.springframework.jmx.JmxTestBean"> <property name="name" value="ANOTHER TEST"/> <property name="age" value="200"/> </bean> </beans>
Spring不只提供接受 Notifications 的注册,还提供发布 Notifications。
Note | |
---|---|
请注意这一章只是与Spring的那些通过 MBeanExporter 所谓MBean输出的管理bean有关系;任何现存的,用户定义的MBean必须使用标准的JMX API来发布通知。 |
在Spring的JMX通知发布支持中,关键的是 NotificationPublisher 接口 (在包 org.springframework.jmx.export.notification 中定义的)。 任一将要通过 MBeanExporter 作为MBean输出的bean实例都可以实现对应的 NotificationPublisherAware接口来获取对接口 NotificationPublisher 的访问。 NotificationPublisherAware 接口只提供了一个 NotificationPublisher 的实例通过简单的setter方法实现bean,这样bean就可以用于发布 Notifications。
如同在Javadoc中对类 NotificationPublisher 的描述,通过 NotificationPublisher 机制发布事件的管理bean 不 负责 任何通知监听器以及诸如此类……的状态管理。Spring的JMX支持谨慎的处理所有的JMX架构问题。应用程序开发者所需要的 就是实现接口 NotificationPublisherAware,用提供的接口 NotificationPublisher 开始发布事件。 注意,在已经用一个 MBeanServer 将管理bean注册之后, 再设置NotificationPublisher。
用 NotificationPublisher 的实例非常简单的…… 仅仅创建一个 Notification 实例 (或一个适当的 Notification 子类的实例),带有与事件相关联数据的通知将被发布, 然后在 NotificationPublisher 实例上调用sendNotification(Notification), 在Notification中传递它。
让我们来看一个简单的例子……在下面的场景里,每当操作 add(int, int) 被调用时, 输出的 JmxTestBean 实例将发布一个 NotificationEvent。
package org.springframework.jmx;
import org.springframework.jmx.export.notification.NotificationPublisherAware;
import org.springframework.jmx.export.notification.NotificationPublisher;
import javax.management.Notification;
public class JmxTestBean implements IJmxTestBean, NotificationPublisherAware {
private String name;
private int age;
private boolean isSuperman;
private NotificationPublisher publisher;
// other getters and setters omitted for clarity
public int add(int x, int y) {
int answer = x + y;
this.publisher.sendNotification(new Notification("add", this, 0));
return answer;
}
public void dontExposeMe() {
throw new RuntimeException();
}
public void setNotificationPublisher(NotificationPublisher notificationPublisher) {
this.publisher = notificationPublisher;
}
}
Spring的JMX提供的接口 NotificationPublisher 和让其工作的辅助装置是一个非常好的特性。 的确,伴随而来的是你的类与Spring和JMX的耦合开销,跟平时一样,这里的建议也是比较实际的, 如果你需要 NotificationPublisher 提供的功能,并且你可以接受与Spring和JMX的耦合,那么就去做。
这部分包含了更多的关于JMX的资源链接:
JMX 规范(JSR-000003)
JMX Remote API 规范(JSR-000160)
MX4J 主页(一个开源JMX规范的实现)
Getting Started with JMX - Sun的一篇介绍JMX的文章