Spring JMX编程学习(四)- MBeans自动探测与注解

6 篇文章 1 订阅

系列文章目录

Java管理扩展JMX入门学习
Spring JMX编程学习(一)- 手动注册bean到MBeanServer
Spring JMX编程学习(二)- 以Bean的方式注册MbeanServer
Spring JMX编程学习(三)- 自定义JMX客户端
Spring JMX编程学习(四)- MBeans自动探测与注解
Spring JMX编程学习(五)- SpringBoot自动注册


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在前面章节当中,我们已经通过在服务端手动注册MBean,创建MBeanServer,然后通过JMXConnectorServer将MBeanServer暴露给远程的客户端连接,本章我们将介绍在服务器通过自动探测的方式注册MBean


提示:以下是本篇文章正文内容,下面案例可供参考

一、自动探测

1. 基础探测模式

在前面的章节中,我们通过MBeanExporter类型的beans属性配置需要注册的MBean,如下所示

<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter" lazy-init="false">
    <property name="beans">
        <map>
            <entry key="com.example.spring.jmx:name=testBean" value-ref="testBean"/>
        </map>
    </property>
    <property name="server" ref="mbeanServer"/>
</bean>

如果是一个或者几个需要注册还是可以的,但是如果有很多的话,就不方便了,而且在新增了需要注册的MBean的时候,还需要修改配置文件,其实是不方便的。我们知道在Spring中一开始是通过在xml一个bean一个bean的配置的,但是后来可以通过加上注解(比如@Component)扫描指定包路径的方式注册。其实MBeanExporter也有类型的操作, 通过自动探测的方式将符合条件的Bean注册为MBean。这个时候MBeanExporter的配置如下

<!--    MBeans自动注册-->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
     开启自动探测的模式
    <property name="autodetect" value="true"/>
     serverConnector不需要注册为MBean
    <property name="excludedBeans" value="serverConnector"/>
    <!--如果存在同名的mBean则覆盖 以前版本的属性为registrationBehaviorName-->
    <property name="registrationPolicy" value="REPLACE_EXISTING"/>
</bean>

此时spring-jmx-server.xml文件的完整配置如下

<bean id="testBean" class="com.example.spring.jmx.JmxTestBean">
    <property name="name" value="I am a manual Server Side testBean"/>
    <property name="age" value="100"/>
</bean>

<bean id="serverConnector"
      class="org.springframework.jmx.support.ConnectorServerFactoryBean" depends-on="registry">
    <property name="serviceUrl"
              value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
</bean>

<!--通过rmi方式-->
<bean id="registry" class="org.springframework.remoting.rmi.RmiRegistryFactoryBean">
    <property name="port" value="1099"/>
</bean>

<!--    MBeans自动注册-->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="autodetect" value="true"/>
    <property name="excludedBeans" value="serverConnector"/>
    <!--如果存在同名的mBean则覆盖 以前版本的属性为registrationBehaviorName-->
    <property name="registrationPolicy" value="REPLACE_EXISTING"/>
</bean>

启动主程序, 但是发现没有注册MBean的日志信息
在这里插入图片描述
通过查看源码org.springframework.jmx.export.MBeanExporter#registerBeans
在这里插入图片描述
主要的逻辑如下

	/**
	 * Performs the actual autodetection process, delegating to an
	 * {@code AutodetectCallback} instance to vote on the inclusion of a
	 * given bean.
	 * @param callback the {@code AutodetectCallback} to use when deciding
	 * whether to include a bean or not
	 */
	private void autodetect(Map<String, Object> beans, AutodetectCallback callback) {
		Assert.state(this.beanFactory != null, "No BeanFactory set");
		Set<String> beanNames = new LinkedHashSet<>(this.beanFactory.getBeanDefinitionCount());
		Collections.addAll(beanNames, this.beanFactory.getBeanDefinitionNames());
		if (this.beanFactory instanceof ConfigurableBeanFactory) {
			Collections.addAll(beanNames, ((ConfigurableBeanFactory) this.beanFactory).getSingletonNames());
		}
		1. 遍历所有的bean名称
		for (String beanName : beanNames) {
		    2. 如果已经配置需要排出的(excludedBeans属性)或者是抽象类不需要注册
			if (!isExcluded(beanName) && !isBeanDefinitionAbstract(this.beanFactory, beanName)) {
				try {
				    3. 获取bean的类型
					Class<?> beanClass = this.beanFactory.getType(beanName);
					4. 这里的callback是个关键 判断是否为MBean的逻辑
					if (beanClass != null && callback.include(beanClass, beanName)) {
						boolean lazyInit = isBeanDefinitionLazyInit(this.beanFactory, beanName);
						Object beanInstance = null;
						if (!lazyInit) {
							beanInstance = this.beanFactory.getBean(beanName);
							if (!beanClass.isInstance(beanInstance)) {
								continue;
							}
						}
						if (!ScopedProxyUtils.isScopedTarget(beanName) && !beans.containsValue(beanName) &&
								(beanInstance == null ||
										!CollectionUtils.containsInstance(beans.values(), beanInstance))) {
							// Not already registered for JMX exposure.
							beans.put(beanName, (beanInstance != null ? beanInstance : beanName));
							if (logger.isDebugEnabled()) {
								logger.debug("Bean with name '" + beanName + "' has been autodetected for JMX exposure");
							}
						}
						else {
							if (logger.isTraceEnabled()) {
								logger.trace("Bean with name '" + beanName + "' is already registered for JMX exposure");
							}
						}
					}
				}
				catch (CannotLoadBeanClassException ex) {
					if (this.allowEagerInit) {
						throw ex;
					}
					// otherwise ignore beans where the class is not resolvable
				}
			}
		}
	}

