2006年Web 2.0最火的技术莫过于AJAX了,各种AJAX的框架层出不穷。正是应验一句老话:“三十年河东,三十年河西”,其实还不到三十年呢,B/S开发模式已经落伍了,现在C/S模式又吃香了,只不过client不是由OS承载,而是换成了web浏览器;client端的编程语言换成了JavaScript。AJAX似乎可以包办一切,唯一的缺陷是所有业务必须由client端来触发,Server端只能被动地响应。针对AJAX的先天缺陷,Comet技术应运而生,或者说是老树开新花,Server端的推送技术,使得Web浏览器真正成为理想的综合业务通用客户端平台。
Grizzly应用Java NIO框架,利用有限的线程为大量并发的socket连接提供服务,使得Server端有非常出色的扩展性。Grizzly Cometd为我们提供了针对Http请求的异步处理机制,加上对Bayeux协议的支持,我们可以在Cometd Servlet中创建消息队列,浏览器端可以灵活地订阅(Subscribe)消息,当Server端有新的消息需要分发时,将消息推送给订阅者;当然浏览器也可以主动地发布(Publish)消息,Server收到消息后,立即推送给此消息的订阅者(可以是Web浏览器,也可以是特殊的客户端或服务端的进程)。Glizzly Cometd的消息分发采用Bayeux协议。当前能够支持Grizzly+Bayeux的Servlet Container主要是Jetty6和Glassfish v2,Tomcat还不支持。在浏览器端,只有Dojo Cometd支持Bayeux协议。
在Jetty下测试Cometd Chat Demo非常顺利,我下载了最新的Jetty 6.1.7,在WinXP(sp2)的DOS窗口中启动Server:
java -jar start.jar etc/jetty.xml etc/jetty-grizzly.xml
使用IE/FF访问 http://localhost:8888/test/chat。
Grizzly是Glassfish的子项目,Glassfish直接使用grizzly的源码,重新打包。我下载的版本是最新发布的V2UR1。Chat Demo(grizzly-cometd-chat.war)来自Jean-Francois Arcand的blog。下载并安装Glassfish V2UR1,配置domains/domain1/config/domain.xml,将ws/tcp注释掉,添加cometSupport支持:
将war文件部署到domains/domain1/autodeploy目录,能够看到Cometd Chat登录界面,输入用户名点击"Join"按钮,显示聊天页面(参见cometd1.PNG),但是未显示已登入聊天室的提示信息。检查glassfish的log文件,发现有异常抛出:
注意:如果使用Glassfish的版本不是V2UR1,可能会得到java.lang.NullPointerException异常。
初步分析错误出自grizzly cometd包对bayeux协议解析时发生数据转型错误。上网抓grizzly的sourcecode,先分析CometdServlet,在分析EventRouter和VerbUtils,发现Glassfish没有使用grizzly的最新版本,再上网抓glassfish的sourcecode。为了便于单步跟踪,在MyEclipse中创建一个Web工程,建立一个ChatDemoServlet替代CometdServlet,建立一个VerbUtils替代原来的VerbUtils,在ChatDemoServlet的doPost()方法中嵌入以下代码:
在MyEclipse中经过单步调试,发现Bug原因是:
JSONParser.parse()对请求参数中的数值进行转型,有小数点时转型为Double,否则转型为Long。而VerbUtils的newSubscribe()只能接收类型为String的参数。改进方案很简单,将VerbUtils的newSubscribe()方法中的以下代码
替换为
重新编译(技巧:在MyEclipse工程中建立一个com.sun.grizzly.cometd.bayeux包,将glassfish源文件的压缩包展开,将appserv-http-engine/src/main/java/com/sun/grizzly/cometd/bayeux目录下所有的java文件导入到此包中),得到VerbUtils.class文件替换glassfish安装路径的lib目录下的appserv-rt.jar文件中的同名文件。
重新测试,server端log文件中没有异常记录,但是登录和发消息都没有看到预期的消息显示在对话内。采用MyEclipse Web2.0浏览器进行跟踪,发现chat.js工作不正常,没有发出http请求到服务端。由于本人对AJAX/Dojo的功力不足,未进一步分析是何原因。与Jetty6的chat demo对比,发现两者使用dojo版本不同,所以想到使用移花接木的手段,尝试用Jetty6的客户端访问Glassfish的服务端:
1)在MyEclipse工程中WebRoot目录下创建chat目录,导入index.html、chat.js和chat.css(参见附加文件)。
2)下载最新的Dojo API包:dojo-release-1.0.2.zip。解压缩后部署到WebRoot目录下。
3)修改index.html,内容如下:
4)改编chat.js,内容如下:
5)将此工程打包为cometd.war(Context Root Path必须是/cometd)部署到glassfish的domains/domain1/autodeploy/目录下。
重新测试http://localhost:8080/cometd/chat/index.html,期待已久的Cometd聊天室可以正常工作啦!
Grizzly应用Java NIO框架,利用有限的线程为大量并发的socket连接提供服务,使得Server端有非常出色的扩展性。Grizzly Cometd为我们提供了针对Http请求的异步处理机制,加上对Bayeux协议的支持,我们可以在Cometd Servlet中创建消息队列,浏览器端可以灵活地订阅(Subscribe)消息,当Server端有新的消息需要分发时,将消息推送给订阅者;当然浏览器也可以主动地发布(Publish)消息,Server收到消息后,立即推送给此消息的订阅者(可以是Web浏览器,也可以是特殊的客户端或服务端的进程)。Glizzly Cometd的消息分发采用Bayeux协议。当前能够支持Grizzly+Bayeux的Servlet Container主要是Jetty6和Glassfish v2,Tomcat还不支持。在浏览器端,只有Dojo Cometd支持Bayeux协议。
在Jetty下测试Cometd Chat Demo非常顺利,我下载了最新的Jetty 6.1.7,在WinXP(sp2)的DOS窗口中启动Server:
java -jar start.jar etc/jetty.xml etc/jetty-grizzly.xml
使用IE/FF访问 http://localhost:8888/test/chat。
Grizzly是Glassfish的子项目,Glassfish直接使用grizzly的源码,重新打包。我下载的版本是最新发布的V2UR1。Chat Demo(grizzly-cometd-chat.war)来自Jean-Francois Arcand的blog。下载并安装Glassfish V2UR1,配置domains/domain1/config/domain.xml,将ws/tcp注释掉,添加cometSupport支持:
<http-listener id="http-listener-1" address="0.0.0.0" port="8080" acceptor-threads="1" security-enabled="false" default-virtual-server="server" server-name="" xpowered-by="true" enabled="true" family="inet" blocking-enabled="false"> <!-- property name="proxiedProtocols" value="ws/tcp"/ --> <property name="cometSupport" value="true"/> </http-listener>
将war文件部署到domains/domain1/autodeploy目录,能够看到Cometd Chat登录界面,输入用户名点击"Join"按钮,显示聊天页面(参见cometd1.PNG),但是未显示已登入聊天室的提示信息。检查glassfish的log文件,发现有异常抛出:
引用
StandardWrapperValve[Grizzly Cometd Servlet]: PWC1406: Servlet.service() for servlet Grizzly Cometd Servlet threw exception
java.lang.ClassCastException: java.lang.Double
at com.sun.grizzly.cometd.bayeux.VerbUtils.newHandshake(VerbUtils.java:126)
at com.sun.grizzly.cometd.bayeux.VerbUtils.parseMap(VerbUtils.java:98)
at com.sun.grizzly.cometd.bayeux.VerbUtils.parse(VerbUtils.java:71)
at com.sun.grizzly.cometd.EventRouterImpl.route(EventRouterImpl.java:90)
at com.demo.servlet.ChatDemoServlet.doPost(ChatDemoServlet.java:152)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:738)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:831)
at org.apache.catalina.core.ApplicationFilterChain.servletService(ApplicationFilterChain.java:411)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:290)
at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:271)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:202)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:94)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:206)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:571)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1080)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:150)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:571)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1080)
at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:272)
at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.invokeAdapter(DefaultProcessorTask.java:637)
at com.sun.enterprise.web.connector.grizzly.comet.CometEngine.executeServlet(CometEngine.java:547)
at com.sun.enterprise.web.connector.grizzly.comet.CometEngine.handle(CometEngine.java:299)
at com.sun.enterprise.web.connector.grizzly.comet.CometAsyncFilter.doFilter(CometAsyncFilter.java:87)
at com.sun.enterprise.web.connector.grizzly.async.DefaultAsyncExecutor.invokeFilters(DefaultAsyncExecutor.java:175)
at com.sun.enterprise.web.connector.grizzly.async.DefaultAsyncExecutor.interrupt(DefaultAsyncExecutor.java:153)
at com.sun.enterprise.web.connector.grizzly.async.AsyncProcessorTask.doTask(AsyncProcessorTask.java:92)
at com.sun.enterprise.web.connector.grizzly.TaskBase.run(TaskBase.java:265)
at com.sun.enterprise.web.connector.grizzly.WorkerThreadImpl.run(WorkerThreadImpl.java:116)
java.lang.ClassCastException: java.lang.Double
at com.sun.grizzly.cometd.bayeux.VerbUtils.newHandshake(VerbUtils.java:126)
at com.sun.grizzly.cometd.bayeux.VerbUtils.parseMap(VerbUtils.java:98)
at com.sun.grizzly.cometd.bayeux.VerbUtils.parse(VerbUtils.java:71)
at com.sun.grizzly.cometd.EventRouterImpl.route(EventRouterImpl.java:90)
at com.demo.servlet.ChatDemoServlet.doPost(ChatDemoServlet.java:152)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:738)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:831)
at org.apache.catalina.core.ApplicationFilterChain.servletService(ApplicationFilterChain.java:411)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:290)
at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:271)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:202)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577)
at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:94)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:206)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:571)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1080)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:150)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:632)
at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:577)
at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:571)
at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1080)
at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:272)
at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.invokeAdapter(DefaultProcessorTask.java:637)
at com.sun.enterprise.web.connector.grizzly.comet.CometEngine.executeServlet(CometEngine.java:547)
at com.sun.enterprise.web.connector.grizzly.comet.CometEngine.handle(CometEngine.java:299)
at com.sun.enterprise.web.connector.grizzly.comet.CometAsyncFilter.doFilter(CometAsyncFilter.java:87)
at com.sun.enterprise.web.connector.grizzly.async.DefaultAsyncExecutor.invokeFilters(DefaultAsyncExecutor.java:175)
at com.sun.enterprise.web.connector.grizzly.async.DefaultAsyncExecutor.interrupt(DefaultAsyncExecutor.java:153)
at com.sun.enterprise.web.connector.grizzly.async.AsyncProcessorTask.doTask(AsyncProcessorTask.java:92)
at com.sun.enterprise.web.connector.grizzly.TaskBase.run(TaskBase.java:265)
at com.sun.enterprise.web.connector.grizzly.WorkerThreadImpl.run(WorkerThreadImpl.java:116)
注意:如果使用Glassfish的版本不是V2UR1,可能会得到java.lang.NullPointerException异常。
初步分析错误出自grizzly cometd包对bayeux协议解析时发生数据转型错误。上网抓grizzly的sourcecode,先分析CometdServlet,在分析EventRouter和VerbUtils,发现Glassfish没有使用grizzly的最新版本,再上网抓glassfish的sourcecode。为了便于单步跟踪,在MyEclipse中创建一个Web工程,建立一个ChatDemoServlet替代CometdServlet,建立一个VerbUtils替代原来的VerbUtils,在ChatDemoServlet的doPost()方法中嵌入以下代码:
@Override public void doPost(HttpServletRequest hreq, HttpServletResponse hres) throws ServletException, IOException { ...... String[] messages = hreq.getParameterValues("message"); if (messages != null && messages.length > 0){ for(String message: messages){ Object verbObj = JSONParser.parse(message); //Verb verb = VerbUtils.parse(verbObj); VerbBase wellFormedVerb = null; if (verbObj.getClass().isArray()){ int length = Array.getLength(verbObj); for (int i=0; i < length; i++){ wellFormedVerb = VerbUtils.parseMap((Map)Array.get(verbObj,i), wellFormedVerb); } } // Notify our listener; } } ...... }
在MyEclipse中经过单步调试,发现Bug原因是:
JSONParser.parse()对请求参数中的数值进行转型,有小数点时转型为Double,否则转型为Long。而VerbUtils的newSubscribe()只能接收类型为String的参数。改进方案很简单,将VerbUtils的newSubscribe()方法中的以下代码
handshake.setVersion((String)map.get("version")); handshake.setMinimumVersion((String)map.get("minimumVersion"));
替换为
Object ov = map.get("version"); if (ov instanceof String) handshake.setVersion((String)ov); else if (ov instanceof Number) handshake.setVersion(ov.toString()); else handshake.setVersion("1.0"); ov = map.get("minimumVersion"); if (ov instanceof String) handshake.setMinimumVersion((String)ov); else if (ov instanceof Number) handshake.setMinimumVersion(ov.toString()); else handshake.setMinimumVersion("0.1");
重新编译(技巧:在MyEclipse工程中建立一个com.sun.grizzly.cometd.bayeux包,将glassfish源文件的压缩包展开,将appserv-http-engine/src/main/java/com/sun/grizzly/cometd/bayeux目录下所有的java文件导入到此包中),得到VerbUtils.class文件替换glassfish安装路径的lib目录下的appserv-rt.jar文件中的同名文件。
重新测试,server端log文件中没有异常记录,但是登录和发消息都没有看到预期的消息显示在对话内。采用MyEclipse Web2.0浏览器进行跟踪,发现chat.js工作不正常,没有发出http请求到服务端。由于本人对AJAX/Dojo的功力不足,未进一步分析是何原因。与Jetty6的chat demo对比,发现两者使用dojo版本不同,所以想到使用移花接木的手段,尝试用Jetty6的客户端访问Glassfish的服务端:
1)在MyEclipse工程中WebRoot目录下创建chat目录,导入index.html、chat.js和chat.css(参见附加文件)。
2)下载最新的Dojo API包:dojo-release-1.0.2.zip。解压缩后部署到WebRoot目录下。
3)修改index.html,内容如下:
<html> <head> <title>Cometd chat</title> <script type="text/javascript" src="../dojo/dojo.js"></script> <script type="text/javascript" src="../dojox/cometd.js"></script> <script type="text/javascript" src="chat.js"></script> <link rel="stylesheet" type="text/css" href="chat.css"> </head> <body> <h1>Cometd Chat</h1> <div id="chatroom"> <div id="chat"></div> <div id="input"> <div id="join" > Username: <input id="username" type="text"/><input id="joinB" class="button" type="submit" name="join" value="Join"/> </div> <div id="joined" class="hidden"> Chat: <input id="phrase" type="text"></input> <input id="sendB" class="button" type="submit" name="join" value="Send"/> <input id="leaveB" class="button" type="submit" name="join" value="Leave"/> </div> </div> </div> </body>
4)改编chat.js,内容如下:
dojo.require("dojox.cometd"); //dojo.require("dojox.cometd.timesync"); var room = { _last: "", _username: null, _connected: true, join: function(name){ if(name == null || name.length==0 ){ alert('Please enter a username!'); }else{ dojox.cometd.init(new String(document.location).replace(/http:\/\/[^\/]*/,'').replace(/\/chat\/.*$/,'')+"/cometd"); // dojox.cometd.init("http://127.0.0.2:8080/cometd"); this._connected=true; this._username=name; dojo.byId('join').className='hidden'; dojo.byId('joined').className=''; dojo.byId('phrase').focus(); // subscribe and join dojox.cometd.startBatch(); dojox.cometd.subscribe("/chat/demo", room, "_chat"); dojox.cometd.publish("/chat/demo", { user: room._username, join: true, chat : room._username+" has joined"}); dojox.cometd.endBatch(); // handle cometd failures while in the room room._meta=dojo.subscribe("/cometd/meta",dojo.hitch(this,function(event){ console.debug(event); if (event.action=="handshake") { room._chat({data:{join:true,user:"SERVER",chat:"reinitialized"}}); dojox.cometd.subscribe("/chat/demo", room, "_chat"); } else if (event.action=="connect") { if (event.successful && !this._connected) room._chat({data:{leave:true,user:"SERVER",chat:"reconnected!"}}); if (!event.successful && this._connected) room._chat({data:{leave:true,user:"SERVER",chat:"disconnected!"}}); this._connected=event.successful; } })); } }, leave: function(){ if (room._username==null) return; if (room._meta) dojo.unsubscribe(room._meta); room._meta=null; dojox.cometd.startBatch(); dojox.cometd.unsubscribe("/chat/demo", room, "_chat"); dojox.cometd.publish("/chat/demo", { user: room._username, leave: true, chat : room._username+" has left"}); dojox.cometd.endBatch(); // switch the input form dojo.byId('join').className=''; dojo.byId('joined').className='hidden'; dojo.byId('username').focus(); room._username=null; dojox.cometd.disconnect(); }, chat: function(text){ if(!text || !text.length){ return false; } dojox.cometd.publish("/chat/demo", { user: room._username, chat: text}); }, _chat: function(message){ var chat=dojo.byId('chat'); if(!message.data){ alert("bad message format "+message); return; } var from=message.data.user; var special=message.data.join || message.data.leave; var text=message.data.chat; if(!text){ return; } if( !special && from == room._last ){ from="..."; }else{ room._last=from; from+=":"; } if(special){ chat.innerHTML += "<span class=\"alert\"><span class=\"from\">"+from+" </span><span class=\"text\">"+text+"</span></span><br/>"; room._last=""; }else{ chat.innerHTML += "<span class=\"from\">"+from+" </span><span class=\"text\">"+text+"</span><br/>"; } chat.scrollTop = chat.scrollHeight - chat.clientHeight; }, _init: function(){ dojo.byId('join').className=''; dojo.byId('joined').className='hidden'; dojo.byId('username').focus(); var element=dojo.byId('username'); element.setAttribute("autocomplete","OFF"); dojo.connect(element, "onkeyup", function(e){ if(e.keyCode == dojo.keys.ENTER){ room.join(dojo.byId('username').value); return false; } return true; }); element=dojo.byId('joinB'); element.onclick = function(){ room.join(dojo.byId('username').value); return false; } element=dojo.byId('phrase'); element.setAttribute("autocomplete","OFF"); dojo.connect(element, "onkeyup", function(e){ if(e.keyCode == dojo.keys.ENTER){ room.chat(dojo.byId('phrase').value); dojo.byId('phrase').value=''; return false; } return true; }); element=dojo.byId('sendB'); element.onclick = function(){ room.chat(dojo.byId('phrase').value); dojo.byId('phrase').value=''; } element=dojo.byId('leaveB'); element.onclick = function(){ room.leave(); } } }; dojo.addOnLoad(room, "_init"); dojo.addOnUnload(room,"leave");
5)将此工程打包为cometd.war(Context Root Path必须是/cometd)部署到glassfish的domains/domain1/autodeploy/目录下。
重新测试http://localhost:8080/cometd/chat/index.html,期待已久的Cometd聊天室可以正常工作啦!
相关推荐
Glassfish V2 和 NetBeans 6 是两个在Java EE开发领域中重要的开源工具。这篇博客主要探讨了如何结合这两个工具进行高效的应用程序开发。 Glassfish V2 是Oracle公司(当时称为Sun Microsystems)推出的Java EE应用...
《Glassfish V2与Apache整合详解》 在IT行业中,服务器的高效运行和灵活配置是提升应用程序性能的关键。本文将详细介绍如何将Java EE应用服务器Glassfish V2与Web服务器Apache进行整合,以实现更强大的服务架构。...
通过windows sc dos 命令给windows 服务添加启动服务。 sc create SS9PE binPath= "D:\appserver\glassfish-v2ur1\lib\appservService.exe
glassfish 安装构建在windows 中自动启动服务。
在本文中,我们将深入探讨Glassfish-v2的核心概念、架构设计以及关键组件,旨在帮助读者掌握这款应用服务器的内在运作机制。 一、Java EE 5标准概述 Java EE(Java Platform, Enterprise Edition)是Sun ...
此外,压缩包中的"demo.exe"和"demo.txt"文件可能与演示应用有关,"demo.exe"可能是Windows平台上的一个可执行程序,用于辅助演示或测试,而"demo.txt"可能是包含说明、日志信息或示例数据的文本文件。为了完整体验...
3. **安装Glassfish**:运行下载的jar包,如在Windows上,使用命令`java -Xmx256m -jar glassfish-installer-v2ur2-b04-windows.jar`。在安装过程中,同意许可协议,完成后,你会在当前目录看到新建的Glassfish...
在本文中,我们将深入探讨如何在Glassfish服务器上部署Web应用程序。Glassfish是一款开源的应用服务器,主要用于运行Java EE(现在称为Jakarta EE)应用程序,包括Web应用程序和企业级Java组件。 首先,对于Java EE...
在本EJBDemo中,我们重点关注如何在Glassfish服务器上开发和部署EJB应用。Glassfish是一款开源的Java EE应用服务器,它提供了全面的支持,包括对EJB的运行环境。 1. **EJB基础** EJB分为三种类型:会话Bean...
下载适用于Windows平台的GlassFish安装文件,文件名为`glassfish-installer-v2ur1-b09d-windows-ml.jar`,大小约为81MB。将下载的文件放置在指定目录,例如`D:\`下,并通过命令行进行安装: ```bash D:\>java –Xmx...
- 在命令行中执行`D:\glassfish\bin>asadmin start-domain domain1`启动GlassFish服务。 - 可以通过浏览器访问http://localhost:4848来登录GlassFish的管理控制台,用户名和密码均为“admin”。 3. **停止...
### JMX 在GlassFish中的应用 #### JMX与GlassFish:深入理解管理与监控 **JMX(Java Management Extensions)**是一种由Sun Microsystems提出并由Java社区推动的标准,旨在为Java应用程序、系统和网络提供一个...
1. **创建项目**:在NetBeans IDE中,通过“新建项目”向导,选择Java EE Web项目,为Web服务创建项目结构。 2. **编写服务接口**:定义SOAP或RESTful服务接口,如使用JAX-WS(Java API for XML Web Services)或...