15.5 GlassFish对JMX的应用
GlassFish中的管理架构是基于JMX技术来构建的。无论是命令行工具还是管理控制台,都是通过服务器端的MBean来完成管理功能或相应的服务。
15.5.1 GlassFish中的MBean
因为GlassFish对JMX标准提供完全支持,通过JDK附带的JMX管理应用工具JConsole可以地看到GlassFish中的MBean。
【例15.6】查看GlassFish中的MBean。
在本机启动GlassFish,运行JConsole。选择“远程进程”,并在输入框中填写localhost:8686或service:jmx:rmi:///jndi/rmi://hostname:8686/jmxrmi,用户名为admin,口令为adminadmin,如图15-13所示。

图15-13 从JConsole登录GlassFish
登录后,JConsole以树形结构显示了GlassFish上运行的MBean,树的层次是依据MBean的ObjectName解析出来的。比如对象名为amx:J2EEServer=server, j2eeType=JDBCResource, name=jdbc/__TimerPool的MBean,在树中的位置就是amx→JDBCResource→jdbc/__TimerPool,如图15-14所示。
GlassFish对JMX的实现构建在Java EE 5的标准之一JSR-77的基础上。GlassFish中的MBean分为5类,如表15-2所示。
在表15-2中,除了User MBean是用户自行开发的MBean外,其他四类MBean均为系统自带的MBean,这四类MBean又可以被分为两种:JMX MBean和AMX MBean。其中,JMX MBean是指标准的JMX MBean,包括Configuration、Monitor和JSR-77三类MBean,只供内部使用。AMX(AppServer Management Extensions) MBean是GlassFish特有的MBean,除了具备JMX标准MBean的特性外,还有自身独到的特点。GlassFish对JMX所做的扩充集中体现在AMX技术上。AMX MBean封装了系统的JMX MBean,以方便被管理应用的以面向对象的方式的调用,因此也被称作其他MBean的门面(Facade)。下面具体对AMX加以介绍。