在以上的回调方法中执行为以下的逻辑

protected boolean isMBean(@Nullable Class<?> beanClass) {
	return JmxUtils.isMBean(beanClass);
}

这个是用于判断一个类是不是属于MBean或者MXBean的。这个在前面我们已经介绍过。这里除了修改接口com.example.spring.jmx.IJmxTestBean的名称包含MBean或者MXBean后缀的方式,还可以通过在对应接口上面添加注解javax.management.MXBean。我们采取后面的方式。
在这里插入图片描述
再次启动程序,报错了,但是也可以发现程序其实已经自动探测了testBean
在这里插入图片描述
详细的错误日志如下

16:02:24.914 [main] DEBUG org.springframework.jmx.export.MBeanExporter - Bean with name 'testBean' has been autodetected for JMX exposure
Exception in thread "main" org.springframework.jmx.export.UnableToRegisterMBeanException: Unable to register MBean [com.example.spring.jmx.JmxTestBean@43b9fd5] with key 'testBean'; nested exception is javax.management.MalformedObjectNameException: Key properties cannot be empty
	at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:626)
	at org.springframework.jmx.export.MBeanExporter.lambda$registerBeans$2(MBeanExporter.java:552)
	at java.util.HashMap.forEach(HashMap.java:1288)
	at org.springframework.jmx.export.MBeanExporter.registerBeans(MBeanExporter.java:552)
	at org.springframework.jmx.export.MBeanExporter.afterSingletonsInstantiated(MBeanExporter.java:435)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:864)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:144)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:85)
	at com.example.spring.jmx.JmxStartServerMain.main(JmxStartServerMain.java:12)
Caused by: javax.management.MalformedObjectNameException: Key properties cannot be empty
	at javax.management.ObjectName.construct(ObjectName.java:483)
	at javax.management.ObjectName.<init>(ObjectName.java:1382)
	at javax.management.ObjectName.getInstance(ObjectName.java:1273)
	at org.springframework.jmx.support.ObjectNameManager.getInstance(ObjectNameManager.java:67)
	at org.springframework.jmx.export.naming.KeyNamingStrategy.getObjectName(KeyNamingStrategy.java:145)
	at org.springframework.jmx.export.MBeanExporter.getObjectName(MBeanExporter.java:756)
	at org.springframework.jmx.export.MBeanExporter.registerBeanInstance(MBeanExporter.java:655)
	at org.springframework.jmx.export.MBeanExporter.registerBeanNameOrInstance(MBeanExporter.java:616)
	... 10 more

这里的异常类是MalformedObjectNameException,也就是说注册的MBean的ObjectName名称不符合规则。其实这里系统会自动取bean的名称也就是配置的id作为ObjectName。我们修改如下

<bean id="com.example.spring.jmx:name=testBean" class="com.example.spring.jmx.JmxTestBean">
    <property name="name" value="I am a autodetect Server Side testBean"/>
    <property name="age" value="100"/>
</bean>

在这里插入图片描述
此时程序是没有问题的。客户端调用也正常。
在这里插入图片描述

2. 注解探测模式

