Spring整合JMX
一、优点
1、可以自动探测实现MBean接口的MBean对象,而且可以将一个普通的Spring Bean注册为MBean;
2、定制管理MBean的接口,根据需要暴露特定管理MBean的操作;
3、使用注解定义MBean管理接口;
4、可以实现对本地和远程MBean的代理。
二、实现方式
基于配置文件的实现
标准MBEAN
定义接口
XXXMBean(接口名以MBean结尾,接口中暴露了对外的方法)
接口实现
XXX(注意实现的名称和接口之间的关系)
配置文件
<?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-2.5.xsd">
<!--MBean Server初始化 -->
<bean id="mbeanServer" class="org.springframework.jmx.export.MBeanExporter">
<!--MBean的探测策略,这里是AUTODETECT_ALL自动探测,默认值为AUTODETECT_ALL,所以这里可以不用配置 -->
<property name="autodetectModeName">
<value>AUTODETECT_ALL</value>
</property>
<!-- 如果不使用自动探测 ,这里是手动注册的配置 -->
<property name="beans">
<map>
<entry key="MBEAN_NAME" value-ref="xxx"/>
</map>
</property>
</bean>
<!--MBean初始化 其中的name="mydomain:xxx=XxxMBean"可以根据需要自行定义-->
<bean id="xxx" class="com.sinosoft.one.demo.jmx.Xxx"/>
<bean name="testMBean:xxx=XxxMBean" class="com.sinosoft.one.demo.jmx.XXX">
<!--实现类中一些需要初始化的属性 -->
<property name="propertyOne" value="val1"/>
<property name="propertyTwo" value="val2"/>
</bean>
</beans>
对于探测模式autodetectModeName属性,Spring提供了4个取值:
AUTODETECT_NONE 不启用自动探测,需要手动向MBean Server进行注册,即通过MBeanExporter的beans属性进入注册;
AUTODETECT_MBEAN 在当前IOC容器中进行查找MBean组件;
AUTODETECT_ASSEMBLER 设置根据MBeanInfoAssembler的策略进行探测;
AUTODETECTALL 自动探测,是AUTODETECTMBEAN和AUTODETECT_ASSEMBLER的并集。
普通Bean注册为MBean
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-2.5.xsd">
<!--MBean Server初始化 -->
<bean id="mbeanServer" class="org.springframework.jmx.export.MBeanExporter">
<!--MBean的探测策略,这里是AUTODETECT_ALL自动探测,默认值为AUTODETECT_ALL,所以这里可以不用配置 -->
<property name="autodetectModeName">
<value>AUTODETECT_ALL</value>
</property>
<!-- 对于普通的Bean ,需要在这里手动注册 -->
<property name="beans">
<map>
<entry key="MBEAN_NAME" value-ref="xxx"/>
</map>
</property>
</bean>
<!--普通Spring Bean初始化-->
<bean id="xxx" class="com.sinosoft.one.demo.jmx.Xxx"/>
</beans>
因为普通的Bean没有对应的MBean接口,所以默认情况下,该类中public的成员都会暴露出来,通过MBean Server可以管理。实际上,系统中MBean的某些属性或方法可能不需要暴露给外部进行管理,为了克服这种缺点,Spring提供了基于方法列表和接口定制的功能,可以将你所感兴趣的属性或方法暴露给外部管理。
管理暴露的方法
方法一:配置方法列表
<!-- 定义assembler -->
<bean id="assembler" class="org.springframework.jmx.export.
assembler.MethodNameBasedMBeanInfoAssembler">
<!--配置方法列表 -->
<property name="managedMethods" value="display" />
</bean>
方法二:接口定制
package com.sinosoft.one.demo.jmx.interface;
/**
*定制对外方法的接口
*/
public interface MethodManagerInterface {
public String display(String arguments);
}
<!-- 定义assembler -->
<bean id="assembler" class="org.springframework.jmx.export.
assembler.MethodNameBasedMBeanInfoAssembler">
<!--配置方法列表 -->
<property name="managedInterfaces" >
<list>
<value>
com.sinosoft.one.demo.MethodManagerInterface
</value>
</list>
</property>
</bean>
最后,在bean mbeanServer中添加assembler属性
<property name="assembler" ref="assembler" />
基于注解的实现 下边是一个实现例子
package com.sinosoft.one.demo.jmx;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedOperationParameter;
import org.springframework.jmx.export.annotation.ManagedOperationParameters;
import org.springframework.jmx.export.annotation.ManagedResource;
/**
* JMX实现的动态控制日志级别
*
* @author gwt
*/
@ManagedResource(objectName = LoggerManager.MBEAN_NAME, description = "It's a program to manage log4j ")
public class LoggerManager {
/**
* MBean Name
*/
public static final String MBEAN_NAME = "log4j:logger=Log4j";
/**
* 用来记录日志信息
*/
private static org.slf4j.Logger managerLogger = LoggerFactory
.getLogger(LoggerManager.class);
/**
* 获取日志的默认级别
*
* @return
*/
@ManagedAttribute
public String getRootLoggerLevel() {
Logger logger = Logger.getRootLogger();
String rootLevel = logger.getEffectiveLevel().toString();
managerLogger.info("Level of the root logger is {}", rootLevel);
return logger.getEffectiveLevel().toString();
}
/**
* 设置日志的默认级别
*
* @param newLevel
*/
@ManagedAttribute(description = "set rootlogger's level")
public void setRootLoggerLevel(String newLevel) {
Logger logger = Logger.getRootLogger();
if (!newLevel.equals(logger.getEffectiveLevel().toString())) {
logger.setLevel(Level.toLevel(newLevel));
}
managerLogger.info("设置日志默认级别为{}", newLevel);
}
/**
* 获取指定工程的日志级别
*
* @return
*/
@ManagedAttribute(description = "get project log level")
@ManagedOperationParameters({ @ManagedOperationParameter(name = "projectName", description = "project name") })
public String getProjectLoggerLevel(String projectName) {
return getLoggerLevel(projectName);
}
/**
* 获取Logger的日志级别.
*/
@ManagedOperation(description = "get appoint logger name ")
@ManagedOperationParameters({ @ManagedOperationParameter(name = "loggerName", description = "Logger name") })
public String getLoggerLevel(String loggerName) {
Logger logger = Logger.getLogger(loggerName);
return logger.getEffectiveLevel().toString();
}
/**
* 设置logger级别
*
* @param loggerName 日志名称
* @param newLevel 新的等级
*/
@ManagedAttribute
@ManagedOperationParameters({
@ManagedOperationParameter(name = "loggerName", description = "logger name"),
@ManagedOperationParameter(name = "newLevel", description = "the level want to set") })
public void setLoggerLevel(String loggerName, String newLevel) {
Logger logger = Logger.getLogger(loggerName);
logger.setLevel(Level.toLevel(newLevel));
}
/**
* 设置工程logger级别
*
* @param ProjectName 工程名
* @param newLevel 新的等级
*/
@ManagedAttribute
@ManagedOperationParameters({
@ManagedOperationParameter(name = "projectName", description = "logger name"),
@ManagedOperationParameter(name = "newLevel", description = "the level want to set") })
public void setProjectLevel(String ProjectName, String newLevel) {
setLoggerLevel(ProjectName, newLevel);
}
}
上面@ManagedResource表示指定该类的实例作为MBean注册到MBean Server中,然后可以通过对属性和方法分别使用@ManagedAttribute和@ManagedOperation来指定暴露的属性和方法。有关这些注解的详细内容,可以查阅相关文档。
配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<description>JMX服务端配置</description>
<!-- 配置MBean自动注册 -->
<context:mbean-export default-domain="demo" registration="replaceExisting" />
<!-- Log4j控制的 MBean -->
<bean class="com.sinosoft.one.demo.jmx.Log4jManager">
<property name="projectLoggerName" value="com.sinosoft.one.demo"/>
</bean>
</beans>
三、使用HttpURLConnection进行JMX访问
在web.xml中添加配置
<servlet>
<servlet-name>jolokia-agent</servlet-name>
<servlet-class>org.jolokia.http.AgentServlet</servlet-class>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jolokia-agent</servlet-name>
<url-pattern>/jolokia/*</url-pattern>
</servlet-mapping>
HttpURLConnection访问的方法
package com.sinosoft.one.demo.util;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSONObject;
public class HttpConnectUtil {
private Logger logger = LoggerFactory.getLogger(getClass());
private String url;
public String sendMessage() {
url = "http://localhost:8080/demo/jolokia/";
logger.info("=================开始获取信息,地址{}=================" , url);
String responseMessage = "";
StringBuffer resposne = new StringBuffer();
HttpURLConnection httpConnection = null;
DataOutputStream out = null;
BufferedReader reader = null;
try {
URL urlPost = new URL(url);
httpConnection = (HttpURLConnection) urlPost.openConnection();
httpConnection.setDoOutput(true);
httpConnection.setDoInput(true);
// 参数长度太大,不能用get方式
httpConnection.setRequestMethod("POST");
// 不使用缓存
httpConnection.setUseCaches(false);
// URLConnection.setInstanceFollowRedirects是成员函数,仅作用于当前函数
httpConnection.setInstanceFollowRedirects(true);
// 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的
// 意思是正文是urlencoded编码过的form参数
httpConnection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
// 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成,
// 要注意的是connection.getOutputStream会隐含的进行connect。
// 实际上只是建立了一个与服务器的tcp连接,并没有实际发送http请求。
logger.debug("打开连接:" + url);
httpConnection.connect();
out = new DataOutputStream(httpConnection.getOutputStream());
// The URL-encoded contend
// 正文,正文内容其实跟get的URL中'?'后的参数字符串一致
List list=new ArrayList();
list.add("com.sinosoft.one.demo");
list.add("DEBUG");
JSONObject jsonObject=new JSONObject();
jsonObject.put("arguments", list);
jsonObject.put("mbean", "log4j:logger=Log4j");
jsonObject.put("type", "exec");
jsonObject.put("operation", "setProjectLevel");
System.out.println(jsonObject);
// // DataOutputStream.writeBytes将字符串中的16位的unicode字符以8位的字符形式写道流里面
out.writeBytes(jsonObject.toJSONString());
// flush and close
out.flush();
reader = new BufferedReader(new InputStreamReader(
httpConnection.getInputStream()));
while ((responseMessage = reader.readLine()) != null) {
resposne.append(responseMessage);
}
if (!"failure".equals(resposne.toString())) {
logger.info("success send to JMX");
} else {
logger.debug("failure send to JMX");
}
// 将该url的配置信息缓存起来
return resposne.toString();
} catch (IOException e) {
logger.error("连接失败,url={}" , url);
return "failed";
} finally {
try {
if (null != out) {
out.close();
}
if (null != reader) {
reader.close();
}
if (null != httpConnection) {
httpConnection.disconnect();
}
} catch (Exception e2) {
logger.error("http connection close error:{}", e2);
}
}
}
}
使用httpUrlConnection来访问JMX需要用JSON格式定义一下参数
必选 类型及范围 说明
mbean true string MBean名称,取值为log:name=LogConfigs
operation true string 操作方法名称,取值为addLogUrl
arguments false array 参数列表,如果没有参数,可省略
type true String 操作类型,取值为exec