站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Eclipse 专题开发一个调试JSP的Eclipse插件

开发一个调试JSP的Eclipse插件

摘要: 本文通过开发一个JSP 编辑器插件的示例,介绍了 Eclipse 中设置 JSP 断点的方法,以及如何远程调试 JSP。
  本文通过开发一个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

    <table borderColor=#cccccc width="90%" bgColor=#e3e3e3 border=1> <tr> <td>
      -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 不会挂起去等待调试客户端连接
    </td></tr></table>
  • JDI 则是一组JAVA接口

    如果是一个 JAVA 的调试客户端,只要实现 JDI 接口,利用JDWP协议,与虚拟机通讯,就可以调用JVMDI了。
  下图为 JPDA 的基本架构:

<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1> <tr> <td>

                          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      |

                     |---------------------|

             
</td></tr></table>
  参见: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:

<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1> <tr> <td>

          1        

          2    <head>    

          3    Hello Example    

          4    </head>    

          5    <body>    

          6    <%@ include file="greeting.jsp" %>    

          7    </body>    

          8      

          
</td></tr></table>
  greeting.jsp:

1 Hello There!

2 Goodbye on <%= new java.util.Date() %>

  JSP 编译后产生的Hello_jsp.java 如下:

<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1> <tr> <td>

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("    \r\n");

46            out.write("<head>    \r\n");

47            out.write("Hello Example");

48            out.write("    \r\n");

49            out.write("</head>    \r\n");

50            out.write("<body>    \r\n");

51            out.write("Hello There!");

52            out.write("

\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(" \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 }

</td></tr></table>
  Tomcat 又将这个 JAVA 代码编译为 Hello_jsp.class,他们位于: $Tomcat_install_path$\work\Standalone\localhost\_ 目录下。但是 JSP 文件名/行号和 JAVA 行号的对应表(以下简称SMAP) 在哪里呢?答案是,它保存在 Class 中。如果用 UltraEdit 打开这个 Class 文件,就可以找到 SourceDebugExtension 属性,这个属性用来保存 SMAP。

  JVM 规范定义了 ClassFile 中可以包含 SourceDebugExtension 属性,保存 SMAP:

<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1> <tr> <td>
SourceDebugExtension_attribute {

  u2 attribute_name_index;

  u4 attribute_length;

  u1 debug_extension[attribute_length];

}

</td></tr></table>
  我用 javassist 做了一个测试(javassist可是一个好东东,它可以动态改变Class的结构,JBOSS 的 AOP就利用了javassist,这里我们只使用它读取ClassFile的属性)

<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1> <tr> <td>

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);   

   }

}

</td></tr></table>
  这段代码显示了SourceDebugExtension 属性,你可以看到SMAP 的内容。编译JSP后,SMAP 就被写入 Class 中, 你也可以利用 javassist 修改 ClassFile 的属性。

  下面就是 Hello_jsp.class 中保存的 SMAP 内容:

<table borderColor=#cccccc width="90%" align=center bgColor=#e3e3e3 border=1> <tr> <td>
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
</td></tr></table>
  首先注明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具体的说明:

<table cellSpacing=0 cellPadding=5 width="100%" bgColor=#eeeeee border=1> <tr> <td>

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

</td></tr></table>



↑返回目录
前一篇: Eclipse插件开发之Bytecode Outline
后一篇: Eclipse中插件开发之SQLExplorer