虽然通过以上的方式我们可以自动注册MBean,但是总有一点很奇怪,首先就是Bean的名称作为MBean的ObjectName必须符合一定的规则,这样导致bean的名称很怪异(与MBean规则耦合),另一方面,作为Spring的Bean哪些作为MBean属性、哪些作为MBean操作也完全都是MBean的一套规则,可能本来这个接口我只是想作为Bean在Spring环境中使用的,最后却因为是一个public方法被暴露给远程了。所以我们需要另一种方式,一方面可以与MBean的规则解耦,还可以更细粒度的控制需要暴露的信息。其实Spring包含这种方式,通过Spring的org.springframework.jmx.export.annotation.ManagedResourceorg.springframework.jmx.export.annotation.ManagedAttributeorg.springframework.jmx.export.annotation.ManagedOperation就可以细粒度的控制需要暴露的信息。比如创建一个新的接口。

package com.example.spring.jmx;

import org.springframework.jmx.export.annotation.*;

@ManagedResource(
        objectName = "com.example.spring.jmx: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;

    @Override
    @ManagedAttribute(description = "The Age Attribute", currencyTimeLimit = 15)
    public int getAge() {
        return age;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    @ManagedAttribute(defaultValue = "foo", persistPeriod = 300)
    public String getName() {
        return name;
    }

    @Override
    @ManagedAttribute(description = "The Name Attribute",
            currencyTimeLimit = 20,
            defaultValue = "bar",
            persistPolicy = "OnUpdate")
    public void setName(String name) {
        this.name = name;
    }

    @Override
    @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;
    }

    @Override
    public long myOperation() {
        return 0;
    }

    public void dontExposeMe() {
        throw new RuntimeException();
    }
}

首先在这个接口中,我们通过ManagedResource的objectName属性设置了MBean的ObjectName为com.example.spring.jmx:name=testBean4,另外暴露了name和age两个属性,前者是可读可写的,后者是只读的,因为setAge方法是没有ManagedAttribute注解的。同样的道理,这里也自由add方法被暴露。(注意:这个类的接口并不需要符合MBean规则,所以去掉上面添加的@MXBean注解)
将以上类配置到Spring容器中

<bean id="testBean" class="com.example.spring.jmx.AnnotationTestBean">
    <property name="name" value="I am a autodetect Server Side AnnotationTestBean"/>
    <property name="age" value="100"/>
</bean>

需要以上的注解起作用,还需要以下几个bean的配置,并且还需要配置到MBeanExporter配置当中

<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 the ObjectName from the annotation-->
<bean id="namingStrategy"
      class="org.springframework.jmx.export.naming.MetadataNamingStrategy">
    <property name="attributeSource" ref="jmxAttributeSource"/>
</bean>

<!--    MBeans自动注册-->
<bean id="exporter" class="org.springframework.jmx.export.MBeanExporter">
    <property name="autodetect" value="true"/>
    <property name="excludedBeans" value="serverConnector"/>
    <!--如果存在同名的mBean则覆盖 以前版本的属性为registrationBehaviorName-->
    <property name="registrationPolicy" value="REPLACE_EXISTING"/>
    <!--MBean信息获取-->
    <property name="assembler" ref="assembler"/>
    <!--MBean命名规则-->
    <property name="namingStrategy" ref="namingStrategy"/>
</bean>

在这里插入图片描述
修改客户端配置文件以及主类

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="clientConnector"
          class="org.springframework.jmx.support.MBeanServerConnectionFactoryBean">
        <property name="connectOnStartup" value="false"/>
        <property name="serviceUrl" value="service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi"/>
    </bean>

    <bean id="annoProxy" class="org.springframework.jmx.access.MBeanProxyFactoryBean">
        <property name="objectName" value="com.example.spring.jmx:name=testBean4"/>
        <property name="proxyInterface" value="com.example.spring.jmx.IJmxTestBean"/>
        <property name="server" ref="clientConnector"/>
    </bean>

</beans>
package com.example.spring.jmx;

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Created in 2020/11/19 14:53 by guanglai.zhou
 */
public class JmxStartClientMain {

    public static void main(String[] args) {
        // 启动Spring容器
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-jmx-client.xml");
        IJmxTestBean annoProxy = (IJmxTestBean) applicationContext.getBean("annoProxy");
        System.out.println(annoProxy.getName());
        System.out.println(annoProxy.add(1,2));
    }
}

在这里插入图片描述
不妨通过JConsole工具查看
在这里插入图片描述

在这里插入图片描述

二、关键源码解析

1. JmxAttributeSource

在上面的配置文件我们使用了如下这个Bean

<bean id="jmxAttributeSource"
      class="org.springframework.jmx.export.annotation.AnnotationJmxAttributeSource"/>

