JMX整理(Bean用于远程调用)

转载自:http://www.cnblogs.com/langtianya/p/4902351.html


What and Why JMX

 

JMX的全称为Java Management Extensions. 顾名思义,是管理Java的一种扩展。这种机制可以方便的管理正在运行中的Java程序。常用于管理线程,内存,日志Level,服务重启,系统环境等。

试想,一个正在运行中的程序,我们如果想改变程序中的一些属性,可以通过什么方法呢?可能有这么几个方法:

  • 对于服务器式的程序,可以制作管理页面,通过HTTP post与servlet来更改服务器端程序的属性。

  • 对于服务器式的程序,还可以通过SOAP方式。但这需要程序开启了SOAP端的服务。

  • 可以使用RMI远程调用。但这需要设计开启RMI服务。

  • 如果是SWT或Swing的程序,则可以通过设计UI管理界面,使用户可以和程序内部交互。

  • 还有一种方式,是将可改变的属性放入配置文件XML,properties或数据库,程序轮询配置文件,以求获取最新的配置。

上面几个方法都是常见,但却无法通用的。所谓通用,是指解决方案符合一个标准,使得任何符合此标准的工具都能解析针对此标准的方案实现。这样A公司设计的方案,B公司可以根据标准来解析。JMX就是Java管理标准。

JMX的构成

JMX由三部分组成:

  1. 程序端的Instrumentation, 我把它翻译成可操作的仪器。这部分就是指的MBean. MBean类似于JavaBean。最常用的MBean则是Standard MBean和MXBean.

  2. 程序端的JMX agent. 这部分指的是MBean Server. MBean Server则是启动与JVM内的基于各种协议的适配器。用于接收客户端的调遣,然后调用相应的MBeans.

  3. 客户端的Remote Management. 这部分则是面向用户的程序。此程序则是MBeans在用户前投影,用户操作这些投影,可以反映到程序端的MBean中去。这内部的原理则是client通过某种协议调用agent操控MBeans. 

JMX agent与Remote Management之间是通过协议链接的,这协议可能包含:

  • HTTP

  • SNMP

  • RMI

  • IIOP

JMX agent中有针对上面协议的各种适配器。可以解析通过相应协议传输过来的数据。Remote Management client则可以用现成的工具,如JConsole, 也可以自己书写java code。

接下来,我们看是一步一步,通过代码示例来熟悉JMX各种特性。

受监管的程序

 

JMX是用于管理java程序的,为了试验,我们首先需要写一个小程序Echo。然后加入JMX对此程序进行监管。这个程序就是每隔10秒钟,输出一个预先定义好的Message。

首先定义Message类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public  class  Message {
     private  String title, body, by;
     
     public  Message() {
         title= "none" ;
         body= "none" ;
         by= "none" ;
     }
     
     public  String getTitle() {
         return  title;
     }
     
     public  void  setTitle(String title) {
         this .title = title;
     }
     
     public  String getBody() {
         return  body;
     }
     
     public  void  setBody(String body) {
         this .body = body;
     }
     
     public  String getBy() {
         return  by;
     }
     
     public  void  setBy(String by) {
         this .by = by;
     }
     
     public  void  echo() {
         System.out.println( "<" +title+ ">" );
         System.out.println(body);
         System.out.println( "by "  + by);
     }
}

