首页 > 开发 > JSP > 正文

开发一个调试JSP的Eclipse插件

2020-02-05 13:43:01
字体:
来源:转载
供稿:网友
  本文通过开发一个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 的功能。
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表