这个Bean实现了接口org.springframework.jmx.export.metadata.JmxAttributeSource,这个接口是用于MetadataMBeanInfoAssembler从管理资源类(MBean)中读取元数据的。主要包含以下几个接口:
在这里插入图片描述
这几个方法看起来也不难理解,无非就是读取MBean的属性、操作、监听等信息,只不过Spring针对这一些都抽象为了自己的类(ManagedResourceManagedAttributeManagedNotification),有一个是我们前面没有介绍到的,就是ManagedMetric,其实这也是JMX属性的一种。目前这个类只有一个实现类AnnotationJmxAttributeSource
getManagedResource用于从类上面读取管理资源信息,实现类如下

@Override
@Nullable
public org.springframework.jmx.export.metadata.ManagedResource getManagedResource(Class<?> beanClass) throws InvalidMetadataException {
    1. 在当前类、父类、接口以及注解中查找ManagedResource注解信息
	ManagedResource ann = AnnotationUtils.findAnnotation(beanClass, ManagedResource.class);
	if (ann == null) {
		return null;
	}
	2. 查找出声明ManagedResource注解的那个类
	Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(ManagedResource.class, beanClass);
	3. 声明注解的类必须是公共类(public)
	Class<?> target = (declaringClass != null && !declaringClass.isInterface() ? declaringClass : beanClass);
	if (!Modifier.isPublic(target.getModifiers())) {
		throw new InvalidMetadataException("@ManagedResource class '" + target.getName() + "' must be public");
	}
	4. 创建一个ManagedResource并从注解中拷贝属性 
	org.springframework.jmx.export.metadata.ManagedResource managedResource = new org.springframework.jmx.export.metadata.ManagedResource();
	AnnotationBeanUtils.copyPropertiesToBean(ann, managedResource, this.embeddedValueResolver);
	return managedResource;
}

这里之所以使用到embeddedValueResolver,是因为这里考虑支持占位符(在注解中包含占位符),这个属性是在bean初始化(BeanFactoryAware接口回调方法)的时候回调的

@Nullable
private StringValueResolver embeddedValueResolver;


@Override
public void setBeanFactory(BeanFactory beanFactory) {
	if (beanFactory instanceof ConfigurableBeanFactory) {
		this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
	}
}
public static void copyPropertiesToBean(Annotation ann, Object bean, @Nullable StringValueResolver valueResolver,
		String... excludedProperties) {
    1. 获取排除拷贝的属性集合
	Set<String> excluded = (excludedProperties.length == 0 ? Collections.emptySet() :
			new HashSet<>(Arrays.asList(excludedProperties)));
	2. 获取注解中所有的属性		
	Method[] annotationProperties = ann.annotationType().getDeclaredMethods();
	3. 将目标类转变为BeanWrapper对象 
	BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(bean);
	for (Method annotationProperty : annotationProperties) {
		String propertyName = annotationProperty.getName();
		4. 当前属性未排除拷贝而且BeanWrapper存在setter方法 
		if (!excluded.contains(propertyName) && bw.isWritableProperty(propertyName)) {
		    5. 通过反射获取注解中指定属性值
			Object value = ReflectionUtils.invokeMethod(annotationProperty, ann);
			6. 如果结果值为字符串而且valueResolver不为null,解析占位符 
			if (valueResolver != null && value instanceof String) {
				value = valueResolver.resolveStringValue((String) value);
			}
			7. 将值设置到BeanWrapper,其实也就是目标对象当中
			bw.setPropertyValue(propertyName, value);
		}
	}
}

以上拷贝有一个前提,就是@ManagedResource注解和org.springframework.jmx.export.metadata.ManagedResource实体类必须属性名称一致,否则使用以上的拷贝方法是没有用的。当前其他几个注解以及对应的实体类的关系都是这样的。
在这里插入图片描述
如果有包含不一致的属性的,比如在@ManagedAttribute中包含有defaultValue属性(类型为String),而在类ManagedAttribute中的defaultValue的属性为Object对象。所以在拷贝的时候单独处理

@Override
@Nullable
public org.springframework.jmx.export.metadata.ManagedAttribute getManagedAttribute(Method method) throws InvalidMetadataException {
    1. 这里是从方法上查找属性 这个注解是使用在方法上的 而不是类上面的
	ManagedAttribute ann = AnnotationUtils.findAnnotation(method, ManagedAttribute.class);
	if (ann == null) {
		return null;
	}
	org.springframework.jmx.export.metadata.ManagedAttribute managedAttribute = new org.springframework.jmx.export.metadata.ManagedAttribute();
	2. 此时不拷贝defaultValue的属性
	AnnotationBeanUtils.copyPropertiesToBean(ann, managedAttribute, "defaultValue");
	3. 注解中defaultValue存在有效值,则单独设置到对象当中
	if (ann.defaultValue().length() > 0) {
		managedAttribute.setDefaultValue(ann.defaultValue());
	}
	return managedAttribute;
}