定义Echo类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public  class  Echo {
     public  static  Message msg =  new  Message();
     public  static  boolean  running= true ;
     public  static  boolean  pause= false ;
     
     public  static  void  main(String[] args) {
         // 开启JMX Agent。如果不需要JMX,只是单独运行程序,请屏蔽掉下面这行代码。
         new  MessageEngineAgent().start();
         
         while (running) {
             try  {
                 Thread.sleep( 10000 );
             catch  (InterruptedException e) {
                 e.printStackTrace();
             }
             if  (!pause) msg.echo();
         }
     }
}

执行Echo,得到每过10秒钟,则会输出一个消息:

<none>

none

by none

MBean

接下来,开始设计管理程序的MBean. 在设计MBean之前,必须要先了解MBean都包括哪几种。MBean包含:

  1. Standard MBean

  2. Dynamic MBean

  3. Open MBean

  4. Model MBean

  5. MXBean

最常用最简单的两个就是Standard MBean与MXBean. 首先搞清楚,MBean和MXBean的区别是什么。

Standard MBean与MXBean的区别

这里有一些细节,列出了两只的区别http://docs.oracle.com/javase/7/docs/api/javax/management/MXBean.html 。它 们最根本的区别是,MXBean在Agent与Client之间会将自定义的Java类型转化为Java Open Type. 这样的好处是Client无需获取MXBean的接口程序,便可访问和操作MXBean的投影。如果使用MBean, client则必须先将MBean的接口程序放到classpath中,否则无法解析MBean中自定义类型。

基于上述原因,我将使用MXBean做为例子。实际上,JVM自带的几乎全是MXBean。

实现

定义MXBean的接口,注意命名规则,必须以MXBean结尾。

1
2
3
4
5
6
7
8
9
10
11
public  interface  MessageEngineMXBean {
     //结束程序。
     public  void  stop();
     //查看程序是否暂停。
     public  boolean  isPaused();
     //暂停程序或者继续程序。
     public  void  pause( boolean  pause);
     public  Message getMessage();
     //修改message
     public  void  changeMessage(Message m);
}

实现部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public  class  MessageEngine  implements  MessageEngineMXBean {
     private  final  Message message = Echo.msg;
     
     @Override
     public  void  stop() {
         Echo.running =  false ;
     }
 
     @Override
     public  boolean  isPaused() {
         return  Echo.pause;
     }
 
     @Override
     public  void  pause( boolean  pause) {
         Echo.pause = pause;
     }
 
     @Override
     public  Message getMessage() {
         return  this .message;
     }
 
     @Override
     public  void  changeMessage(Message m) {
         this .message.setBody(m.getBody());
         this .message.setTitle(m.getTitle());
         this .message.setBy(m.getBy());
     }
}

Notification

在JMX中,还有一个重要的概念是Notification。构成Notification的几个接口是:

  1. NotificationEmitter, 只要实现此接口,就可以发出Notification和订阅Notification. 类NotificationBroadcasterSupport则实现了NotificationEmitter.

  2. NotificationListener, 实现此接口的可以订阅JMX的Notification。

  3. Notification, 消息本身。

修改MessageEngine, 使它在pause的时候发送通知给订阅者。我把修改的部分贴上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public  class  MessageEngine  extends  NotificationBroadcasterSupport  implements  MessageEngineMXBean {
     private  long  sequenceNumber =  1 ;
     ... ...
     ... ...
     public  MessageEngine() {
         addNotificationListener( new  NotificationListener() {
             @Override
             public  void  handleNotification(Notification notification, Object handback) {
                 System.out.println( "*** Handling new notification ***" );
                 System.out.println( "Message: "  + notification.getMessage());
                 System.out.println( "Seq: "  + notification.getSequenceNumber());
                 System.out.println( "*********************************" );
             }
         },  null null );
     }
     ... ...
     ... ...
     @Override
     public  void  pause( boolean  pause) {
         Notification n =  new  AttributeChangeNotification( this ,
                 sequenceNumber++, System.currentTimeMillis(),
                 "Pause changed" "Paused" "boolean" ,
                 Echo.pause, pause);
         Echo.pause = pause;
         this .sendNotification(n);
     }
     ... ...
     ... ...
     // 规定可以发送的Notification Type,不在Type list中的Notification不会被发送。
     @Override
     public  MBeanNotificationInfo[] getNotificationInfo() {
         String[] types =  new  String[]{
             AttributeChangeNotification.ATTRIBUTE_CHANGE
         };
         
         String name = AttributeChangeNotification. class .getName();
         String description =  "An attribute of this MBean has changed" ;
         MBeanNotificationInfo info = 
                 new  MBeanNotificationInfo(types, name, description);
         return  new  MBeanNotificationInfo[]{info};
     }
}

Client端如何使用Notification,可以查看后面的Client一节。

JMX Agent

如果说Agent只是被Local使用,比如本地的JConsole,只需要开启MBeanServer,并注册MBean即可。不需要配置协议适配器。但如果需要远程管理,比如远程的JConsole或者自定义的管理器,则还需要配置两者相互打交道的协议适配器。

1
2
3
4
5
6
7
8
9
10
11
12
public  class  MessageEngineAgent {
     public  void  start() {
         MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 
         try  {
             ObjectName mxbeanName =  new  ObjectName( "com.example:type=MessageEngine" );
             MessageEngineMXBean mxbean =  new  MessageEngine();
             mbs.registerMBean(mxbean, mxbeanName);
         catch  (Exception e) {
             e.printStackTrace();
         }
     }
}

因为java默认自带的了JMX RMI的连接器。所以,只需要在启动java程序的时候带上运行参数,就可以开启Agent的RMI协议的连接器。

1
2
3
4
java -Dcom.sun.management.jmxremote.port = 9999  \
      -Dcom.sun.management.jmxremote.authenticate =  false  \
      -Dcom.sun.management.jmxremote.ssl =  false  \
      jmx.Echo

认证与授权

JMX的认证与授权是非常必要的,我们不可能允许任何client都能连接我们的Server。JMX的认证和授权可以复杂的使用LDAP, SSL。也可以使用最简单的文件存储用户信息方式。本文作为启蒙,只给出最简单的认证方式。

在java启动的时候,添加运行参数:

1
2
3
4
5
6
java -Dcom.sun.management.jmxremote.port = 9999  \
      -Dcom.sun.management.jmxremote.authenticate =  true  \
      -Dcom.sun.management.jmxremote.password.file = pathTo/my.password \
      -Dcom.sun.management.jmxremote.access.file = pathTo/my.access \
      -Dcom.sun.management.jmxremote.ssl =  false  \
      jmx.Echo

my.password里面定义了用户名和密码:

1
2
user1 password1
user2 password2

my.access里面定义了用户授权信息:

1
2
3
4
user1 readOnly
user2 readWrite \
       create jmx.*,javax.management.timer.* \
       unregister

更详细的内容可以从这里找到: http://docs.oracle.com/javase/7/docs/technotes/guides/management/agent.html 。

现在可以启动程序了。启动以后,我们使用下面的Client来连接我们写的JMX Agent.

JMX Client

JConsole

JDK提供了一个工具在jdk/bin目录下面,这就是JConsole。使用JConsole可以远程或本地连接JMX agent。如下图所以:

无论是远程还是本地,连接进去所看到的都一样。进去MBeans面板以后,找到MessageEngine。MessageEngine下面有 Attributes, Operations和Notification。可以浏览MessageEngine中的Attributes并更改那些可写的属性。也可以执行 Operations下面的stop, pause方法。此外,必须订阅Notifications才能收到消息。

JConsole有缺点,它只能对MXBean中的主要基本类型做修改,但不能修改复杂类型。

Custom Client

我们也可以用java写client调用Agent。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public  class  Client  implements  NotificationListener {
 
     public  static  void  main(String[] args) {
         try  {
             new  Client().start();
         catch  (Exception e) {
             e.printStackTrace();
         }
     }
     
     public  void  start()  throws  Exception {
         // 如果agent不做配置的话,默认jndi path为jmxrmi
         JMXServiceURL url =  new  JMXServiceURL( "service:jmx:rmi://localhost/jndi/rmi://localhost:9999/jmxrmi" );
         JMXConnector jmxc = JMXConnectorFactory.connect(url,  null );
         MBeanServerConnection server = jmxc.getMBeanServerConnection();
         ObjectName mbeanName =  new  ObjectName( "com.example:type=MessageEngine" );
         // 订阅Notification
         server.addNotificationListener(mbeanName,  this null null );
         
         // 访问paused属性。
         boolean  paused = (Boolean)server.getAttribute(mbeanName,  "Paused" );
         System.out.println(paused);
         if  (!paused) {
             server.invoke(mbeanName,  "pause" new  Object[]{ true },  new  String[]{ "boolean" });
         }
         // 构建一个jmx.Message类型实例。
         CompositeType msgType =  new  CompositeType( "jmx.Message" "Message Class Name" ,
                   new  String[]{ "title" , "body" "by" },
                   new  String[]{ "title" , "body" "by" }, 
                   new  OpenType[]{SimpleType.STRING,SimpleType.STRING,SimpleType.STRING});
         CompositeData msgData =  new  CompositeDataSupport(msgType,
                 new  String[]{ "title" , "body" , "by" },
                 new  Object[]{ "Hello" "This is a new message." "xpbug" }); 
         // 调用changeMessage方法
         server.invoke(mbeanName,  "changeMessage" new  Object[]{msgData},  new  String[]{CompositeData. class .getName()});
         server.invoke(mbeanName,  "pause" new  Object[]{ false },  new  String[]{ "boolean" });
         
         // 访问修改后的Message属性。
         msgData = (CompositeData)server.getAttribute(mbeanName,  "Message" );
         System.out.println( "The message changes to:" );
         System.out.println(msgData.get( "title" ));
         System.out.println(msgData.get( "body" ));
         System.out.println(msgData.get( "by" ));
         
         Thread.sleep( 1000 * 10 );
     }
 
     @Override
     public  void  handleNotification(Notification notification, Object handback) {
         System.out.println( "*** Handling new notification ***" );
         System.out.println( "Message: "  + notification.getMessage());
         System.out.println( "Seq: "  + notification.getSequenceNumber());
         System.out.println( "*********************************" );        
     }
}

运行一下client,看看都发生了什么。

源码下载

http://pan.baidu.com/s/1sjLKewX 

来自:http://my.oschina.net/xpbug/blog/221547


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值