图15-14 JConsle显示的GlassFish中的MBean
表15-2 MBean的分类
|
类 型 |
对象名 |
说 明 |
|
Configuration |
前缀com.sun.appserv 含有标识category=config |
用来读写和创建domain.xml中定义的配置,相关接口以Config结尾。都为Model MBean |
|
Monitoring |
前缀com.sun.appserv 含有标识category=monitor |
用来统计监控数据,相关接口以Monitor结尾。都为动态MBean |
|
JSR-77 |
前缀com.sun.appserv 含有标识category=runtime |
封装JSR-77规范中定义的对象,不可直接调用,只能通过MEJB访问。都为Model MBean |
|
AMX |
前缀amx |
仅有的公用MBean,封装各类JMX MBean |
|
User |
前缀user |
用户自定义的MBean |
15.5.2 AMX概述
AMX技术是对JMX的扩充,其主要目的是使MBean的应用更加方便和面向对象。AMX将GlassFish服务器端的MBean重新封装,并在客户端映射成对等的代理,称为动态客户端代理(Dynamic Client-side Proxy,DCP)。通过DCP,可以以面向对象的方式开发JMX管理应用。
AMX技术在GlassFish中被广泛地应用在以下两个方面。
(1) 服务器端配置修改,比如部署应用,增删JDBC对象等。
(2) 监控运行实例的状态。
AMX所带来的好处如下:
l 以面向对象的方法来操作远程的MBean。DCP屏蔽了远程访问的细节和JMX的实现,使得对服务器端MBean的访问如同访问一个本地的对象。
l 良好设计的层次结构并有一致的对象名命名规则与之对应。这使得AMX MBean便于查询和以通用的方式被访问。AMX对Java EE 5中各类可管理对象的封装与规范JSR-77中的定义完全对应。
l 更全面系统的约定。这种约定体现在数据类型、命名方法、参数、返回值和事件等方面。
l 一致并且结构化的配置管理。AMX封装了GlassFish的各类配置,封装后的接口类名放置在domain.xml文件中。
15.5.3 动态客户代理(Dynamic Client Proxy)
AMX支持以两种方式来访问服务器端的MBean:标准的JMX方式(通过MBeanServer- Connection)和 GlassFish所特有的DCP方式访问。
DCP方式充分体现了AMX的优点,即更加方便和面向对象。DCP方式借助于客户端的一套预定义的组件,这套组件保存了MBean的ObjectName和MBeanServerConnection,并实现了AMX相关接口定义的属性和方法。通过这套组件,避免了标准JMX方法繁琐的关于MBean调用方法名、输入参数和返回参数的说明,而是如同调用本地对象一样直接调用MBean接口。查看如表15-3所示的DCP所实现的AMX接口和JMX标准的MBeanInfo,对此会有更深刻的认识。
表15-3 AMX和MBeanInfo的比较
|
AMX Interface代理模式 |
MBeanInfo模式 |
|
Set
getContaineeJ2EETypes(); AbcConfig createAbcConfig(...); Abc getAbc(); |
Set
getContaineeJ2EETypes(); ObjectName createAbcConfig(...); ObjectName getAbcObjectName(); |
注:表中ABC表示某个具体类。
与JMX的标准实现相比,DCP的代码更加简单直观,此外DCP还可以缓存部分对象以提高效率。DCP将缓存不常变化的MBeanInfo和其他一些不常变化的属性,比如ContainerObjectName、InterfaceName、FullType、Group和Name,但对于属于Configuration和Monitoring的MBean,由于其MBeanInfo中的属性会根据配置和监控的状态经常变化,通常不被缓存。
运用了DCP技术的客户端实例可以以面向对象的方式访问服务器端的AMX MBean,同时,通过Extra的接口,AMX实例也可以访问到服务器端标准JMX MBean的特性。Extra不是标准的JMX特性,它是AMX MBean所特有的。从AMX实例中得到JMX标准所支持的连接可以通过以下代码进行:
final MBeanServerConnection conn =
Util.getExtra(amx).getConnectionSource().getMBeanServerConnection();
下面是DCP应用实例。它通过连接AppserverConnectionSource,访问到docroot,再由docroot访问到其他的DCP对象。
【例15.7】DCP应用:
package amxsamples;
import com.sun.appserv.management.DomainRoot;
import com.sun.appserv.management.client.AppserverConnectionSource;
import com.sun.appserv.management.config.DomainConfig;
import com.sun.appserv.management.util.misc.ExceptionUtil;
import com.sun.appserv.management.util.misc.StringUtil;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.ConnectException;
import java.util.Map;
import java.util.Properties;
public final class SampleMain {
private final DomainRoot mDomainRoot;
public SampleMain() throws IOException {
final Properties props = getConnectProperties(
"SampleMain.properties");
//Domain Admin Server的机器名或IP地址
final String host = props.getProperty(
"connect.host", "localhost");
//RMI管理端口,默认8686。注意这里不是连接到管理控制台的端口4848或4849
final int port = Integer.parseInt(props.getProperty(
"connect.port", "8686"));
//管理员名
final String user = props.getProperty("connect.user", "admin");
// 管理员密码
final String password = props.getProperty(
"connect.password", "adminadmin");
AppserverConnectionSource conn = null;
try {
final String info = "host=" + host + ", port=" + port
+", user=" + user + ", password=" + password;
System.out.println("Connecting...:" + info);
//用默认的协议RMI
conn = new AppserverConnectionSource(
AppserverConnectionSource.PROTOCOL_RMI,
host, port, user, password, null, null);
// 强制连接
conn.getJMXConnector(false);
} catch(Throwable t) {
//AMX通过ExceptionUtil的方法getRootCause(e),来简化和统一进行错误处理
final Throwable rootCause = ExceptionUtil.getRootCause(t);
System.out.println("Caught " + rootCause.getClass().getName());
}
assert conn != null : "不能连接到服务器";
mDomainRoot = conn.getDomainRoot();
handleList();
}
public static void main(final String[] args) {
try {
new SampleMain();
} catch(final Throwable t) {
final Throwable rootCause = ExceptionUtil.getRootCause(t);
if (rootCause instanceof ConnectException) {
System.out.println("不能连接到指定的机器名和端口");
} else {
rootCause.printStackTrace();
}
}
}
/**
* 得到已部署的各类应用并显示
*/
public void handleList() {
final DomainConfig dcp = mDomainRoot.getDomainConfig();
System.out.println("\n--- 已部署的组件--- \n");
displayMap("J2EEApplicationConfig",
dcp.getJ2EEApplicationConfigMap());
displayMap("EJBModuleConfig", dcp.getEJBModuleConfigMap());
displayMap("WebModuleConfig", dcp.getWebModuleConfigMap());
displayMap("RARModuleConfig", dcp.getRARModuleConfigMap());
displayMap("AppClientModuleConfig",
dcp.getAppClientModuleConfigMap());
displayMap("LifecycleModuleConfig",
dcp.getLifecycleModuleConfigMap());
}
/**
* 在System.out上打印输出Map对象
*/
private void displayMap(final String msg, final Map<?, ?> m){
System.out.println(msg + ": " + m.keySet());
}
/**
* 从文件中读取properties
*/
private final Properties getConnectProperties(final String file)
throws IOException {
final Properties props = new Properties();
if (file != null) {
System.out.println("Reading properties from: " +
StringUtil.quote(file));
final File f = new File(file);
System.out.println("file:"+f.getAbsolutePath());
if (f.exists()) {
final FileInputStream is = new FileInputStream(f);
try {
props.load(is);
} finally {
is.close();
}
} else {
System.out.println("File \"" + file +
" does not exist, using defaults." );
}
}
return(props);
}
}
15.5.4 AMX对JMX的扩展
除了通过DCP以面向对象的方式访问AMX MBean外,AMX还对JMX的其他方面进行了扩展。这其中包括JMX的Notifications模型和Dotted Name支持。
1. JMX Notifications扩展
沿用JMX定义的Notifications模型。对于一些特定的应用,AMX利用了UserData来存放Notification的用户数据。UserData是一个java.util.Map结构,这个结构维护着一个Key-Value列表,其中每个key是由每个发出Notification的AMX接口来定义的。com.sun.appserv.management.Util提供了以下方法来访问Notification中的用户数据:
Map<String, Serializable> getAMXNotificationData(Notification notif);
Serializable getAMXNotificationValue(Notification notif, String key);
<T extends Serializable> T getAMXNotificationValue(Notification notif, String key, Class<T> theClass);
2. Dotted Name支持
Dotted Name是GlassFish命令行工具asadmin定义的一套约定。AMX全面支持Dotted Names约定,并将Dotted Names当成MBean的属性来处理。在这套约定的支持下,asadmin的三个子命令(list、set和get )可以通过一个由“.”分隔的字串寻址到GlassFish中的MBean。Dotted Name分为三部分,如下所示:
dotted-name = scope-name '.' [ path '.' ]value-name
其中,第一部分scope-name是对组件的分类。它的值只能是“server”、“server-config”或“domain”。
第二部分映射着组件所对应MBean的ObjectName。具体的映射是由一个叫DottedNameRegistry的MBean来实现的。
第三部分是关于组件的属性。
【例15.8】Dotted Name的使用。
要查看所有属于server的组件,可以键入命令:
asadmin> list server.*
server.admin-service
server.admin-service.das-config
server.admin-service.jmx-connector.system
server.application-ref.EventAction
server.application-ref.JBIFramework
server.application-ref.MEjbApp
server.application-ref.WSTCPConnectorLCModule
server.application-ref.WSTXServices
...
可以看到server中组件以Dotted Name形式被列举出来。另外,可以借助Dotted Name来定位某个组件。比如,要查看的组件server.resources.jdbc-connection-pool.DerbyPool的属性,可以键入命令:
asadmin> get server.resources.jdbc-connection-pool.DerbyPool.*
server.resources.jdbc-connection-pool.DerbyPool
.allow-non-component-callers = false
server.resources.jdbc-connection-pool.DerbyPool
.associate-with-thread = false
server.resources.jdbc-connection-pool.DerbyPool
.connection-creation-retry-attempts = 0
server.resources.jdbc-connection-pool.DerbyPool
.connection-creation-retry-interval-in-seconds = 10
server.resources.jdbc-connection-pool.DerbyPool
.connection-leak-reclaim = false
server.resources.jdbc-connection-pool.DerbyPool
.connection-leak-timeout-in-seconds = 0
server.resources.jdbc-connection-pool.DerbyPool
.connection-validation-method = auto-commit
AMX还提供了ConfigDottedNames和MonitoringDottedNames接口,这样所有的Configuration和Monitoring类型的AMX MBean也都可以通过Dotted Name被访问到。
15.5.5 AMX实现
下面将从GlassFish的源代码的角度说明其对AMX的实现。
1. AMX接口和实现的物理分布
AMX的实现类主要来自com.sun.enterprise.management包或其子包,AMX的接口类主要来自com.sun.appserv.management包或其子包。为简单起见,后面对AMX的类名引用省略了其共同前缀的包名。比如,support.AMXImplBase表示的是类com.sun.enterprise.management. support.AMXImplBase。关于GlassFish中AMX的具体接口和实现分布说明参见表15-4。
表15-4 GlassFish源代码中AMX的接口和实现分布
|
想了解 |
请查看 |
|
JMX MBean的相关接口 |
包com.sun.enterprise.admin.mbeanapi |
|
JMX MBean的相关实现 |
包com.sun.enterprise.admin |
|
AMX MBean的相关接口 |
包com.sun.appserv.management,项目admin-core/mbeanapi |
|
AMX MBean的相关实现 |
包com.sun.enterprise.management,项目admin/mbeanapi-impl |
|
Configuration MBean的持久性实现 |
文件admin-mbeans-descriptors.xml |
|
JSR-77 MBean的持久性实现 |
文件runtime-mbeans-descriptors.xml |
|
User MBean的持久性实现 |
文件domain.xml |
其中,关于MBean持久性的实现是结合几个XML文件的设置来完成的。相关代码列举如下。
(1) 文件admin-mbeans-descriptors.xml中关于JDBC-resource MBean的设置:
<mbean name="jdbc-resource" group="config" >
<descriptor>
<field name="elementChangeEvent" value="ResourceDeployEvent" />
<field name="ObjectName" value="{0}:type=jdbc-resource, jndi-name={1},
category=config" />
<field name="xpath"
value="/domain/resources/jdbc-resource[@jndi-name='{1}']" />
<field name="CLIName" value="domain.resources.jdbc-resource.{1}" />
</descriptor>
</mbean>
(2) 文件runtime-mbeans-descriptors.xml中关于JSR-77 MBean的JDBC-resource设置:
<mbean name="JDBCResource" group="runtime">
<descriptor>
<field name="ObjectName" value="{0}:j2eeType=JDBCResource, name={2},
J2EEServer={1}, category=runtime"/>
</descriptor>
</mbean>
2. AMX接口和实现的相关类图
GlassFish通过一系列接口和实现类来实现AMX。图15-15和图15-16分别以JDBCResource为例说明了AMX接口和实现的相关类图。