与上面解析方法的不同,除了这个是在方法级别上,而且也不支持占位符模式。ManagedMetricManagedOperation的使用和解析情况差不多,最后就是ManagedNotificationManagedOperationParameter这两个注解,前者作用于方法,后者作用于类,二者的共同点就是都可以同时存在多个。

@Override
public org.springframework.jmx.export.metadata.ManagedOperationParameter[] getManagedOperationParameters(Method method)
		throws InvalidMetadataException {
	1. 获取注解值 可能存在多个
	Set<ManagedOperationParameter> anns = AnnotationUtils.getRepeatableAnnotations(
			method, ManagedOperationParameter.class, ManagedOperationParameters.class);
	2. 创建同样个数的实例 然后再拷贝属性值		
	return copyPropertiesToBeanArray(anns, org.springframework.jmx.export.metadata.ManagedOperationParameter.class);
}

@Override
public org.springframework.jmx.export.metadata.ManagedNotification[] getManagedNotifications(Class<?> clazz)
		throws InvalidMetadataException {

	Set<ManagedNotification> anns = AnnotationUtils.getRepeatableAnnotations(
			clazz, ManagedNotification.class, ManagedNotifications.class);
	return copyPropertiesToBeanArray(anns, org.springframework.jmx.export.metadata.ManagedNotification.class);
}

private static <T> T[] copyPropertiesToBeanArray(Collection<? extends Annotation> anns, Class<T> beanClass) {
    1. 创建一个类的数组对象 个数为注解集合的大小 
	T[] beans = (T[]) Array.newInstance(beanClass, anns.size());
	int i = 0;
	2. 将注解属性分别拷贝到对应的实体类上面
	for (Annotation ann : anns) {
		beans[i++] = copyPropertiesToBean(ann, beanClass);
	}
	3. 返回实体类
	return beans;
}

@Nullable
private static <T> T copyPropertiesToBean(@Nullable Annotation ann, Class<T> beanClass) {
	if (ann == null) {
		return null;
	}
	T bean = BeanUtils.instantiateClass(beanClass);
	AnnotationBeanUtils.copyPropertiesToBean(ann, bean);
	return bean;
}

所以AnnotationJmxAttributeSource类的作用就是读取各种Spring提供的注解属性并创建包含这些属性的实体类。

2. MBeanInfoAssembler

通过JmxAttributeSource我们已经获取了Bean上面的注解信息,接下来是需要与我们的目标对象创建联系,从上一章我们知道之所以使用注解,是因为我们不喜欢MBean的各种规则(类名)。所以现在将一个不符合MBean规则的类型的Bean转变为MBean就必须为这个对象生成管理接口(management interface),其实也就是javax.management.modelmbean.ModelMBeanInfo(主要用于ModelMBean,以下为官方说明)。

This interface is implemented by the ModelMBeanInfo for every ModelMBean. An implementation of this interface must be shipped with every JMX Agent.Java resources wishing to be manageable instantiate the ModelMBean using the MBeanServer’s createMBean method. The resource then sets the ModelMBeanInfo and Descriptors for the ModelMBean instance. The attributes, operations, and notifications exposed via the ModelMBeanInfo for the ModelMBean comprise the management interface and are accessible from MBeans, connectors/adaptors like other MBeans. Through the Descriptors, values and methods in the managed application can be defined and mapped to attributes and operations of the ModelMBean. This mapping can be defined during development in a file or dynamically and programmatically at runtime.Every ModelMBean which is instantiated in the MBeanServer becomes manageable: its attributes, operations, and notifications become remotely accessible through the connectors/adaptors connected to that MBeanServer. A Java object cannot be registered in the MBeanServer unless it is a JMX compliant MBean. By instantiating a ModelMBean, resources are guaranteed that the MBean is valid. MBeanException and RuntimeOperationsException must be thrown on every public method. This allows for wrapping exceptions from distributed communications (RMI, EJB, etc.)

对应的接口如下

public interface MBeanInfoAssembler {

