1 Introduction
??? Java Management Extension (JMX) API定義于JSR
3,用于應用程序管理。這些API對于被管理的應用程序來說是本地的。也就是說,在JSR
160發布之前,如果客戶端要通過JMX來管理和監控遠程的應用程序,并沒有標準的做法。JSR 160擴展了JSR
3,提供了標準的API用于連接到支持JMX的遠程應用程序。JSR 255將會把JMX升級到2.0,并可能體現在Java 7中。
??? 目前JSR 160定義了基于RMI(支持RMI/JRMP 和 RMI/IIOP)的連接器,還定義了可選的JMXMP連接器,它基于TCP Socket和Java序列化機制。
?
2 JMX Remoting API
2.1 JMXServiceURL
??? JMXServiceURL用于標識JMXConnectorServer,它是采用以下形式的字符串:
??? service:jmx:<protocol>://[[[ <host>]: <port>]/ <path>]
???
"protocol" 指定了協議,例如:rmi、iiop、jmxmp 或者 soap。"host"、"port" 和
"path"是可選的。JMXServiceURL并不足以描述所有的用于連接到JMXConnectorServer的配置信息(例如配置
RMIClientSocketFactory 和RMIServerSocketFactory),因此在構造JMXConnectorServer 和
JMXConnector的時候,還可能需要通過一個Map實例指定其它屬性。
?
2.2 JMXConnectorServer
??? JMXConnectorServer
是MBeanServer端的組件,它需要被關聯到MBeanServer:可以通過在創建JMXConnectorServer的時候顯式指定
MBeanServer;也可以把JMXConnectorServer(本身是一個MBean)注冊到MBeanServer中。
???
在被關聯到MBeanServer之后,需要啟動JMXConnectorServer以處理客戶端的請求。同樣,如果希望停止處理客戶端的請求,那么需
要停止JMXConnectorServer。JMXConnectorServer被停止之后,不應該試圖重新啟動它,而是應該將其丟棄。
??? 最好通過JMXConnectorServerFactory來創建JMXConnectorServer,例如:
-
??
-
JMXServiceURL?address?=?new?JMXServiceURL("service:jmx:rmi://host");??
-
??
-
??
-
Map?environment?=?null;??
-
??
-
??
-
MBeanServer?server?=?MBeanServerFactory.createMBeanServer();??
-
??
-
??
-
JMXConnectorServer?cntorServer?=?JMXConnectorServerFactory.newJMXConnectorServer(address,?environment,?server);??
-
??
-
??
-
cntorServer.start();??
??? 在以上的例子中,創建JMXConnectorServer的時候指定了MBeanServer。以下是一個將JMXConnectorServer作為MBean注冊到MBeanServer的例子:
-
??
-
JMXServiceURL?address?=?new?JMXServiceURL("service:jmx:rmi://host");??
-
??
-
??
-
Map?environment?=?null;??
-
??
-
JMXConnectorServer?cntorServer?=?JMXConnectorServerFactory.newJMXConnectorServer(address,?environment,?null);??
-
??
-
??
-
MBeanServer?server?=?MBeanServerFactory.createMBeanServer();??
-
??
-
??
-
ObjectName?cntorServerName?=?ObjectName.getInstance("connectors:protocol=rmi");??
-
server.registerMBean(cntorServer,?cntorServerName);??
-
??
-
??
-
cntorServer.start();??
-
??
-
??
-
server.invoke(cntorServerName,?"start",?null,?null);??
-
??
-
??
-
Object?proxy?=?MBeanServerInvocationHandler.newProxyInstance(server,?cntorServerName,?JMXConnectorServerMBean.class,?true);??
-
JMXConnectorServerMBean?cntorServerMBean?=?(JMXConnectorServerMBean)proxy;??
-
cntorServerMBean.start();??
???
需要注意的是,在這個例子中使用了三種不同的方式啟動JMXConnectorServer:第一種方式是直接調用cntorServer對象的
start方法;第二種方式是通過MBeanServer調用MBean的方法;第三種方式是使用了
MBeanServerInvocationHandler,它繼承自InvocationHandler,用于從MBean的管理接口向
MBeanServer中的 MBean
轉發方法調用。當調用從newProxyInstance()方法返回的代理對象上的toString()、hashCode()
和equals(Object)等方法時,這些方法調用同樣也會被轉發到MBeanServer中的MBean ,因此當且僅當 MBean
在其管理接口中聲明了這些方法時才能如此操作,否則會拋出異常。
?
2.3 JMXConnector
??
JMXConnector是客戶端的組件,客戶端程序通過它連接到遠程的MBeanServer。客戶端可以通過JMXConnector獲得遠程
MBeanServer的MBeanServerConnection
接口,并以類似本地的方式使用它。JMXConnector還支持在遠程的MBeanServer上注冊本地或者遠程的通知監聽器,以接收來自遠程
MBeanServer的通知。
??? 最好通過JMXConnectorFactory來創建JMXConnector,例如:
-
??
-
JMXServiceURL?address?=?...;??
-
??
-
??
-
Map?environment?=?null;??
-
??
-
??
-
JMXConnector?cntor?=?JMXConnectorFactory.connect(address,?environment);??
-
??
-
??
-
MBeanServerConnection?mbsc?=?cntor.getMBeanServerConnection();??
-
??
-
??
-
String?domain?=?mbsc.getDefaultDomain();??
-
也可以先實例化JMXConnector,然后在連接到MBeanServer,例如:??
-
??
-
JMXServiceURL?address?=?...;??
-
??
-
??
-
Map?creationEnvironment?=?null;??
-
??
-
??
-
JMXConnector?cntor?=?JMXConnectorFactory.newJMXConnector(address,?creationEnvironment);??
-
??
-
??
-
??
-
Map?connectionEnvironment?=?null;??
-
??
-
??
-
cntor.connect(connectionEnvironment);??
-
??
-
??
-
MBeanServerConnection?mbsc?=?cntor.getMBeanServerConnection();??
-
??
-
??
-
String?domain?=?mbsc.getDefaultDomain();??
???
需要注意的是,以上的例子中采用了兩個不同的Map對象保存相關的屬性:一個用于實例化;另外一個用于連接。MBeanServerConnection
接口在JMX 1.2中定義,用于支持remote
API。由于之前版本的MBeanServer接口不能直接被客戶端使用(例如其registerMBean(), deserialize(),
getClassLoader()等方法對于客戶端來說沒有意義),因此在JMX
1.2中,MBeanServer接口繼承自MBeanServerConnection接口。
???
RMIConnector是標準的JMXConnector。由于它使用RMI協議,因此需要一個stub
object來處理RMI調用的細節。stub class通常存在于客戶端的類路徑上,但是stub
object通常是從命名服務器處下載,因此RMI客戶端需要知道stub object被綁定到RMI server上的路徑。JSR
160定義了兩種獲得stub object的方式:
??? 第一種方式不需要命名服務器,而是將stub
object編碼為JMXServiceURL的一部分,即"encoded form"。如果采用RMI/JRMP協議,那么對stub
object被序列化之后的所有字節進行Base64編碼,然后將編碼后的字符添加到JMXServiceURL中,并且以/stub/開始;如果采用
RMI/ IIOP,那么IOR被添加到JMXServiceURL中,并以/ior/開始。以下是個例子:
-
??
-
service:jmx:rmi:??
-
??
-
??
-
service:jmx:iiop:??
??? 第二種方式是在JMXServiceURL中指定命名服務器和stub object被綁定到的JNDI路徑,即"JNDI
form"。在JMXServiceURL中以/jndi/開始。將JNDI的相關配置傳遞給RMIConnector的方式有多種。例如URL
'rmi://namingHost:1099/jndiPath'指明命名服務器在名為'namingHost'的主機上并監聽1099端口,命名服務
器是rmiregistry,路徑是'/jndiPath'。同樣URL
'iiop://namingHost:900/jndiPath'指明命名服務器在名為'namingHost'的主機上并監聽900端口,命名服務器
是COS命名服務,路徑是'/jndiPath'。以下是個例子:
-
??
-
service:jmx:rmi:??
-
??
-
??
-
service:jmx:iiop:??
???
此外,JNDI屬性也可以通過系統屬性、類路徑上的'jndi.properties'文件、在調用JMXConnector.connect(Map
environment)或者JMXConnectorFactory.connect(JMXServiceURL url, Map
environment)方法時傳遞的environment參數等不同方式指定。如果JNDI屬性通過這些方式指定,那么JMXServiceURL中
可以使用簡短的JNDI格式,即只指定JNDI path。以下是個例子:
-
??
-
service:jmx:rmi:??
-
??
-
??
-
service:jmx:iiop:??
-
??
-
??
-
??
-
Map?environment?=?new?HashMap();??
-
environment.put(Context.INITIAL_CONTEXT_FACTORY,?"com.sun.jndi.rmi.registry.RegistryContextFactory");??
-
environment.put(Context.PROVIDER_URL,?"rmi://namingHost:1099");??
-
??
-
JMXServiceURL?url?=?new?JMXServiceURL("service:jmx:rmi://rmiHost/jndi/jndiPath");??
-
??
-
JMXConnector?connector?=?JMXConnectorFactory.connect(url,?environment);??
?
2.4 Remote Notification
??? JSR
160定義的連接器可以接收到遠程MBean發送的通知。通知被傳遞的細節依賴于連接器使用的協議。要接收通知,客戶端必須要通過調用
MBeanServerConnection.addNotificationListener(...)方法來注冊一個監聽器,這個方法有兩個重載的版
本:
??? 第一個版本接受一個ObjectName實例作為監聽器,由于監聽器是遠程MBeanServer中的一個MBean,因此方法中的filter和handback參數必須被傳遞到遠程的MBeanServer,因此它們必須是可以被序列化的。
???
另一個版本接受NotificationListener的實例作為監聽器。這個監聽器是客戶端的本地對象,而且監聽器對象不會被傳遞到遠程的
MBeanServer,因此它也不必是可以被序列化的。NotificationFilter是否被傳遞到遠程的MBeanServer取決于
connector使用的協議,handback對象不會被傳遞到遠程MBeanServer。在另一方面,遠程的MBean發送的通知必須是可以被序列
化的,因為通知會被傳遞到客戶端。以下是在遠程的MBean上注冊監聽器的例子:
-
??
-
JMXServiceURL?address?=?...;??
-
??
-
??
-
JMXConnector?connector?=?JMXConnectorFactory.connect(address);??
-
??
-
??
-
MBeanServerConnection?mbsc?=?connector.getMBeanServerConnection();??
-
??
-
??
-
ObjectName?delegateName?=?ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate");??
-
??
-
NotificationListener?listener?=?new?NotificationListener()??
-
{??
-
???public?void?handleNotification(Notification?notification,?Object?handback)??
-
???{??
-
????????
-
???}??
-
};??
-
??
-
mbsc.addNotificationListener(delegateName,?listener,?null,?null);??
?
3 JMX Remoting Security
??? JSR
160提供了一種可插拔的認證機制,它基于JMXAuthenticator接口。JMXAuthenticator接口中只包含如下一個方法,它接受從
客戶端得到的身份證明信息作為參數,返回javax.security.auth.Subject對象的一個實例。
-
public
?Subject?authenticate(Object?credentials)?
throws
?SecurityException??
??? 以下是在JMXConnectorServer 中使用JMXAuthenticator的例子:
-
??
-
JMXServiceURL?address?=?...;??
-
??
-
??
-
JMXAuthenticator?authenticator?=?...;??
-
??
-
??
-
Map?environment?=?new?HashMap();??
-
environment.put(JMXConnectorServer.AUTHENTICATOR,?authenticator);??
-
??
-
??
-
MBeanServer?server?=?MBeanServerFactory.createMBeanServer();??
-
??
-
??
-
JMXConnectorServer?cntorServer?=?JMXConnectorServerFactory.newJMXConnectorServer(address,?environment,?server);??
-
??
-
??
-
cntorServer.start();??
??? 以下是在JMXConnector中使用JMXAuthenticator的例子:
-
??
-
JMXServiceURL?address?=?...;??
-
??
-
??
-
JMXConnector?cntor?=?JMXConnectorFactory.newJMXConnector(address,?null);??
-
??
-
??
-
Object?credentials?=?...;??
-
??
-
??
-
Map?environment?=?new?HashMap();??
-
environment.put(JMXConnector.CREDENTIALS,?credentials);??
-
??
-
??
-
try
??
-
{??
-
???cntor.connect(environment);??
-
}??
-
catch
?(SecurityException?x)??
-
{??
-
?????
-
???throw?x;??
-
}??
-
??
-
??
-
MBeanServerConnection?mbsc?=?cntor.getMBeanServerConnection();??
-
??
-
??
-
ObjectName?delegate?=?ObjectName.getInstance("JMImplementation:type=MBeanServerDelegate");??
-
String?id?=?mbsc.getAttribute(delegate,?"MBeanServerId");??
??? 當調用從JMXConnector 得到的MBeanServerConnection
接口上的方法時,最終發生在遠程MBeanServer的調用會以認證后的Subject
身份執行。如果使用SecurityManager,那么還可以為不同的Subject指定不同的權限。