一、 引言
JMX(Java管理擴(kuò)展)提供了一組工具用來(lái)管理本地和遠(yuǎn)程應(yīng)用程序、系統(tǒng)對(duì)象、設(shè)備等。本文將解釋如何使用JMX(JSR 160)來(lái)遠(yuǎn)程控制Web應(yīng)用程序,并將解釋應(yīng)用程序中可用于JMX客戶的代碼,同時(shí)將展示使用如MC4J和jManage等的不同客戶如何連接到支持JMX的應(yīng)用程序。此外,我們還將詳細(xì)地討論使用RMI協(xié)議和JNDI來(lái)保護(hù)通訊層。
首先我們要分析一個(gè)簡(jiǎn)單的web應(yīng)用程序,它監(jiān)控已經(jīng)登陸的用戶數(shù)目并通過(guò)一個(gè)安全的JMX服務(wù)來(lái)顯示該項(xiàng)統(tǒng)計(jì)。我們還將運(yùn)行這個(gè)應(yīng)用程序的多個(gè)實(shí)例并且從所有的運(yùn)行實(shí)例中跟蹤這個(gè)統(tǒng)計(jì)數(shù)字。當(dāng)然,你可以下載這個(gè)示例web應(yīng)用程序。它需要你安裝J2SE 5.0 SDK并且你的JAVA_HOME環(huán)境變量指向基安裝目錄。J2SE 5.0實(shí)現(xiàn)了1.2版本的JMX API和JMX 1.0版本的Remote API。同時(shí)還需要一個(gè)支持Servlet的容器;我使用的是Apache Tomcat 5.5.12。另外,我還使用Apache Ant來(lái)構(gòu)建這一示例應(yīng)用程序。
二、 建立示例應(yīng)用程序
首先,你要下載示例應(yīng)用程序并且使用ant war(更多的細(xì)節(jié)見(jiàn)build.XML中的注釋)來(lái)創(chuàng)建一個(gè)WAR文件。把jmxapp.war復(fù)制到Tomcat的webapps目錄。假定Tomcat正在運(yùn)行于你的本地機(jī)器的端口8080,那么該應(yīng)用程序的URL將是:
http://localhost:8080/jmxapp
如果你看到一個(gè)提示你輸入名字和口令的登陸屏幕,那么一切已經(jīng)就緒了。
三、 跟蹤一些有意義的數(shù)據(jù)
本文中的應(yīng)用程序使用Struts框架來(lái)提交登錄表單。一旦提交結(jié)束,即執(zhí)行LoginAction.execute(..)方法-它將簡(jiǎn)單地檢查是否用戶的ID為"hello"以及是否其口令為"world"。如果二者都正確,那么登錄成功并且控制被導(dǎo)向login_success.JSP;如果不正確,那么我們返回到登錄表單。根據(jù)登錄成功與否決定調(diào)用incrementSuccessLogins(HttpServletRequest)方法還是incrementFailedLogins(HttpServletRequest)方法。現(xiàn)在,讓我們先分析一下incrementFailedLogins(HttpServletRequest):
private void incrementFailedLogins(HttpServletRequest request) { HttpSession session = request.getSession(); ServletContext context =session.getServletContext(); Integer num = (Integer) context.getAttribute( Constants.FAILED_LOGINS_KEY); int newValue = 1; if (num != null) { newValue = num.intValue() + 1; } context.setAttribute( Constants.FAILED_LOGINS_KEY, new Integer(newValue)); } |
這個(gè)方法增加一個(gè)在應(yīng)用程序范圍存儲(chǔ)的FAILED_LOGINS_KEY變量。這個(gè)incrementSuccessLogins(HttpServletRequest)方法是以相似的方法實(shí)現(xiàn)的。該應(yīng)用程序追蹤有多少人成功地登錄和有多少人認(rèn)證失敗。這真不錯(cuò),但是我們?cè)撊绾未嫒∵@些數(shù)據(jù)?這就是引入JMX的原因。
四、 創(chuàng)建JMX MBeans
MBeans基礎(chǔ)知識(shí)及其適于JMX架構(gòu)的方面超出了本文所討論的范圍。我們將為我們的應(yīng)用程序簡(jiǎn)單地創(chuàng)建、實(shí)現(xiàn)、暴露和保護(hù)一個(gè)MBean。我們所感興趣的是暴露相應(yīng)與下列兩個(gè)方法的兩種數(shù)據(jù)。下面是我們的簡(jiǎn)單MBean接口:
public interface LoginStatsMBean { public int getFailedLogins(); public int getSuccessLogins(); } |
這兩個(gè)方法簡(jiǎn)單地返回成功和失敗登陸的數(shù)目。LoginStatsMBean的實(shí)現(xiàn)-LoginStats,為上面兩種方法提供了一種具體的實(shí)現(xiàn)。讓我們分析一下getFailedLogins()實(shí)現(xiàn):
public int getFailedLogins() { ServletContext context = Config.getServletContext(); Integer val = (Integer) context.getAttribute( Constants.FAILED_LOGINS_KEY); return (val == null) ? 0 : val.intValue(); } |
該方法返回一個(gè)存儲(chǔ)在ServletContext中的值。getSuccessLogins()方法是以相似的方式實(shí)現(xiàn)的。
五、 創(chuàng)建和保護(hù)一個(gè)JMX代理
管理應(yīng)用程序的JMX相關(guān)方面的JMXAgent類有以下幾個(gè)責(zé)任:
1. 創(chuàng)建一個(gè)MBeanServer。
2. 用MBeanServer注冊(cè)LoginStatsMBean。
3. 創(chuàng)建一個(gè)JMXConnector以允許遠(yuǎn)程客戶進(jìn)行連接。
o 包含對(duì)JNDI的使用。
o 也必須有一個(gè)RMI注冊(cè)運(yùn)行。
4. 使用一個(gè)用戶名和口令保護(hù)JMXConnector。
5. 分別在應(yīng)用程序啟動(dòng)和停止時(shí),啟動(dòng)和停止JMXConnector。
JMXAgent的類輪廓是:
public class JMXAgent { public JMXAgent() { //初始化JMX服務(wù)器 } public void start() { //啟動(dòng)JMX服務(wù)器 } //在應(yīng)用程序結(jié)束時(shí)調(diào)用 public void stop() { //停止JMX服務(wù)器 } } |
讓我們理解在該構(gòu)造器的這部分代碼-它能夠使得客戶遠(yuǎn)程地監(jiān)控該應(yīng)用程序。
用MBeans創(chuàng)建一個(gè)MBeanServer
我們首先創(chuàng)建一個(gè)MBeanServer對(duì)象。它是JMX基礎(chǔ)結(jié)構(gòu)的核心組件,它允許我們暴露我們的MBeans作為可管理的對(duì)象。MBeanServerFactory.createMBeanServer(String)方法使得這一任務(wù)極為輕松。所提供的參數(shù)是服務(wù)器的域。可以把它當(dāng)作這個(gè)MBeanServer的唯一的名字。然后,我們用MbeanServe來(lái)注冊(cè)LoginStatsMBean。MBeanServer.reGISterMBean(Object,ObjectName)方法使用的參數(shù)有兩個(gè):一個(gè)是MBean實(shí)現(xiàn)的一個(gè)實(shí)例;另一個(gè)是類型ObjectName的一個(gè)對(duì)象-它用于唯一地標(biāo)識(shí)該MBean;在這種情況下,DOMAIN+":name=LoginStats"就足夠了。
MBeanServer server = MBeanServerFactory.createMBeanServer(DOMAIN); server.registerMBean(new LoginStats(),new ObjectName(DOMAIN+ ":name=LoginStats")); |
到現(xiàn)在為止,我們已經(jīng)創(chuàng)建了一個(gè)MBeanServer并且用它注冊(cè)了LoginStatsMBean。下一步是使得該服務(wù)器對(duì)客戶可用。為此,我們必須創(chuàng)建一個(gè)JMXServiceURL-它描述了客戶將用來(lái)存取該JMX服務(wù)的URL:
JMXServiceURL url = new JMXServiceURL("rmi",null, Constants.MBEAN_SERVER_PORT, "/jndi/rmi://localhost:" +Constants.RMI_REGISTRY_PORT +"/jmxapp"); |
讓我們細(xì)致地分析一下上面一行代碼。該JMXServiceURL構(gòu)造器使用了四個(gè)參數(shù):
1. 在連接時(shí)使用的協(xié)議(rmi,jmxmp,iiop,等等)。
2. JMX服務(wù)的主機(jī)。用localhost作為參數(shù)就足夠了。然而,提供null強(qiáng)制JMXServiceURL找到可能是最好的主機(jī)名。例如,在這種情況下,它將把null翻譯成zarar-這是我的計(jì)算機(jī)的名字。
3. JMX服務(wù)使用的端口。
4. 最后,我們必須提供URL路徑-它指示怎樣找到JMX服務(wù)。在這種情況下,它會(huì)是/jndi/rmi://localhost:1099/jmxapp。
其中,/jndi部分是指,客戶必須為JMX服務(wù)做一下JNDI查詢。rmi://localhost:1099指示,存在一個(gè)運(yùn)行于本機(jī)的端口1099的RMI注冊(cè)。這里的jmxapp是在RMI注冊(cè)中唯一標(biāo)識(shí)這個(gè)JMX服務(wù)的。在JMXServiceURL對(duì)象上的一個(gè)toString()產(chǎn)生下列結(jié)果:
service:jmx:rmi://zarar:9589/jndi/rmi://localhost:1100/jmxapp
上面是客戶將最終使用來(lái)連接到該JMX服務(wù)的URL。J2SE 5.0文檔有關(guān)于這個(gè)URL結(jié)構(gòu)的更為詳細(xì)的解釋。
(一) 保護(hù)服務(wù)
J2SE 5.0提供了一種有利于JMX用一種容易的方式進(jìn)行用戶認(rèn)證的機(jī)制。我創(chuàng)建了一個(gè)簡(jiǎn)單的文本文件-它存儲(chǔ)用戶名和口令信息。文件的內(nèi)容是:
zarar siddiqi
fyodor dostoevsky
用戶zarar和fyodor被分別通過(guò)口令siddiqi和dostoevsky認(rèn)證。下一步是創(chuàng)建并保護(hù)一個(gè)JMXConnectorServer,它暴露了該MbeanServer。username/password文件的路徑被存儲(chǔ)在該鍵下的一個(gè)映射中-jmx.remote.x.password.file。這個(gè)映射在以后創(chuàng)建JMXConnectorServer時(shí)使用。
ServletContext context = Config.getServletContext(); //得到存儲(chǔ)jmx用戶信息的文件 String userFile =context.getRealPath("/")+"/Web-INF/classes/"+Constants.JMX_USERS_FILE; //創(chuàng)建authenticator并且初始化RMI服務(wù)器 Map<string> env = new HashMap<string>(); env.put("jmx.remote.x.password.file", userFile); 現(xiàn)在,讓我們創(chuàng)建JMXConnectorServer。下面一行代碼完成這一功能: connectorServer = JMXConnectorServerFactory. newJMXConnectorServer(url, env, server); |
這個(gè)JMXConnectorServerFactory.newJMXConnectorServer(JMXServiceURL,Map,MBeanServer)方法使用我們剛創(chuàng)建的三個(gè)對(duì)象作為參數(shù)-它們是JMXServiceURL,存儲(chǔ)認(rèn)證信息的映射和MBeanServer。其中,connectorServer實(shí)例變量允許我們分別在應(yīng)用程序啟動(dòng)和停止時(shí),分別用start()和stop()來(lái)啟動(dòng)和停止JMXConnectorServer。
提示 盡管JSR 160的J2SE 5.0實(shí)現(xiàn)相當(dāng)有力;但是另外的實(shí)現(xiàn),例如MX4J,也提供了一些類-它們提供了方便的特性,例如口令混淆,也就是PasswordAuthenticator類。
七、 啟動(dòng)RMI注冊(cè)
在早些時(shí)候,我提到RMI注冊(cè)并且指出當(dāng)訪問(wèn)服務(wù)時(shí)執(zhí)行一個(gè)JNDI查詢。然而,現(xiàn)在我們沒(méi)有一個(gè)正運(yùn)行的RMI注冊(cè),因此一個(gè)JNDI查詢將失敗。一個(gè)RMI注冊(cè)的啟動(dòng)可以用手工方式或編程方式來(lái)實(shí)現(xiàn)。
(一) 使用命令行
在你的Windows或Linux命令行上,輸入下列一名來(lái)啟動(dòng)一個(gè)RMI注冊(cè):
rmiregistry &
這將啟動(dòng)你的默認(rèn)主機(jī)和端口(分別是localhost和1109)的RMI注冊(cè)。然而,對(duì)于我們的web應(yīng)用程序來(lái)說(shuō),我們不可能依賴一個(gè)在應(yīng)用程序啟動(dòng)時(shí)可用的RMI而寧愿用編程方式來(lái)實(shí)現(xiàn)之。
(二) 以編程方式啟動(dòng)RMI注冊(cè)
為了以編程方式啟動(dòng)RMI注冊(cè),你可以使用LocateRegistry.createRegistry(int port)方法。該方法返回類型注冊(cè)的一個(gè)對(duì)象。當(dāng)我們想在應(yīng)用程序一端終止這個(gè)注冊(cè)時(shí),我們保存這個(gè)參考。就在我們啟動(dòng)我們的在JMXAgent.start()中的JMXConnectorServer之前,我們首先啟動(dòng)RMI注冊(cè),使用下列代碼行:
registry = LocateRegistry.createRegistry(Constants.RMI_REGISTRY_PORT);
在應(yīng)用程序一端,在JMXAgent.stop()中停止JMXConnectorServer之后,調(diào)用下列方法來(lái)終止該注冊(cè):
UnicastRemoteObject.unexportObject(registry,true);
注意,StartupListener類觸發(fā)了應(yīng)用程序開(kāi)始和結(jié)束任務(wù)。
八、 訪問(wèn)我們的JMX服務(wù)
我們可以有好幾種方法來(lái)存取JSR 160服務(wù)。為此,我們可以通過(guò)編程或通過(guò)使用一個(gè)GUI來(lái)實(shí)現(xiàn)。
(一) 使用MC4J連接
通過(guò)把jmxapp.war復(fù)制到Tomcat的Webapps目錄來(lái)發(fā)布該應(yīng)用程序。下載并且安裝MC4J。一旦安裝完,創(chuàng)建一新的類型JSR 160的服務(wù)器連接并且指定該服務(wù)器URL-它在應(yīng)用程序啟動(dòng)時(shí)在應(yīng)用程序服務(wù)器日志中打印。在我的示例中,它是:
service:jmx:rmi://zarar:9589/jndi/rmi://localhost:1100/jmxapp
提供用戶名和口令,MC4J分別把它們參考為"Principle"和"Credentials"。點(diǎn)擊Next將把你帶到一個(gè)屏幕-在此你可以定制你的classpath。默認(rèn)設(shè)置應(yīng)該工作正常,并且你可以點(diǎn)擊"Finish"來(lái)連接到該JMX服務(wù)。一旦建立連接,瀏覽如圖1所示的MC4J樹(shù)結(jié)構(gòu),直到你找到LoginStats MBean實(shí)現(xiàn)的"Properties"選項(xiàng)。
![]() 圖1.MC4J視圖 |
點(diǎn)擊Properties顯示統(tǒng)計(jì),如圖2所示:
![]() 圖2.屬性窗口 |
(二) 使用jManage連接到一個(gè)"簇"
通過(guò)把jmxapp.war復(fù)制到Tomcat的webapps目錄發(fā)布該應(yīng)用程序。請(qǐng)注意一下在應(yīng)用程序啟動(dòng)時(shí)所打印的URL。接下來(lái),發(fā)布這個(gè)應(yīng)用程序的另一個(gè)實(shí)例-通過(guò)改變Constants類中的RMI_REGISTRY_PORT并且MBEAN_SERVER_PORT變量,這樣該應(yīng)用程序的第二個(gè)實(shí)例就不會(huì)試圖使用已經(jīng)在使用的端口了。改變?cè)赽uild.XML文件中的app.name屬性,以便新的實(shí)例將被發(fā)布到一個(gè)不同的上下文(例如,jmxapp2)。用ant創(chuàng)建一個(gè)清理的war文件-它將在其目錄下創(chuàng)建jmxapp2.war。把jmxapp2.war復(fù)制到Tomcat的webapps目錄。該應(yīng)用程序?qū)⒁l(fā)布,而且現(xiàn)在你有相同應(yīng)用程序的兩個(gè)實(shí)例在運(yùn)行了。我再次提醒你注意在啟動(dòng)時(shí)所打印的URL。
下載和安裝jManage。一旦安裝了,使用jManage的web接口來(lái)創(chuàng)建一個(gè)JSR 160應(yīng)用程序-通過(guò)使用主頁(yè)中的"添加新應(yīng)用程序"鏈接。"添加應(yīng)用程序"頁(yè)面顯示在圖3中:
![]() 圖3."添加應(yīng)用程序"頁(yè)面 |
為要發(fā)布的第二個(gè)應(yīng)用程序重復(fù)前面的步驟并再次使用適當(dāng)?shù)挠脩裘⒖诹詈蚒RL。。一旦你創(chuàng)建了這兩個(gè)應(yīng)用程序,你必須通過(guò)遵循在主頁(yè)中找到的"添加新應(yīng)用程序簇"鏈接來(lái)創(chuàng)建一個(gè)簇。現(xiàn)在,把這兩個(gè)已經(jīng)創(chuàng)建的應(yīng)用程序添加到你的簇上,如圖4所示:
![]() 圖4.添加應(yīng)用程序簇頁(yè)面 |
好了,我們已經(jīng)完成了!從主頁(yè)上,點(diǎn)擊簇中的一個(gè)應(yīng)用程序,然后點(diǎn)擊"Find More Objects"按鈕。你將看到name=LoginStats MBean;點(diǎn)擊它,則你就會(huì)看到我們已經(jīng)暴露的FailedLogins和SuccessLogins屬性。點(diǎn)擊在該同一頁(yè)面上的"Cluster View"鏈接將顯示與圖5相類似的一個(gè)頁(yè)面-其中,你可以看到兩個(gè)應(yīng)用程序的運(yùn)行計(jì)數(shù)統(tǒng)計(jì):
![]() 圖5.針對(duì)jmxapp和jmxapp2的簇視圖 |
試著登錄到兩個(gè)應(yīng)用程序(http://localhost:8080/jmxapp和http://localhost:8080/jmxapp2)并且觀察這些數(shù)字是怎樣改變的。
九、 結(jié)論
現(xiàn)在你已經(jīng)知道了怎樣使你的新的和現(xiàn)有web應(yīng)用程序支持JMX并且安全地管理它們-使用MC4J和jManage。盡管J2SE 5.0提供了JMX說(shuō)明書的一個(gè)有力的實(shí)現(xiàn),但是另外的開(kāi)源工程例如XMOJO和MX4J還提供了另外的特征,例如經(jīng)由web接口甚至更多的方式的連接。如果有興趣的讀者想了解更多地有關(guān)JMX的知識(shí),你可以看一下J. Steven Perry寫的《Java Management Extensions》一書。如果你對(duì)遠(yuǎn)程應(yīng)用程序管理感興趣的話,Jeff Hanson寫的《Connecting JMX客戶and Servers》將是很有閱讀價(jià)值的,其中提供了許多真實(shí)世界的例子。