	/**
	 * Create the ModelMBeanInfo for the given managed resource.
	 * @param managedBean the bean that will be exposed (might be an AOP proxy)
	 * @param beanKey the key associated with the managed bean
	 * @return the ModelMBeanInfo metadata object
	 * @throws JMException in case of errors
	 */
	ModelMBeanInfo getMBeanInfo(Object managedBean, String beanKey) throws JMException;

}

使用的实现类为MetadataMBeanInfoAssembler,其中就是用了上面的JmxAttributeSource接口,同时实现了org.springframework.beans.factory.InitializingBean接口,所以查看afterPropertiesSet方法
在这里插入图片描述

@Override
public void afterPropertiesSet() {
	if (this.attributeSource == null) {
		throw new IllegalArgumentException("Property 'attributeSource' is required");
	}
}

很简单,就是保证attributeSource属性不为空即可、然后看一下
org.springframework.jmx.export.assembler.AbstractMBeanInfoAssembler#getMBeanInfo

@Override
public ModelMBeanInfo getMBeanInfo(Object managedBean, String beanKey) throws JMException {
    1. 目标对象不能是JDK动态代理对象,否则会抛出IllegalArgumentException,仅支持没有代理的或者CGLIB代理的对象
	checkManagedBean(managedBean);
	2. 根据managedBean构建ModelMBeanInfo对象,就是就是获取setter/getter方法构建AttributeInfo,其他方法构建OperationInfo,其他信息ClassName、Description都是目标类的类型,而ConstructorInfo、NotificationInfo只是空的数组,毕竟从一个普通类里面获取不到这些信息
	ModelMBeanInfo info = new ModelMBeanInfoSupport(
			getClassName(managedBean, beanKey), getDescription(managedBean, beanKey),
			getAttributeInfo(managedBean, beanKey), getConstructorInfo(managedBean, beanKey),
			getOperationInfo(managedBean, beanKey), getNotificationInfo(managedBean, beanKey));
	Descriptor desc = info.getMBeanDescriptor();
	3. 从attributeSource中获取信息并填充 
	populateMBeanDescriptor(desc, managedBean, beanKey);
	info.setMBeanDescriptor(desc);
	return info;
}

这里的第1步和第3步,就会从元数据中获取信息并填充
其中的第1步中
在这里插入图片描述
在这里插入图片描述

@Override
protected ModelMBeanAttributeInfo[] getAttributeInfo(Object managedBean, String beanKey) throws JMException {
	PropertyDescriptor[] props = BeanUtils.getPropertyDescriptors(getClassToExpose(managedBean));
	List<ModelMBeanAttributeInfo> infos = new ArrayList<>();
	1. 遍历当前bean中的每个属性
	for (PropertyDescriptor prop : props) {
	    2、 判断每个属性的getter方法是否满足条件,如果满足条件 对应的属性就会暴露为可读
		Method getter = prop.getReadMethod();
		getter方法不能是属于Object类的方法
		if (getter != null && getter.getDeclaringClass() == Object.class) {
			continue;
		}
		getter方法中包含有ManagedAttribute注解或者ManagedMetric注解
		if (getter != null && !includeReadAttribute(getter, beanKey)) {
			getter = null;
		}
		3. 判断每个属性的setter方法是否满足条件,如果满足条件 对应的属性就会暴露为可写
		Method setter = prop.getWriteMethod();
		setter方法存在而且包含有ManagedAttribute注解
		if (setter != null && !includeWriteAttribute(setter, beanKey)) {
			setter = null;
		}
        4. 只要getter或setter方法有一个满足条件 此处就不为空 那么这个属性就会被暴露 根据情况决定是否可读可写
		if (getter != null || setter != null) {
			// If both getter and setter are null, then this does not need exposing.
			获取属性名称,这里isUseStrictCasing返回true 则将属性名称的第一个字母变为大写 比如age->Age
			String attrName = JmxUtils.getAttributeName(prop, isUseStrictCasing());
			通过setter、getter方法上的注解生成一个描述符 比如在上面的案例当中为 The Age Attribute
			String description = getAttributeDescription(prop, beanKey);
			构建一个ModelMBeanAttributeInfo对象
			ModelMBeanAttributeInfo info = new ModelMBeanAttributeInfo(attrName, description, getter, setter);

			Descriptor desc = info.getDescriptor();
			if (getter != null) {
				设置getMethod
				desc.setField(FIELD_GET_METHOD, getter.getName());
			}
			if (setter != null) {
			    设置setMethod
				desc.setField(FIELD_SET_METHOD, setter.getName());
			}
			获取注解中各种属性并填充到Descriptor当中 
			populateAttributeDescriptor(desc, getter, setter, beanKey);
			info.setDescriptor(desc);
			infos.add(info);
		}
	}

	return infos.toArray(new ModelMBeanAttributeInfo[0]);
}

