Spring JMX编程学习(五)- SpringBoot自动注册

6 篇文章 1 订阅

系列文章目录

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



前言

在前面的章节中我们从Java原生编程到Spring,在原生Java中从创建一个MBean,到创建MBeanServer,注册MBean,在Spring中通过自动探测模式查找包含有相关注解(比如org.springframework.jmx.export.annotation.ManagedResource)的Bean,通过org.springframework.jmx.export.assembler.MBeanInfoAssembler查找ModelMBeanInfo,然后将对应的Bean适配为javax.management.modelmbean.RequiredModelMBean(org.springframework.jmx.export.SpringModelMBean),同时还提供了org.springframework.jmx.export.naming.ObjectNamingStrategy策略接口用于从注解中获取MBean的ObjectName,而在SpringBoot当中又会是怎样的呢?


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

一、Spring Boot中使用JMX的方式

创建一个接口以及实现类

package com.example.spring.jmx;

public interface IJmxTestBean {
    int add(int x, int y);

    long myOperation();

    int getAge();

    void setAge(int age);

    String getName();

    void setName(String name);
}

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();
    }
}

然后创建一个配置类用于注册一个Bean

package com.example.spring.jmx;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class BootConfig {

    @Bean
    public AnnotationTestBean testBean() {
        AnnotationTestBean testBean =  new AnnotationTestBean();
        testBean.setAge(18);
        testBean.setName("如花");
        return testBean;
    }
}

编写配置文件,注意配置文件中修改了org.springframework.jmx包的日志级别为debug,方便启动后查看注册相关日志

server.port=8081
logging.level.root=info
logging.level.org.springframework.jmx=debug

创建一个启动类

package com.example.spring.jmx;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JmxSpringBootStartMain {

    public static void main(String[] args) {
        SpringApplication.run(JmxSpringBootStartMain.class, args);
    }
}

在这里插入图片描述
启动之后查看日志,可以看出容器注册了MBean.那么Spring Boot是怎么做到的呢?首先在org.springframework.boot.autoconfigure.SpringBootApplication注解当中包含了org.springframework.boot.autoconfigure.EnableAutoConfiguration注解
在这里插入图片描述
在这里插入图片描述
而EnableAutoConfiguration注解又通过AutoConfigurationImportSelector这个类在Spring加载bean配置的时候从META-INF/spring.factories配置文件中读取所有键值为org.springframework.boot.autoconfigure.EnableAutoConfiguration的类进行加载,而这其中就包含了自动注册JMX相关Bean的类:org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration
在这里插入图片描述
对应的类声明如下,这是一个配置类,如果当前classpath下包含有类MBeanExporter(ConditionalOnClass)这个配置类就会被注册到容器当中了。因为spring.jmx.enabled如果没有配置也是true。
在这里插入图片描述
在这个配置类中,分别注册了MBeanServer、objectNamingStrategy以及mbeanExporter,都是前面提到过的,只不过现在由Spring Boot自动注册了。但是如果需要通过客户端访问我们还差一步,就是需要注册一个serverConnector。我们在BootConfig中添加如下代码

@Bean
public JMXConnectorServer serverConnector(Registry registry) throws IOException, JMException {
    ConnectorServerFactoryBean factory = new ConnectorServerFactoryBean();
    factory.setServiceUrl("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi");
    factory.afterPropertiesSet();
    return factory.getObject();
}

@Bean
public Registry registry() throws Exception {
    RmiRegistryFactoryBean factory = new RmiRegistryFactoryBean();
    factory.setPort(1099);
    factory.afterPropertiesSet();
    return factory.getObject();
}

因为当前创建的serverConnector依赖于RMI协议,所以需要创建一个registry,而且必须是在serverConnector之前初始化,因为在serverConnector方法当中将registry作为参数传入,这样就能保证先后顺序了。还有以下方式,也可以保证顺序性

这里改为static方法
@Bean
public static Registry registry() throws Exception {
    RmiRegistryFactoryBean factory = new RmiRegistryFactoryBean();
    factory.setPort(1099);
    factory.afterPropertiesSet();
    return factory.getObject();
}

@Bean
public JMXConnectorServer serverConnector() throws IOException, JMException {
    ConnectorServerFactoryBean factory = new ConnectorServerFactoryBean();
    factory.setServiceUrl("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi");
    factory.afterPropertiesSet();
    return factory.getObject();
}

