研发一个调试JSP的Eclipse插件
发布时间:2023-05-20 13:48:53 所属栏目:教程 来源:
导读:本文通过开发一个jsp 编辑器插件的示例,介绍了 eclipse 中设置 jsp 断点的方法,以及如何远程调试 jsp。作为基础知识,本文的前两部分描述了 java debug 和 jsr-45 的基本原理。
环境要求: 本文的代码是在
环境要求: 本文的代码是在
本文通过开发一个jsp 编辑器插件的示例,介绍了 eclipse 中设置 jsp 断点的方法,以及如何远程调试 jsp。作为基础知识,本文的前两部分描述了 java debug 和 jsr-45 的基本原理。 环境要求: 本文的代码是在 eclipse3.0.0,jdk1.4.2 和 tomcat5.0.5 上测试过的。 java 调试框架(jpda)简介 jpda 是一个多层的调试框架,包括 jvmdi、jdwp、jdi 三个层次。java 虚拟机提供了 jpda 的实现。其开发工具作为调试客户端,可以方便的与虚拟机通讯,进行调试。eclipse 正是利用 jpda 调试 java 应用,事实上,所有 java 开发工具都是这样做的。sun jdk 还带了一个比较简单的调试工具以及示例。 jvmdi 定义了虚拟机需要实现的本地接口 jdwp 定义了jvm与调试客户端之间的通讯协议 调试客户端和jvm 既可以在同一台机器上,也可以远程调试。jdk 会包含一个默认的实现 jdwp.dll,jvm 允许灵活的使用其他协议代替 jdwp。sun jdk 有两种方式传输通讯协议:socket 和共享内存(后者仅仅针对 windows),一般我们都采用 socket 方式。 你可以用下面的参数,以调试模式启动jvm -xdebug -xnoagent -xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n -xrunjdwp jvm 加载 jdwp.dll transport=dt_socket 使用 socket 传输 address 表示调试端口号 server=y 表示 jvm 作为服务器,建立 socket suspend=n 表示启动过程中,jvm 不会挂起去等待调试客户端连接 jdi 则是一组java接口 如果是一个 java 的调试客户端,只要实现 jdi 接口,利用jdwp协议,与虚拟机通讯,就可以调用jvmdi了。 下图为 jpda 的基本架构: components debugger interface / |-----------------------| / | vm | debuggee ----( |-----------------------| <------- jvmdi - java vm debug interface / | back-end | / |-----------------------| / | comm channel -( | <--------------- jdwp - java debug wire protocol / | |---------------------| | front-end | |---------------------| <------- jdi - java debug interface | ui | |---------------------| 参见:http://java.sun.com/j2se/1.4.2/docs/guide/jpda/architecture.html eclipse作为一个基于 java 的调试客户端,利用 org.eclipse.jdt.debug plugin 提供了jdi 的具体实现。jdi 接口主要包含下面 4 个包 com.sun.jdi com.sun.jdi.connect com.sun.jdi.event com.sun.jdi.request 本文不对 jdi 进行深入阐述,这里重点介绍 jdi 中与断点相关的接口。 com.sun.jdi 主要是jvm(virtualmachine) 线程(threadreference) 调用栈(stackframe) 以及类型、实例的描述。利用这组接口,调试客户端可以用类似类反射的方式,得到所有类型的定义,动态调用 class 的方法。 com.sun.jdi.event 封装了jvm 产生的事件, jvm 正是将这些事件通知给调试客户端的。例如 breakpointevent 就是 jvm 执行到断点的时候,发出的事件;classprepareevent就是 class 被加载时发出的事件。 com.sun.jdi.request 封装了调试客户端可以向 jvm发起的请求。例如 breakpointrequest 向 jvm 发起一个添加断点的请求;classpreparerequest 向 jvm 注册一个类加载请求,jvm 在加载指定 class 的时候,就会发出一个 classprepareevent 事件。 jsr-45规范 jsr-45(debugging support for other languages)为那些非 java 语言写成,却需要编译成 java 代码,运行在 jvm 中的程序,提供了一个进行调试的标准机制。也许字面的意思有点不好理解,什么算是非 java 语言呢?其实 jsp 就是一个再好不过的例子,jsr-45 的样例就是一个 jsp。 jsp的调试一直依赖于具体应用服务器的实现,没有一个统一的模式,jsr-45 针对这种情况,提供了一个标准的模式。我们知道,java 的调试中,主要根据行号作为标志,进行定位。但是 jsp 被编译为 java 代码之后,java 行号与 jsp 行号无法一一对应,怎样解决呢? jsr-45 是这样规定的:jsp 被编译成 java 代码时,同时生成一份 jsp 文件名和行号与 java 行号之间的对应表(smap)。jvm 在接受到调试客户端请求后,可以根据这个对应表(smap),从 jsp 的行号转换到 java 代码的行号;jvm 发出事件通知前, 也根据对应表(smap)进行转化,直接将 jsp 的文件名和行号通知调试客户端。 我们用 tomcat 5.0 做个测试,有两个 jsp,hello.jsp 和 greeting.jsp,前者 include 后者。tomcat会将他们编译成 java 代码(hello_jsp.java),java class(hello_jsp.class) 以及 jsp 文件名/行号和 java 行号之间的对应表(smap)。 hello.jsp: 1 <html> 2 <head> 3 <title>hello example</title> 4 </head> 5 <body> 6 <%@ include file="greeting.jsp" %> 7 </body> 8 </html> greeting.jsp: 1 hello there!<p> 2 goodbye on <%= new java.util.date() %> jsp 编译后产生的hello_jsp.java 如下: hello_jsp.java: 1 package org.apache.jsp; 2 3 import javax.servlet.*; 4 import javax.servlet.http.*; 5 import javax.servlet.jsp.*; 6 7 public final class hello_jsp extends org.apache.jasper.runtime.httpjspbase 8 implements org.apache.jasper.runtime.jspsourcedependent { 9 10 private static java.util.vector _jspx_dependants; 11 12 static { 13 _jspx_dependants = new java.util.vector(1); 14 _jspx_dependants.add("/greeting.jsp"); 15 } 16 17 public java.util.list getdependants() { 18 return _jspx_dependants; 19 } 20 21 public void _jspservice(httpservletrequest request, httpservletresponse response) 22 throws java.io.ioexception, servletexception { 23 24 jspfactory _jspxfactory = null; 25 pagecontext pagecontext = null; 26 httpsession session = null; 27 servletcontext application = null; 28 servletconfig config = null; 29 jspwriter out = null; 30 object page = this; 31 jspwriter _jspx_out = null; 32 33 34 try { 35 _jspxfactory = jspfactory.getdefaultfactory(); 36 response.setcontenttype("text/html"); 37 pagecontext = _jspxfactory.getpagecontext(this, request, response, 38 null, true, 8192, true); 39 application = pagecontext.getservletcontext(); 40 config = pagecontext.getservletconfig(); 41 session = pagecontext.getsession(); 42 out = pagecontext.getout(); 43 _jspx_out = out; 44 45 out.write("<html> /r/n"); 46 out.write("<head> /r/n"); 47 out.write("<title>hello example"); 48 out.write("</title> /r/n"); 49 out.write("</head> /r/n"); 50 out.write("<body> /r/n"); 51 out.write("hello there!"); 52 out.write("<p> /r/ngoodbye on "); 53 out.write(string.valueof( new java.util.date() )); 54 out.write(" /r/n"); 55 out.write(" /r/n"); 56 out.write("</body> /r/n"); 57 out.write("</html> /r/n"); 58 } catch (throwable t) { 59 if (!(t instanceof javax.servlet.jsp.skippageexception)){ 60 out = _jspx_out; 61 if (out != null && out.getbuffersize() != 0) 62 out.clearbuffer(); 63 if (pagecontext != null) pagecontext.handlepageexception(t); 64 } 65 } finally { 66 if (_jspxfactory != null) _jspxfactory.releasepagecontext ( pagecontext); 67 } 68 } 69 } tomcat 又将这个 java 代码编译为 hello_jsp.class,他们位于: $tomcat_install_path$/work/standalone/localhost/_ 目录下。但是 jsp 文件名/行号和 java 行号的对应表(以下简称smap) 在哪里呢?答案是,它保存在 class 中。如果用 ultraedit 打开这个 class 文件,就可以找到 sourcedebugextension 属性,这个属性用来保存 smap。 jvm 规范定义了 classfile 中可以包含 sourcedebugextension 属性,保存 smap: sourcedebugextension_attribute { u2 attribute_name_index; u4 attribute_length; u1 debug_extension[attribute_length]; } 我用 javassist 做了一个测试(javassist可是一个好东东,它可以动态改变class的结构,jboss 的 aop就利用了javassist,这里我们只使用它读取classfile的属性) public static void main(string[] args) throws exception{ string[]files = { "e://tomcat5_0_5//work//catalina//localhost//_//org//apache//jsp//hello_jsp.class", }; for(int k = 0; k < files.length; k++){ string file = files[k]; system.out.println("class : " + file); classfile classfile = new classfile(new datainputstream(new fileinputstream(file))); attributeinfo attributeinfo = classfile.getattribute("sourcedebugextension"); system.out.println("attribute name :" + attributeinfo.getname() + "]/n/n"); byte[]bytes = attributeinfo.get(); string str = new string(bytes); system.out.println(str); } } 这段代码显示了sourcedebugextension 属性,你可以看到smap 的内容。编译jsp后,smap 就被写入 class 中, 你也可以利用 javassist 修改 classfile 的属性。 下面就是 hello_jsp.class 中保存的 smap 内容: smap e:/tomcat5_0_5/work/catalina/localhost/_/org/apache/jsp/hello_jsp.java jsp *s jsp *f + 0 hello.jsp /hello.jsp + 1 greeting.jsp /greeting.jsp *l 1:45 2:46 3:47 3:48 4:49 5:50 1#1:51 1:52 2:53 7#0:56 8:57 *e 首先注明java代码的名称:hello_jsp.java,然后是 stratum 名称: jsp。随后是两个jsp文件的名称 :hello.jsp、greeting.jsp。两个jsp文件共10行,产生的hello_jsp共69行代码。最后也是最重要的内容就是源文件文件名/行号和目标文件行号的对应关系(*l 与 *e之间的部分) 在规范定义了这样的格式: 源文件行号 # 源文件代号,重复次数 : 目标文件开始行号,目标文件行号每次增加的数量 (inputstartline # linefileid , repeatcount : outputstartline , outputlineincrement) 源文件行号(inputstartline) 目标文件开始行号(outputstartline) 是必须的。下面是对这个smap具体的说明: 1:45 2:46 3:47 3:48 4:49 5:50(没有源文件代号,默认为hello.jsp) 开始行号 结束行号 hello.jsp: 1 -> hello_jsp.java: 45 2 -> 46 3 -> 47 48 4 -> 49 5 -> 50 1#1:51 1:52 2:53(1#1表示 greeting.jsp 的第1行) greeting.jsp: 1 -> hello_jsp.java: 51 52 2 -> 53 7#0:56 8:57(7#0表示 hello.jsp 的第7行) hello.jsp: 7 -> hello_jsp.java: 56 8 -> 57 开发一个jsp编辑器 eclipse 提供了 texteditor,作为文本编辑器的父类。由于 editor 的开发不是本文的重点,不做具体论述。我们可以利用 eclipse 的 plugin 项目向导,生成一个简单的 jsp 编辑器: (1)点击 file 菜单,new -> project -> plug-in project ; (2)输入项目名称 jsp_debug,下一步; (3)输入 plugin id : com.jsp.debug plugin class name : com.jsp.debug.jsp_debugplugin (4)选择用模板创建 使用 plug-in with editor,输入 java package name :com.jsp.editors editor class name :jspeditor file extension :jsp 一个 jsp editor 就产生了。 运行这个plugin,新建一个java项目,新建一个 hello.jsp 和 greeting.jsp,在 navigator 视图双击 jsp,这个editor就打开了。 在jsp编辑器中设置断点 在编辑器中添加断点的操作方式有两种,一种是在编辑器左侧垂直标尺上双击,另一种是在左侧垂直标尺上点击鼠标右键,选择菜单"添加/删除断点"。 在 eclipse 的实现中,添加断点实际上就是为 ifile 添加一个marker ,类型是ibreakpoint.breakpoint_marker,然后将断点注册到 breakpointmanager。 breakpointmanager 将产生一个 breakpointrequest,通知正在运行的jvm target,如果此时还没有启动 jvm,会在 jvm 启动的时候,将所有断点一起通知 jvm target。 添加断点使用一个 abstractruleractiondelegate,重载 createaction 方法,返回一个 iaction managebreakpointruleraction动作: public class managebreakpointruleractiondelegate extends abstractruleractiondelegate{ protected iaction createaction(itexteditor editor, iverticalrulerinfo rulerinfo) { return new managebreakpointruleraction(rulerinfo, editor); } } 为了将 managebreakpointruleractiondelegate 添加到文本编辑器左侧标尺的鼠标右键菜单,并且能够处理左侧标尺的鼠标双击事件,在 plugin.xml 中加入定义。 处理双击事件: <extension point="org.eclipse.ui.editoractions"> <editorcontribution targetid="com.jiaoly.editors.jspeditor" id="com.jiaoly.debug.managebreakpointruleractiondelegate"> <action label="添加/删除断点" class="com.jiaoly.debug.managebreakpointruleractiondelegate" actionid="rulerdoubleclick" id="com.jiaoly.debug.managebreakpointruleractiondelegate"> </action> </editorcontribution> </extension> 添加右键菜单: <extension point="org.eclipse.ui.popupmenus"> <viewercontribution targetid="#textrulercontext" id="com.jiaoly.debug.managebreakpointruleractiondelegate"> <action label="添加/删除断点" class="com.jiaoly.debug.managebreakpointruleractiondelegate" menubarpath="addition" id="com.jiaoly.debug.managebreakpointruleractiondelegate"> </action> </viewercontribution> </extension> managebreakpointruleraction 是实际添加断点的action,实现了 iupdate 接口,这个action的工作,就是判断当前选中行是否存在断点类型的 marker,如果不存在创建一个,如果存在,将它删除。 public class managebreakpointruleraction extends action implements iupdate{ private iverticalrulerinfo rulerinfo; private itexteditor texteditor; private string bpmarkertype ; //当点marker的类型 private list allmarkers; //当前鼠标点击行所有的marker private string addbp; //action 的显示名称 public managebreakpointruleraction(iverticalrulerinfo ruler, itexteditor editor){ this.rulerinfo = ruler; this.texteditor = editor; bpmarkertype = ibreakpoint.breakpoint_marker; addbp = "添加/删除断点"; //$non-nls-1$ settext(this.addbp); } public void update() { this.allmarkers = this.fetchbpmarkerlist(); } public void run(){ if(this.allmarkers.isempty()) this.addmarker(); else this.removemarkers(this.allmarkers); } } update 方法会在点击时首先调用,这时就可以收集当前选中行是否有marker了(调用fetchbpmarkerlist方法),如果有,就保存在 变量allmarkers 中。由于managebreakpointruleraction每一次都产生一个新的实例,因此不会产生冲突。 下面是update的调用栈,可以看出,update方法是在鼠标点击事件中被调用的: managebreakpointruleraction.update() line: 55 managebreakpointruleractiondelegate(abstractruleractiondelegate).update() line: 114 managebreakpointruleractiondelegate(abstractruleractiondelegate).mousedown(mouseevent) line: 139 updae被调用后,会执行 run 方法,就可以根据 allmarkers.isempty() 确定要删除还是添加 marker 了。 添加断点的时候,首先利用 iverticalrulerinfo,获取鼠标点击的行号,根据行号,从 document 模型中取得该行的描述iregion,得到开始字符位置和结束字符位置,创建一个 jsp 断点。 protected void addmarker() { ieditorinput editorinput= this.gettexteditor().geteditorinput(); idocument document= this.getdocument(); //the line number of the last mouse button activity int rulerline= this.getrulerinfo().getlineoflastmousebuttonactivity(); try{ int linenum = rulerline + 1; if(linenum > 0){ //returns a description of the specified line iregion iregion = document.getlineinformation(linenum - 1); int charstart = iregion.getoffset(); int charend = (charstart + iregion.getlength()) - 1; jspdebugutility.createjsplinebreakpoint(this.getresource(), linenum, charstart, charend); } }catch(coreexception coreexception){ coreexception.printstacktrace(); } catch(badlocationexception badlocationexception){ badlocationexception.printstacktrace(); } } 注册 jsp 断点为支持 jsr-45 规范,eclipse 中提供了 javastratumlinebreakpoint。不过它目前是一个 internal 的实现,在以后的版本中不能保证不作修改。这里为了简单起见,直接从 javastratumlinebreakpoint 继承。 public class jspbreakpoint extends javastratumlinebreakpoint { public jspbreakpoint(iresource resource, string stratum, string sourcename, string sourcepath, string classnamepattern, int linenumber, int charstart, int charend, int hitcount, boolean register, map attributes) throws debugexception { super(resource, stratum, sourcename, sourcepath, classnamepattern, linenumber, charstart, charend, hitcount, register, attributes); } } 查看 javastratumlinebreakpoint 的源代码可以知道,创建 javastratumlinebreakpoint 的时候做了两件事情: (1) 创建断点类型的 marker, 并且设置了marker的属性resource.createmarker(markertype); (2) 将断点注册到断点管理器 debugplugin.getdefault().getbreakpointmanager().addbreakpoint(this); 断点管理器负责产生一个 breakpointrequest,通知正在运行的jvm target 如果此时还没有启动 jvm,会在 jvm 启动的时候,将所有断点一起通知 jvm target。 下面是 javastratumlinebreakpoint 构造函数中的代码: iworkspacerunnable wr= new iworkspacerunnable() { public void run(iprogressmonitor monitor) throws coreexception { // create the marker setmarker(resource.createmarker(markertype)); // modify pattern string pattern = classnamepattern; if (pattern != null && pattern.length() == 0) { pattern = null; } // add attributes addlinebreakpointattributes(attributes, getmodelidentifier(), true, linenumber, charstart, charend); addstratumpatternandhitcount(attributes, stratum, sourcename, sourcepath, pattern, hitcount); // set attributes ensuremarker().setattributes(attributes); register(register); } }; run(null, wr); protected void register(boolean register) throws coreexception { if (register) { debugplugin.getdefault().getbreakpointmanager().addbreakpoint(this); } else { setregistered(false); } } 移除断点的时候,根据 marker 找到相应的 ibreakpoint,从 breakpointmanager 中移除 breakpointmanager 会自动删除 marker,通知 jvm target。 breakpointmanager = debugplugin.getdefault().getbreakpointmanager(); ibreakpoint breakpoint = breakpointmanager.getbreakpoint(imarker); breakpointmanager.removebreakpoint(breakpoint, true); jspbreakpoint 重载了父类的addtotarget(jdidebugtarget target) 方法。重载这个方法的目的是根据不同的应用服务器,设置不同的 referencetypename和sourcepath。我们知道,每种应用服务器编译 jsp 产生java class 名称的规则都不相同,例如tomcat编译hello.jsp 产生的java 类名为 org.apache.jsp. hello_jsp,而websphere6.0 却是 com.ibm._jsp._hello。只有确定服务器类型,才能知道referencetypename 和souecepath应该是什么。目前通过启动 jvm 时target 名称来判断应用服务器类型: string targetstring = target.getlaunch().getlaunchconfiguration().getname(); 如果targetstring 包含 tomcat ,就认为是 tomcat。 产生 referencetypename 后首先创建一个 classpreparerequest 通知,然后从vm中取出所有的classes,如果是当前的 class,再创建一个添加断点通知。之所以这样做,是因为有可能这个 class 还没有被 jvm 加载,直接通知 jvm 没有任何意义。在 class 被加载的时候,jvm 会通知 eclipse,这个时候,才产生添加断点通知。需要指出的是,本文示例代码获取 referencetypename 的方法不是很完善: (1) 仅仅实现了tomcat 读者有兴趣可以实现更多的web容器,例如 jboss3 以上,websphere6.0 (2) 一些特殊情况没有处理例如 路径名为package的jsp,路径名或文件名带有数字的jsp public void addtotarget(jdidebugtarget target) throws coreexception { imarker marker = this.getmarker(); iresource resource = marker.getresource(); string targetstring = target.getlaunch().getlaunchconfiguration().getname(); ijspnameutil util = jspdebugutility.getjspnameutil(targetstring); // pre-notification fireadding(target); string referencetypename; try { referencetypename = getpattern(); //如果没有设置 pattern, 根据 server 的类型, 产生新的 pattern if(referencetypename == null || "".equals(referencetypename.trim()) || "*".equals(referencetypename.trim())){ referencetypename = util.referencetypename(resource); } } catch (coreexception e) { jdidebugplugin.log(e); return; } this.ensuremarker().setattribute(type_name, referencetypename); string sourcepath = util.sourcepath(resource); this.ensuremarker().setattribute(jspbreakpoint.source_path, sourcepath); string classpreparetypename= referencetypename; //如果这时 class 还没有被加载, 注册一个 classpreparerequest 请求 // //当 class 加载的时候, 首先会触发 javabreakpoint 的 handleclassprepareevent 方法 //调用 createrequest(target, event.referencetype()) --> newrequest() --> // createlinebreakpointrequest() 创建 enable或disable 断点的请求 // // 设置 enable/disable 动作在 configurerequest() --> updateenabledstate(request) 方法中 // 根据 getmarker().getattribute(enabled, false) 确定断点是否有效 registerrequest(target.createclasspreparerequest(classpreparetypename), target); // create breakpoint requests for each class currently loaded virtualmachine vm = target.getvm(); if (vm == null) { target.requestfailed("unable_to_add_breakpoint_-_vm_disconnected._1"), null); } list classes = null; try { classes= vm.allclasses(); } catch (runtimeexception e) { target.targetrequestfailed("javapatternbreakpoint.0"), e); } if (classes != null) { iterator iter = classes.iterator(); while (iter.hasnext()) { referencetype type= (referencetype)iter.next(); if (installablereferencetype(type, target)) { createrequest(target, type); } } } } 调试jsp 现在我们可以调试 jsp 了。 (1)运行 jsp_debug plugin 首先在 run -> run 中添加一个 run-time workbench,点击 run 按钮,eclipse 的plugin开发环境会启动一个新的eclipse,这个新启动的 eclipse 中,我们创建的 jsp_debug plugin 就可以使用了。新建 一个 java 项目 test (注意,一定要是java项目),新建一个 hello.jsp 和 greeting.jsp,打开hello.jsp,在编辑器左侧标尺双击,就出现了一个断点。 (2)以 debug 模式启动tomcat: windows 开始 -> 运行,键入 cmd,启动一个命令行窗口: cd e:/tomcat5_0_5/bin (我的 tomcat 安装在 e:/tomcat5_0_5 目录,jdk 安装在 d:/j2sdk1.4.2) d:/j2sdk1.4.2/bin/java -xdebug -xnoagent -xrunjdwp:transport=dt_socket,address=8888,server=y, suspend=n -djava.endorsed.dirs="../common/endorsed" -classpath "d:/j2sdk1.4.2/lib/tools.jar;../bin/bootstrap.jar" -dcatalina.base=".." -dcatalina.home=".." -djava.io.tmpdir="../temp" org.apache.catalina.startup.bootstrap start -xdebug -xnoagent -xrunjdwp:transport=dt_socket,address=8888,server=y,suspend=n 表示以调试方式启动,端口号是 8888 classpath中要加入 d:/j2sdk1.4.2/lib/tools.jar,因为我是 tomcat5.0.5,如果是5.5就不需要了。 (3) 测试hello.jsp 将 hello.jsp 和 greeting.jsp 拷贝到 e:/tomcat5_0_5/webapps/root 目录,从浏览器访问 hello.jsp http://localhost:8000/hello.jsp。成功的话就可以继续下面的工作了,如果失败,检查你的tomcat设置。 (4)启动远程调试 在 eclipse 中启动远程调试,将 eclipse 作为一个 debug 客户端,连接到 tomcat 。在 java 透视图中,点击 run -> debug ,添加一个 remote java application,名称是 start tomcat server(不能错,因为我们要根据这个名称,判断当前的 web server 类型) project是创建的 test 项目 port 为 8888,和启动 tomcat 时设置的一样 点击 debug 按钮,就可以连接到 tomcat 上了。切换到 debug 透视图,在debug 视图中,能够看到所有 tomcat 中线程的列表。 (5)调试hello.jsp 为 hello.jsp 添加断点,然后从浏览器访问hello.jsp,就可以在断点处挂起了。你可以使用单步执行,也可以在variables视图查看jsp中的变量信息。 由于 eclipse 自身的实现,现在的 jsp editor 有一个问题,单步执行到 include jsp 行后,会从hello.jsp的1行再次执行。这是因为 eclipse jdt debug视图缓存了 stackframe 中已经打开的editor,stackframe不改变时,不会再重新计算当前调试的是否是其他resource。本来应该打开 greeting.jsp的,现在却从 hello.jsp 的第 1 行开始执行了。 结束语 很多集成开发环境都支持 jsp 的调试,在 eclipse 中也有 myeclipse 这样的插件完成类似的功能。但是在 jsr-45 规范产生前,每种应用服务器对 jsp debug 的实现是不一样的,例如 websphere 5 就是在 jsp 编译产生的 java 代码中加入了两个数组,表示源文件和行号的对应信息。tomcat 率先实现了 jsr-45 规范,websphere 6.0 现在也采取这种模式, 有兴趣的话,可以查看 websphere 6.0 编译的 class,和 tomcat 不一样,smap 文件会和java代码同时产生。 但是启动server前,需要设置 jvm 参数 was.debug.mode = true 同时在 ibm-web-ext.xmi 中设置 <jspattributes xmi:id="jspattribute_0" name="keepgenerated" value="true"/> <jspattributes xmi:id="jspattribute_1" name="createdebugclassfiles" value="true"/> <jspattributes xmi:id="jspattribute_2" name="debugenabled" value="true"/> 利用本文的基本原理,我们也可以开发其他基于 java 脚本语言的编辑器(例如 groovy),为这个编译器加入 debug 的功能。 (编辑:聊城站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
站长推荐