解析的结果如下所示
在这里插入图片描述
而getOperationInfo则是获取操作的信息,比如add方法如下
在这里插入图片描述
当然其中也包括setter、setter方法
在这里插入图片描述
另外getNotificationInfo会从类上获取ManagedNotification或ManagedNotifications注解信息构造ModelMBeanNotificationInfo信息。
其中的第3步

protected void populateMBeanDescriptor(Descriptor desc, Object managedBean, String beanKey) {
    1. getManagedResource通过类获取ManagedResource信息 这个就是上面提到的
	ManagedResource mr = obtainAttributeSource().getManagedResource(getClassToExpose(managedBean));
	if (mr == null) {
		throw new InvalidMetadataException(
				"No ManagedResource attribute found for class: " + getClassToExpose(managedBean));
	}
    
    2. 设置ManagedResource中的各个属性到desc当中
	applyCurrencyTimeLimit(desc, mr.getCurrencyTimeLimit());

	if (mr.isLog()) {
		desc.setField(FIELD_LOG, "true");
	}
	if (StringUtils.hasLength(mr.getLogFile())) {
		desc.setField(FIELD_LOG_FILE, mr.getLogFile());
	}

	if (StringUtils.hasLength(mr.getPersistPolicy())) {
		desc.setField(FIELD_PERSIST_POLICY, mr.getPersistPolicy());
	}
	if (mr.getPersistPeriod() >= 0) {
		desc.setField(FIELD_PERSIST_PERIOD, Integer.toString(mr.getPersistPeriod()));
	}
	if (StringUtils.hasLength(mr.getPersistName())) {
		desc.setField(FIELD_PERSIST_NAME, mr.getPersistName());
	}
	if (StringUtils.hasLength(mr.getPersistLocation())) {
		desc.setField(FIELD_PERSIST_LOCATION, mr.getPersistLocation());
	}
}

在这里插入图片描述

在MBeanExporter类方法org.springframework.jmx.export.MBeanExporter#registerBeans中会进行自动探测需要注册的Bean,就会使用到这个assembler了, 其中的代码如下,

// Allow the assembler a chance to vote for bean inclusion.
if ((mode == AUTODETECT_ASSEMBLER || mode == AUTODETECT_ALL) &&
		this.assembler instanceof AutodetectCapableMBeanInfoAssembler) {
	autodetect(this.beans, ((AutodetectCapableMBeanInfoAssembler) this.assembler)::includeBean);
}

首先这里是一个回调,判断当前的bean是不是有资格进行注册,方法如下,其实就是判断这个bean的类上是否包含ManagedResource注解。

public boolean includeBean(Class<?> beanClass, String beanName) {
	return (obtainAttributeSource().getManagedResource(getClassToExpose(beanClass)) != null);
}

如果满足注册的条件,则会进行注册。在org.springframework.jmx.export.MBeanExporter#registerBeanInstance方法中包含如下代码

if (logger.isDebugEnabled()) {
	logger.debug("Located managed bean '" + beanKey + "': registering with JMX server as MBean [" +
			objectName + "]");
}
1. 适配为ModelMBean
ModelMBean mbean = createAndConfigureMBean(bean, beanKey);
2. 进行注册 
doRegister(mbean, objectName);
injectNotificationPublisherIfNecessary(bean, mbean, objectName);
/** Constant for the JMX {@code mr_type} "ObjectReference". */
private static final String MR_TYPE_OBJECT_REFERENCE = "ObjectReference";
/**
 * Creates an MBean that is configured with the appropriate management
 * interface for the supplied managed resource.
 * @param managedResource the resource that is to be exported as an MBean
 * @param beanKey the key associated with the managed bean
 * @see #createModelMBean()
 * @see #getMBeanInfo(Object, String)
 */
protected ModelMBean createAndConfigureMBean(Object managedResource, String beanKey)
		throws MBeanExportException {
	try {
	    1. 根据配置exposeManagedResourceClassLoader(默认为true)创建SpringModelMBean或者RequiredModelMBean
		ModelMBean mbean = createModelMBean();
		2. 通过assembler获取ModelMBeanInfo信息并设置到mbean当中
		mbean.setModelMBeanInfo(getMBeanInfo(managedResource, beanKey));
		3. Sets the instance handle of the object against which to execute all methods in this ModelMBean management interface (MBeanInfo and Descriptors).The type of reference for the managed resource. Can be: ObjectReference, Handle, IOR, EJBHandle, RMIReference.
		mbean.setManagedResource(managedResource, MR_TYPE_OBJECT_REFERENCE);
		return mbean;
	}
	catch (Throwable ex) {
		throw new MBeanExportException("Could not create ModelMBean for managed resource [" +
				managedResource + "] with key '" + beanKey + "'", ex);
	}
}

