看tomcat源码的时候,看到一个MBean的概念,从而了解到了JMX. JMX,全称Java Management Extensions,是管理java的一种扩展.它提供了一个观察、改变正在运行的java程序变量的一个途径.
JMX能做什么
首先看一个示例程序,从网上找到的示例:
public interface UserMBean {
String getUsername();
void setUsername(String username);
String getPassword();
void setPassword(String password);
int add(int x, int y);
}
public class User implements UserMBean {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword() {
return password;
}
@Override
public void setPassword(String password) {
this.password = password;
}
public int add(int x, int y) {
return x + y;
}
}
import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
public class MBeanTest {
public static void main(String[] args) {
try {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("jmxdemo:type=User");
User user = new User();
beanServer.registerMBean(user, objectName);
String oldusername = null;
String oldpassword = null;
while (true) {
if (oldusername != user.getUsername() || oldpassword != user.getPassword()) {
System.out.println(user.getUsername() + ',' + user.getPassword());
oldusername = user.getUsername();
oldpassword = user.getPassword();
}
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行MBeanTest的main方法后,在控制台通过jconsole
命令启动java控制台监控
选中我们的测试类:MBeanTest连接,在MBeans标签下,找到我们的jmxdemo,可以看到User下的两个属性和一个方法.此时修改username
在main方法中,当User对象的属性发生变化的时候会在控制台打印新的属性值,修改后在控制台可以看到我们经过jconsole修改的属性已经生效.
JMX是什么
看下Apache的官方说明:
The JMX technology provides the tools for building distributed, Web-based, modular and
dynamic solutions for managing and monitoring devices, applications, and service-driven networks. By design,
this standard is suitable for adapting legacy systems, implementing new management and monitoring solutions,
and plugging into those of the future.
JMX为建立分布式,基于web技术,模块化动态管理和监控设备,应用,基于服务的网络提供了一个工具.根据设计,这个标准可以适配旧系统,也可以为新的系统提供一个增强的管理和监控功能.
JMX的架构分为三层
基础层
最底层的是MBean,也就是我们需要管理的类.MBean有三种:StandardMBean,DynamicMBean,OpenBean
- StandardMBean 大多数的MBean通过类名以MBean结尾来指定当前类为StandardMBean,如上边的UserMBean,然后通过代理来和MBean做交互:
public interface CacheControlMBean {
//如果是需要做远程调用的,最好是在方法中抛出IOException,以免在做代理的时候,这个异常被UndeclaredThrowableException包裹
int getSize() throws IOException;
void setSize(int size) throws IOException;
int getUsage() throws IOException;
int dropOldest(int n) throws IOException;
}
public void main() {
MBeanServer mbs = ...;
Integer sizeI = (Integer) mbs.getAttribute(objectName, "Size");
int size = sizeI.intValue();
if (size > desiredSize) {
mbs.invoke(objectName,
"dropOldest",
new Integer[]{new Integer(size - desiredSize)},
new String[]{"int"});
}
MBeanServer mbs = ...;
CacheControlMBean cacheControl = (CacheControlMBean)
MBeanServerInvocationHandler.newProxyInstance(mbs,
objectName,
CacheControlMBean.class,
false);
int size = cacheControl.getSize();
if (size > desiredSize)
cacheControl.dropOldest(size - desiredSize);
}
- Dynamic MBeans
Dynamic MBeans用于在编译阶段未生成的接口或类,dynamic MBean必须实现javax.management.DynamicMBean接口,所有的属性,方法都在运行时定义
- OpenBean MBean中的属性只能使用基本的java类型,在jdk1.6之前,如果需要使用CompositeData来表示这个属性是OpenType类型,具体可以参考JMX的官方文档CompositeData.在JDK1.6中,引入了MXBean的概念,它会自动把CompositeType转换为指定的类型,使得MBean的属性可以是任何java类型. 要标示一个类为MXBean有以下方式:
//public并且类名以MXBean结尾
public interface WhatsitMXBean {}
//添加@MXBean注解
@MXBean
public interface Whatsit1Interface {}
@MXBean(true)
public interface Whatsit2Interface {}
尽管这样,在声明这个复杂类的时候,还是需要标示这个类是CompositeType,以便能够正常做转换.java文档中提供了4种方式:
Static {@code from} method:
public class NamedNumber {
public int getNumber() {return number;}
public String getName() {return name;}
private NamedNumber(int number, String name) {
this.number = number;
this.name = name;
}
public static NamedNumber from(CompositeData cd) {
return new NamedNumber((Integer) cd.get("number"),
(String) cd.get("name"));
}
private final int number;
private final String name;
}
Public constructor with @ConstructorProperties annotation:
public class NamedNumber {
public int getNumber() {return number;}
public String getName() {return name;}
@ConstructorProperties({"number", "name"})
public NamedNumber(int number, String name) {
this.number = number;
this.name = name;
}
private final int number;
private final String name;
}
Setter for every getter:
public class NamedNumber {
public int getNumber() {return number;}
public void setNumber(int number) {this.number = number;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public NamedNumber() {}
private int number;
private String name;
}
Interface with only getters:
public interface NamedNumber {
public int getNumber();
public String getName();
}
适配层
MBeanServer,主要是提供对资源的注册和管理,和远程做通信
接入层
提供远程访问的入口,接口是javax.management.remote.JMXConnector Java提供了几种方式来做接入层
- 直接在服务器端启动jconsole查看,也就是示例代码中展示的.
- 通过页面来访问 修改MBeanTest如下:
public class MBeanTest {
public static void main(String[] args) {
try {
// MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
// ObjectName objectName = new ObjectName("jmxdemo:type=User");
// User user = new User();
// beanServer.registerMBean(user, objectName);
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("jmxdemo:type=User");
User user = new User();
server.registerMBean(user, objectName);
ObjectName adapterName = new ObjectName("UserAgent:name=htmladapter,port=8082");
HtmlAdaptorServer adapter = new HtmlAdaptorServer();
server.registerMBean(adapter, adapterName);
adapter.start();
String oldusername = null;
String oldpassword = null;
while (true) {
if (oldusername != user.getUsername() || oldpassword != user.getPassword()) {
System.out.println(user.getUsername() + ',' + user.getPassword());
oldusername = user.getUsername();
oldpassword = user.getPassword();
}
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
通过浏览器访问:http://localhost:8082.
需要说明的是jdk中并未包含HtmlAdaptorServer类,需要到apache网站下载jmx-1_2_1-ri.zip解压后,将lib文件夹下的两个jar包加入到classpath中.
- 自己做二次开发 再次修改MBeanTest如下:
public class MBeanTest {
public static void main(String[] args) {
try {
MBeanServer beanServer = ManagementFactory.getPlatformMBeanServer();
ObjectName objectName = new ObjectName("jmxdemo:type=User");
User user = new User();
beanServer.registerMBean(user, objectName);
//这个步骤很重要,注册一个端口,绑定url后用于客户端通过rmi方式连接JMXConnectorServer
LocateRegistry.createRegistry(9999);
//URL路径的结尾可以随意指定,但如果需要用Jconsole来进行连接,则必须使用jmxrmi
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
JMXConnectorServer jcs = JMXConnectorServerFactory.newJMXConnectorServer(url, null, beanServer);
jcs.start();
String oldusername = null;
String oldpassword = null;
while (true) {
if (oldusername != user.getUsername() || oldpassword != user.getPassword()) {
System.out.println(user.getUsername() + ',' + user.getPassword());
oldusername = user.getUsername();
oldpassword = user.getPassword();
}
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
同时新建一个client类:
public class ClientTest {
public static void main(String[] args) {
try {
JMXServiceURL url = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://localhost:9999/jmxrmi");
JMXConnector jmxc = JMXConnectorFactory.connect(url, null);
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
ObjectName mbeanName = new ObjectName("jmxdemo:type=User");
//设置指定Mbean的特定属性值 //这里的setAttribute、getAttribute操作只能针对bean的属性
//例如对getName或者setName进行操作,只能使用Name,需要去除方法的前缀
mbsc.setAttribute(mbeanName, new Attribute("Password", "pass"));
mbsc.setAttribute(mbeanName, new Attribute("Username", "user"));
String password = (String) mbsc.getAttribute(mbeanName, "Password");
String username = (String) mbsc.getAttribute(mbeanName, "Username");
System.out.println("password=" + password + ";username=" + username);
UserMBean proxy = MBeanServerInvocationHandler.newProxyInstance(mbsc, mbeanName, UserMBean.class, false);
proxy.test();//在server端(MBeanTest)调用test方法
mbsc.invoke(mbeanName, "test", null, null);//在server端(MBeanTest)调用test方法
//invoke调用bean的方法,只针对非设置属性的方法,不能对get和set方法做调用
//mbsc.invoke(mbeanName, "getUsername", null, null);//会报错
} catch (Exception e) {
System.out.println(e);
}
}
}
在执行Client的main方法前,在User类中添加方法public void test(){System.out.println("test!!!");}
,同时UserMBean中添加接口public void test();
当然如果没有client类,也可以通过jconsole使用远程访问:
为什么要用
JMX有两大功能,一个是提供了对存变量的修改,一个是对内存中java变量的查看. 首先看怎样对内存中的变量做动态修改.目前实现动态修改内存中的变量有多种方式,如每隔特定的时间去某个服务中心拉取变量的值,或者服务中心主动推送值发生变化的变量,并且目前这种方式都可以通过中间件的方式实现,对业务流程无入侵.但这种方式需要对接入系统的框架做适配. 通过JMX则对系统使用的框架没有要求. Tomcat就是使用了JMX来实现类实例的控制,它通过JmxEnabled和LifecycleMBeanBase来对加载的类做统一的控制,同时LifecycleMBeanBase也管控了Tomcat加载的所有的类的实例.
以上总结了JMX是什么,怎么用,以及什么时候用的问题,感觉还是挺不错的.目前了解到JBOSS是通过JMX来实现的,后边有需要用到的场景的话可以再详细了解下.