这篇文章主要对 Instrumentation Level
提供的组件有个基本认识~本文绝大多数理论支撑来自于《JMX Specification 1.4》规范
MBean
MBean是提供资源的对象称呼,它一共有四种类型:
- Standard MBean
- Dynamic MBean
- Open MBean
- Model MBean
这里会主要讲一下 Standard MBean
、 Dynamic MBean
以及 MXBean
。因为这几个比较常用一些,其他的影后如果有用到再加吧😋~
Standard MBean
Standard MBean
通过定义接口将可管理的资源暴露出去。 Standard MBean
的创建规则必须要遵守以下两个:
- 创建接口
SomethingMBean
- 实现步骤一的接口,类名必须是
Something
其次接口里的各个方法定义遵循JavaBeans component model
的命名规则:
- 可以读的属性,需要定义带前缀
get
的方法,且必须要有方法返回值。比如String getUsername()
- 可以写的属性,需要定义带前缀
set
的方法,且有且仅有一个方法参数。比如void setUsername(String)
- 除上面以外的方法,都视为可执行的操作方法
除此之外,还有其他的一些资源数据暴露规则:
- 公有的构造方法会暴露出去
- 广播出去的
Notification
对象会暴露出去The management interface of a standard MBean is composed of: ■ Its constructors: only the public constructors of the MBean class are exposed ■ Its attributes: the properties that are exposed through getter and setter methods ■ Its operations: the remaining methods exposed in the MBean interface ■ Its notifications: the notification objects and types that the MBean broadcasts ——FROM《JMX Specification 1.4》
既然不同的资源有不同的接口定义,那 MBean Server
是怎么加载/暴露各种各样的资源数据的?
The process of inspecting the MBean interface and applying these design patterns is called introspection. The JMX agent uses introspection to look at the methods and superclasses of a class, determine if it represents an MBean that follows the design patterns, and recognize the names of both attributes and operations. ——FROM《JMX Specification 1.4》
规范手册里提到使用一个 introspection
的方法来处理 MBean
,从而获取各种各样的关于 MBean
的信息。笔者在 com.sun.jmx.interceptor.DefaultMBeanServerInterceptor
里找到了解析痕迹:
private ObjectInstance registerObject(String classname,
Object object, ObjectName name)
throws InstanceAlreadyExistsException,
MBeanRegistrationException,
NotCompliantMBeanException {
if (object == null) {
final RuntimeException wrapped =
new IllegalArgumentException("Cannot add null object");
throw new RuntimeOperationsException(wrapped,
"Exception occurred trying to register the MBean");
}
// 在registerObject()的时候会解析MBean
DynamicMBean mbean = Introspector.makeDynamicMBean(object);
return registerDynamicMBean(classname, mbean, name);
}
追踪下去后可以在 com.sun.jmx.mbeanserver.MBeanSupport
里发现 MBeanInfo
的创建过程:
<T> MBeanSupport(T resource, Class<T> mbeanInterfaceType)
throws NotCompliantMBeanException {
if (mbeanInterfaceType == null)
throw new NotCompliantMBeanException("Null MBean interface");
if (!mbeanInterfaceType.isInstance(resource)) {
final String msg =
"Resource class " + resource.getClass().getName() +
" is not an instance of " + mbeanInterfaceType.getName();
throw new NotCompliantMBeanException(msg);
}
ReflectUtil.checkPackageAccess(mbeanInterfaceType);
this.resource = resource;
MBeanIntrospector<M> introspector = getMBeanIntrospector();
this.perInterface = introspector.getPerInterface(mbeanInterfaceType);
// 构建MBeanInfo,这个会暴露给Manager看
this.mbeanInfo = introspector.getMBeanInfo(resource, perInterface);
}
Standard MBean
的好处就是方便易用;但是如果要暴露复合类型的话,在 Manager Client
端上会无法获取复合类型对象的详细信息(因为客户端没有这个复合类型的字节文件)。
MemoryUsage is a model-specific class. With Standard MBeans, a client of the MBean Server cannot access the Usage attribute if it does not know the class MemoryUsage. Suppose the client is a generic console based on JMX technology. Then the console would have to be configured with the model-specific classes of every application it might connect to. The problem is even worse for clients that are not written in the Java language. Then there may not be any way to tell the client what a MemoryUsage looks like. ——MXBean.java
Standard MBean
的使用可以看这里:
MXBean
这货可以说是进阶版的 MBean
,它相比 Standard MBean
使用 CompositeData
数据结构来表示复合类型数据,这种数据结构可以在各种客户端上实现和表示。所以有了它就能解决 Standard MBean
无法暴露复合类型的资源问题。
<T> MBeanSupport(T resource, Class<T> mbeanInterfaceType)
throws NotCompliantMBeanException {
if (mbeanInterfaceType == null)
throw new NotCompliantMBeanException("Null MBean interface");
if (!mbeanInterfaceType.isInstance(resource)) {
final String msg =
"Resource class " + resource.getClass().getName() +
" is not an instance of " + mbeanInterfaceType.getName();
throw new NotCompliantMBeanException(msg);
}
ReflectUtil.checkPackageAccess(mbeanInterfaceType);
this.resource = resource;
// 获取到com.sun.jmx.mbeanserver.MXBeanSupport
// 追踪下去里面会有提供获取OpenType的方法,这里就不放了
MBeanIntrospector<M> introspector = getMBeanIntrospector();
this.perInterface = introspector.getPerInterface(mbeanInterfaceType);
this.mbeanInfo = introspector.getMBeanInfo(resource, perInterface);
}
要创建MXBean也需要两个步骤,但是条件没有 Standard MBean
这样苛刻了:
- 创建接口
SomethingMBean
- 实现步骤一的接口,类名可以自定义
MXBean
的使用可以看这里👉《MXBean 学习样例》
Dynamic MBean
前面在学习 Standard MBean
时,提出过 MBean Server
如何识别不同的资源对象呢?我们说到了introspection
,但是吧在 registerObject()
方法里面 Introspector.makeDynamicMBean()
会返回一个 Dynamic MBean
对象。前面无论是 Standard MBean
还是 MXBean
,它们最终都会转换为 Dynamic MBean
进行处理。
如果要创建一个 Dynamic MBean
需要实现下面这个接口:
public interface DynamicMBean {
/**
* 根据传入的Attribute,返回该Attribute对应的值。
* 相当于开发人员根据传入的attribute参数做一个映射,把资源里的attribute数据取出来返回
*/
public Object getAttribute(String attribute) throws AttributeNotFoundException,
MBeanException, ReflectionException;
// 同上,只不过传入的是一串attribute数组,查出来一串数据列表回去
public AttributeList getAttributes(String[] attributes);
/**
* 根据传入的Attribute,给内部的资源设置对应的属性值。
* 相当于开发人员根据传入的attribute参数,给内部资源设置对应的值
* 注意,传入的参数类型是Attribute,有key,有value;key是attribute,value是要设置的值
*/
public void setAttribute(Attribute attribute) throws AttributeNotFoundException,
InvalidAttributeValueException, MBeanException, ReflectionException ;
// 同上
public AttributeList setAttributes(AttributeList attributes);
// 调用operation方法时
public Object invoke(String actionName, Object params[], String signature[])
throws MBeanException, ReflectionException ;
// 获取描述一个MBean的基本信息
public MBeanInfo getMBeanInfo();
}
总的来说, Dynamic MBean
就是一个中间层,它将 Standard MBean
、 MXBean
抽象了出来, MBean Server
直接和 Dynamic MBean
打交道就行了,并不需要关系它究竟是 Standard MBean
还是 MXBean
。
所以如果我们想实现多个相近的类,我们就可以利用这个中间层做转换,最终只需要一个 Dynamic Bean
就能注册许多种 MBean
。Dynamic MBean
的使用可以看这里👉《Dynamic MBean学习样例》
小结
上面给我们展示了各种各样的 MBean
的开发方式,但无论是哪种 MBean
都不会影响 MBean Server
的注册,仅仅只会影响开发人员的开发方式。所以 MBean
只需要知道几个常用的即可,能因地制宜就可以了。
Notification
JMX Notification
主要由五个组件构成:
Notification
,基本的消息载体,它可以表示任意的事件NotificationListener
,需要接收消息的对象都要实现该接口NotificationFilter
,通过实现该接口可以过滤Notification
NotificationBroadcaster
,通过实现该接口,MBean
就能发送Notification
了NotificationEmitter
,通过实现该接口,可以监听MBean
的移除Listener
事件
在 JMX
里面,所有的消息都是广播出去的,如果没有配置 Filter
,那么任意实现了 Listener
的 MBean
都会收到消息;而配置了 Filter
就能过滤不需要的消息。这和安卓的广播过滤差不多的。
Notification
Notification
是消息的通用载体,任何的消息都能转到 Notification
对象里。接下来我们先康康创建一个 Notification
需要哪些参数:
public Notification(String type, Object source,
long sequenceNumber, long timeStamp, String message)
type
:定义消息的类型。规范上仅要求用一个String
类型来定义不同的消息类型。只不过这个类型字符串需要遵守一定的规则(最好):- 官方的
JMX
架构内所有的消息都是以jmx.mbean.eventType
来命名的;如果是商家自定义的,最好以VendorName.resouceA.eventType
。JMX Specification
上给了一张图:
- 官方的
source
,产生这条消息的事件源,通常是给发送消息的MBean
对象本身sequenceNumber
,可以视为该条消息的IDtimeStamp
,创建这条消息的时间message
,描述这条消息的内容userData
,这个属性需要在创建出Notification
后手动setUserData()
一般情况下, Notification
够用了,适用于绝大多数场景;但是如果想增加一些语义(让开发更加简便, Notification
的用户数据全都保存在 Object userData
里面,就没面向对象内味;而如果把需要传递的数据通过方法直接取到,就很爽),可以通过继承 Notification
实现定义自己的数据和方法,可以参考 AttributeChangeNotification
。
NotificationBroadcaster & NotificationEmitter
NotificationBroadcaster
是用来接收监听器注册/移除的接口; NotificationEmitter
继承了NotificationBroadcaster
接口,并在其之上增加了一个接收监听器移除的方法。NotificationBroadcaster
的接口代码如下所示:
public interface NotificationBroadcaster {
/**
* 添加Listener给实现 NotificationBroadcaster 的MBean时触发该方法
* @param listener 要注册的NotificationListener。当MBean发送消息时调用listener发送
* @param filter 当注册NotificationListener时,和Listener一起发来Filter。用来判断某条消息应不应该发送给一起来的Listener
* @param handback 和listener对应的handback,用来保存一些携带的参数。当回调listener时一起发送给listener,帮助listener处理消息的。
*/
public void addNotificationListener(NotificationListener listener,
NotificationFilter filter,
Object handback)
throws java.lang.IllegalArgumentException;
/**
* 当移除该MBean的一个Listener时会触发该方法
* @param listener 要删除的NotificationListener
*/
public void removeNotificationListener(NotificationListener listener)
throws ListenerNotFoundException;
/**
* 用来定义这个MBean会发送哪些Notification和Notification Type。
* 规范里推荐我们把要发送的Notification和Notification Type定义在这里
* 但是实际上也可以不这么做,只不过有的比较严谨的客户端会使用这个方法
* 保证消息完整性
*/
public MBeanNotificationInfo[] getNotificationInfo();
}
NotificationEmitter
的接口代码如下所示:
/**
* 是上面的removeNotificationListener()的增强版,这里面还带有filter和handback保证listener的唯一性
*/
public void removeNotificationListener(NotificationListener listener,
NotificationFilter filter,
Object handback)
总而言之,这里定义了一套关于 监听器注册/删除时回调 的方法。
NotificationListener
这个接口就是监听器接口啦,实现了监听器并注册到对应的 MBean
之后, MBean
发送消息给监听器,监听器就能收到了。
public interface NotificationListener extends java.util.EventListener {
/**
* 当产生消息通知时就该调用该方法。该方法应该尽快返回,避免阻塞
* @param notification 消息通知
* @param handback 当注册时(addNotificationListener)会要求传入该对象,该对象会在MBean发送者发送消息的时候传给监听器
*/
public void handleNotification(Notification notification, Object handback);
}
NotificationFilter
该接口是用来判断是否应该发送消息给对应的监听器:
public interface NotificationFilter extends java.io.Serializable {
/**
* Invoked before sending the specified notification to the listener.
* 在发送消息通知给特定的监听器之前判断一遍,
* @param notification 待发送的消息通知
* @return true表示该消息可以发送;false表示不可以发送
*/
public boolean isNotificationEnabled(Notification notification);
}
NotificationFilter
通常会在注册监听器的时候一起和监听器注册,消息发送者应该保存监听器、监听器过滤器,然后在消息发送前通过监听过滤器判断待发送的消息是否应该发送。
Notification
的使用可以看这里👉《Notification 学习样例》
小结
该部分说了一些和 Notification
相关的接口, Notification
各组件关系图如下所示:
点击查看【processon】
Metadata
当 MBean
注册到 MBeanServer
时,会自动创建 MBean
的元数据信息类 MBeanInfo
。 MBeanInfo
里面保存了 MBean
的属性、操作方法、支持的消息通知类型等等。
Metadata
的使用可以看这里👉《Metadata 学习样例》
总结
Instrumentation Level
这层基本都是为上层搭建一些基础设施,全文下来,能对这一层提供的大部分内容有一个基本的认识了。