在这里插入图片描述

/**
 * Gets the {@code ModelMBeanInfo} for the bean with the supplied key
 * and of the supplied type.
 */
private ModelMBeanInfo getMBeanInfo(Object managedBean, String beanKey) throws JMException {
	ModelMBeanInfo info = this.assembler.getMBeanInfo(managedBean, beanKey);
	if (logger.isInfoEnabled() && ObjectUtils.isEmpty(info.getAttributes()) &&
			ObjectUtils.isEmpty(info.getOperations())) {
		logger.info("Bean with key '" + beanKey +
				"' has been registered as an MBean but has no exposed attributes or operations");
	}
	return info;
}

3. ObjectNamingStrategy

在org.springframework.jmx.export.MBeanExporter#registerBeanInstance方法中首先必须获取ObjectName信息,此时可以通过将指定Bean实现结果SelfNaming提供一个名称,如果不是,则是通过namingStrategy来获取的。默认情况下实现策略类为KeyNamingStrategy。

protected ObjectName getObjectName(Object bean, @Nullable String beanKey) throws MalformedObjectNameException {
	if (bean instanceof SelfNaming) {
		return ((SelfNaming) bean).getObjectName();
	}
	else {
		return this.namingStrategy.getObjectName(bean, beanKey);
	}
}

我们当前注册为MetadataNamingStrategy,这个类通过获取注解信息来生成MBean的ObjectName.
在这里插入图片描述
初始化保证attributeSource属性必须存在。

@Override
public void afterPropertiesSet() {
	if (this.attributeSource == null) {
		throw new IllegalArgumentException("Property 'attributeSource' is required");
	}
}

而在实现的接口方法中逻辑如下,比较简单

/**
 * Reads the {@code ObjectName} from the source-level metadata associated
 * with the managed resource's {@code Class}.
 */
@Override
public ObjectName getObjectName(Object managedBean, @Nullable String beanKey) throws MalformedObjectNameException {
	Assert.state(this.attributeSource != null, "No JmxAttributeSource set");
	1. 获取目标列
	Class<?> managedClass = AopUtils.getTargetClass(managedBean);
	2. 获取目标类上面的注解信息
	ManagedResource mr = this.attributeSource.getManagedResource(managedClass);

	// Check that an object name has been specified.
	if (mr != null && StringUtils.hasText(mr.getObjectName())) {
	    3. 目标类注解上包含ObjectName信息,则直接取该值
		return ObjectNameManager.getInstance(mr.getObjectName());
	}
	else {
		Assert.state(beanKey != null, "No ManagedResource attribute and no bean key specified");
		try {
		    4. 使用传入的beanKey,其实就是beanName
			return ObjectNameManager.getInstance(beanKey);
		}
		catch (MalformedObjectNameException ex) {
		    5. 但是通常beanName都不符合ObjectName的格式 所以这里尝试构造一个,比如com.example.spring.jmx:name=testBean(包名:name=bean名称)    
			String domain = this.defaultDomain;
			if (domain == null) {
				domain = ClassUtils.getPackageName(managedClass);
			}
			Hashtable<String, String> properties = new Hashtable<>();
			properties.put("type", ClassUtils.getShortName(managedClass));
			properties.put("name", beanKey);
			return ObjectNameManager.getInstance(domain, properties);
		}
	}
}

总结

MBeans自动探测方式分为两种,在简单模式下,只需要开启MBeanExporter一个配置属性即可(autodetect设置为true),但是在这种模式下,首先需要对应的Bean的接口符合MBean的相关规则,实现类与接口的类名称或者在接口上面添加@MXBean注解,更麻烦的是bean的名称也要符合ObjectName的规则,Spring提供了更简洁的方式,通过在实现类上面添加注解,既不会与Spring的beanName耦合,而且可以更细粒度的暴露信息,为了支持注解,必须在MBeanExporter中配置自定义的MBean组织方式assembler属性和MBean的命名策略namingStrategy,说白了,其实既是从注解中获取,以后只需要在实现类上面添加注解即可完成注册了,不需要再修改MBeanExporter等配置了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值