图15-15 AMX的JDBCResource MBean相关接口的类图

图15-16 AMX的JDBCResource MBean相关实现的类图
对图15-15和图15-16中的主要接口介绍如下。
(1) AMX接口是所有AMX借以扩展的最基本接口。其关键方法说明如表15-5所示。
表15-5 AMX接口说明
|
方法名 |
说 明 |
|
getName() |
返回AMX对象名,即ObjectName中的name属性值 |
|
getJ2EEType() |
返回对象的j2eeType,即ObjectName中的j2eeType属性值。有着相同j2eeType的AMX对象实现了相同的接口 |
|
getContainer() |
返回AMX容器。除了DomainRoot,每个AMX对象都包含于一个容器 |
|
getDomainRoot() |
DomainRoot是所有AMX对象的容器(Container) |
|
getFullType() |
返回对象的j2eeTypes,并前缀以包含关系 比如:J2EEServer.J2EEApplication.WebModule.Servlet 类型是Servlet,包含关系是J2EEServer下的J2EEApplication下的WebModule 注意:J2EEServer不是根节点,它包含于DomainRoot下的J2EEDomain |
|
getGroup() |
AMX对象所属的组 |
(2) Container接口用以说明是否包含其他AMX对象。每个AMX对象的容器都可通过getContainer()得到。DomainRoot是唯一的例外,它的容器是空(null)。
AMXImplBase类是所有AMX MBean的默认实现,包括对Delegate的应用。
(3) Delegate接口。AMX MBean都是标准JMX MBean,即必须实现getMBeanInfo等方法,对此它们通常是委托一类叫Delegate的对象来实现的。每个Delegate都要实现接口com.sun.enterprise.management.support.Delegate:
public interface Delegate{
public Object getAttribute(String name)throws
AttributeNotFoundException;
public
boolean supportsAttribute(String name);
public AttributeList getAttributes(final String[] attrNames);
public void setAttribute(final Attribute attrName
throws
AttributeNotFoundException, InvalidAttributeValueException;
public AttributeList setAttributes(final AttributeList attrs);
public boolean supportsOperation(String
name, Object[] args, String[] types);
public MBeanInfo getMBeanInfo();
public Object invoke(String operationName, Object[] args,
String[] types );
public void setOwner(DelegateOwner owner);
}
针对离线或本地AMX MBean所对应的委托实现是offline.ConfigDelegate。类support.DelegateToMBeanDelegate是委托的另一种方式,它是将一个MBean的实现委托给另一个MBean来完成。
【例15.9】AMX MBean调用其对应的Delegate。
代码摘自com.sun.enterprise.management.support.AMXImplBase,显示了AMX MBean如何调用一个它的Delegate,从而得到指定的属性值:
protected Object delegateGetAttribute(final String name)
throws Exception {
assert(name != null);
//得到对应的Delegate
final Delegate delegate = getDelegate();
assert(delegate != null);
//调用Delegate方法得到指定的属性值
final Object value = delegate.getAttribute(name);
//将属性值由通用的Object对象转换成具体类型
Object result = value;
if (value != null) {
Class<?> attrClass = getAttributeClass(name);
if (attrClass != null) {
if (ClassUtil.IsPrimitiveClass(attrClass)) {
attrClass = ClassUtil
.PrimitiveClassToObjectClass(attrClass);
}
if (!attrClass.isAssignableFrom(value.getClass())) {
try {
result = convertToClass(value, attrClass);
} catch(Exception e) {
// OK, there are a few exceptions
result = value;
}
}
} else {
getMBeanLogger().warning(
"AMXImplBase.delegateGetAttribute:"
+ "Can't find class for attribute: "
+ name + "=" + value + " in object "
+ getObjectName());
}
}
return(result);
}
3. 实现类与接口的对应
每个AMX MBean的实现类与接口之间有一定的对应关系,表15-6列举了各个AMX MBean实现类和接口之间的对应关系。
表15-6 AMX MBean实现类与接口的对应关系
|
接 口 |
实 现 |
|
AMX、AMXDebug、Container |
AMXImplBase(被AMXNonConfigImplBase扩展) |
|
AMXConfig |
AMXConfigImplBase |
|
ConfigFactoryCallback(impl) |
ConfigFactory、ResourceFactoryImplBase |
|
J2EEManagedObject |
J2EEManagedObjectImplBase |
|
Monitoring |
MonitoringImplBase |
|
MonitoringStats |
MonitoringStatsImplBase(扩展MonitoringImplBase) |
|
BeanMonitor |
BeanMonitorImplBase(扩展MonitoringStatsImplBase) |
|
ModuleConfig |
DeployedItemConfigBase(扩展AMXConfigImplBase) |
|
J2EEResource |
J2EEResourceImplBase |
|
EJB |
EJBImplBase |
|
J2EEModule |
J2EEModuleImplBase |
|
AMXJMXMonitor |
JMXMonitorBase |