这里不分析具体原因了,感兴趣的话可以参考博客:@Bean注册Bean,你有多了解?

二、客户端调用的问题

创建一个SpringBoot客户端

package com.example.spring.client.jmx;

import com.example.spring.jmx.IJmxTestBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.jmx.access.MBeanProxyFactoryBean;
import org.springframework.jmx.support.MBeanServerConnectionFactoryBean;
import org.springframework.util.ClassUtils;

import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import java.io.IOException;

@SpringBootApplication
public class JmxSpringBootStartClient {

    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(JmxSpringBootStartClient.class, args);
        IJmxTestBean jmxTestBean = applicationContext.getBean(IJmxTestBean.class);
        System.out.println(jmxTestBean.getName());
        System.out.println(jmxTestBean.add(1, 2));
    }

    @Bean
    public MBeanServerConnection mBeanServerConnection() throws IOException {
        MBeanServerConnectionFactoryBean factoryBean = new MBeanServerConnectionFactoryBean();
        factoryBean.setServiceUrl("service:jmx:rmi://localhost/jndi/rmi://localhost:1099/jmxrmi");
        factoryBean.setConnectOnStartup(false);
        factoryBean.afterPropertiesSet();
        return factoryBean.getObject();
    }

    @Bean
    public IJmxTestBean proxy() throws IOException, MalformedObjectNameException {
        MBeanProxyFactoryBean factoryBean = new MBeanProxyFactoryBean();
        factoryBean.setObjectName(new ObjectName("com.example.spring.jmx:name=testBean4"));
        factoryBean.setProxyInterface(IJmxTestBean.class);
        factoryBean.setServer(mBeanServerConnection());
        factoryBean.afterPropertiesSet();
        return (IJmxTestBean) factoryBean.getObject();
    }
}

执行结果如下所示
在这里插入图片描述

三、是否面向接口的问题

在上面的服务端或是客户端,我们将com.example.spring.jmx.AnnotationTestBean类型的bean转为MBean进行暴露,不知道大家有没有疑问,这个类一定需要实现一个接口吗?我们不妨去掉实现的接口,修改类如下:

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 {
    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(defaultValue = "foo", persistPeriod = 300)
    public String getName() {
        return name;
    }

    @ManagedAttribute(description = "The Name Attribute",
            currencyTimeLimit = 20,
            defaultValue = "bar",
            persistPolicy = "OnUpdate")
    public void setName(String name) {
        this.name = 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 long myOperation() {
        return 0;
    }

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

然后再次启动服务端,从这个结果看是没有问题的、
在这里插入图片描述
但是问题出在了客户端上,为啥呢?现在对应的类没有实现IJmxTestBean接口,关于proxy的定义必须修改了。首先proxyInterface或managementInterface这两个参数必须要传。
在这里插入图片描述
而现在没有所谓的接口,能不能传递类呢?我们尝试修改如下

@Bean
public AnnotationTestBean proxy() throws IOException, MalformedObjectNameException {
    MBeanProxyFactoryBean factoryBean = new MBeanProxyFactoryBean();
    factoryBean.setObjectName(new ObjectName("com.example.spring.jmx:name=testBean4"));
    factoryBean.setProxyInterface(AnnotationTestBean.class);
    factoryBean.setServer(mBeanServerConnection());
    factoryBean.afterPropertiesSet();
    return (AnnotationTestBean) factoryBean.getObject();
}

运行客户端这个时候抛出如下的异常

Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.example.spring.jmx.AnnotationTestBean]: Factory method 'proxy' threw exception; nested exception is java.lang.IllegalArgumentException: [com.example.spring.jmx.AnnotationTestBean] is not an interface
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	... 19 common frames omitted
Caused by: java.lang.IllegalArgumentException: [com.example.spring.jmx.AnnotationTestBean] is not an interface
	at org.springframework.aop.framework.AdvisedSupport.addInterface(AdvisedSupport.java:210) ~[spring-aop-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.aop.framework.ProxyFactory.<init>(ProxyFactory.java:72) ~[spring-aop-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.jmx.access.MBeanProxyFactoryBean.afterPropertiesSet(MBeanProxyFactoryBean.java:97) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at com.example.spring.client.jmx.JmxSpringBootStartClient.proxy(JmxSpringBootStartClient.java:42) [classes/:na]
	at com.example.spring.client.jmx.JmxSpringBootStartClient$$EnhancerBySpringCGLIB$$d645d62b.CGLIB$proxy$0(<generated>) ~[classes/:na]
	at com.example.spring.client.jmx.JmxSpringBootStartClient$$EnhancerBySpringCGLIB$$d645d62b$$FastClassBySpringCGLIB$$a38b3d09.invoke(<generated>) ~[classes/:na]
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	at com.example.spring.client.jmx.JmxSpringBootStartClient$$EnhancerBySpringCGLIB$$d645d62b.proxy(<generated>) ~[classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_121]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_121]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_121]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_121]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.10.RELEASE.jar:5.1.10.RELEASE]
	... 20 common frames omitted

在这里插入图片描述
那么此时该如何办呢?当然了面向接口编程,就可以避免这这种问题了,不过我们可以尝试从Java原生接口来解决这个问题。首先去掉代理相关配置
在这里插入图片描述
通过原生java获取MBean

ConfigurableApplicationContext applicationContext = SpringApplication.run(JmxSpringBootStartClient.class, args);
MBeanServerConnection mbsc = (MBeanServerConnection) applicationContext.getBean("mBeanServerConnection");
String name = "com.example.spring.jmx:name=testBean4";
ObjectName mbeanName = new ObjectName(name);
MBeanInfo mBeanInfo = mbsc.getMBeanInfo(mbeanName);
System.out.println(mBeanInfo);
ObjectInstance objectInstance = mbsc.getObjectInstance(mbeanName);
Object add = mbsc.invoke(mbeanName, "add", new Object[]{1, 2}, new String[]{int.class.getName(), int.class.getName()});
System.out.println(add);

此时控制台打印如下信息

javax.management.modelmbean.ModelMBeanInfoSupport[description=My Managed Bean, attributes=[ModelMBeanAttributeInfo: Age ; Description: The Age Attribute ; Types: int ; isReadable: true ; isWritable: false ; Descriptor: currencyTimeLimit=15, default=, descriptorType=attribute, displayName=Age, getMethod=getAge, name=Age, ModelMBeanAttributeInfo: Name ; Description: The Name Attribute ; Types: java.lang.String ; isReadable: true ; isWritable: true ; Descriptor: currencyTimeLimit=20, default=foo, descriptorType=attribute, displayName=Name, getMethod=getName, name=Name, persistPeriod=300, persistPolicy=OnUpdate, setMethod=setName], constructors=[], operations=[ModelMBeanOperationInfo: add ; Description: Add two numbers ; Descriptor: descriptorType=operation, displayName=add, name=add, role=operation ; ReturnType: int ; Signature: int, int, , ModelMBeanOperationInfo: getName ; Description: getName ; Descriptor: descriptorType=operation, displayName=getName, name=getName, role=getter, visibility=(4) ; ReturnType: java.lang.String ; Signature: , ModelMBeanOperationInfo: setName ; Description: The Name Attribute ; Descriptor: descriptorType=operation, displayName=setName, name=setName, role=setter, visibility=(4) ; ReturnType: void ; Signature: java.lang.String, , ModelMBeanOperationInfo: getAge ; Description: The Age Attribute ; Descriptor: descriptorType=operation, displayName=getAge, name=getAge, role=getter, visibility=(4) ; ReturnType: int ; Signature: ], notifications=[], descriptor=currencyTimeLimit=15, descriptorType=mbean, displayName=com.example.spring.jmx.AnnotationTestBean, log=true, logFile=jmx.log, name=com.example.spring.jmx.AnnotationTestBean, persistLocation=foo, persistName=bar, persistPeriod=200, persistPolicy=OnUpdate, visibility=1]
3

![在这里插入图片描述](https://img-blog.csdnimg.cn/20201126183600644.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzM3NjA3OTQ1,size_16,color_FFFFFF,t_70#pic_center)

总结

本章学习在Spring Boot中JMX的使用,其实与前面都是大同小异的,只是通过Spring Boot的自动配置功能减少了一些功能bean的注册而已

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

lang20150928

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

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

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

打赏作者

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

抵扣说明:

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

余额充值