Java Tools

            BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理 ::
            83 隨筆 :: 0 文章 :: 16 評論 :: 0 Trackbacks

          開發一個調試 JSP 的 Eclipse 插件

          developerWorks
          文檔選項
          將此頁作為電子郵件發送

          將此頁作為電子郵件發送

          未顯示需要 JavaScript 的文檔選項

          樣例代碼



          級別: 初級

          焦烈焱 (jiaolieyan@hotmail.com), 程序經理

          2005 年 6 月 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 的功能。






          回頁首


          下載

          名字 大小 下載方法
          debug.zip 260 KB HTTP
          jsp_debug_project.zip 280 KB HTTP
          關于下載方法的信息 Get Adobe® Reader®


          參考資料



          關于作者

           

          焦烈焱,上海普元軟件公司程序經理,通過 jiaolieyan@hotmail.com 可以和他聯系。

          posted on 2007-07-03 16:40 和田雨 閱讀(411) 評論(0)  編輯  收藏 所屬分類: JSPEclipse
          主站蜘蛛池模板: 运城市| 麟游县| 长顺县| 黄冈市| 阳新县| 衡东县| 通渭县| 兴义市| 寿宁县| 阜城县| 临沭县| 太湖县| 博罗县| 观塘区| 搜索| 鹰潭市| 南城县| 嘉祥县| 慈溪市| 巴彦淖尔市| 赣州市| 石台县| 女性| 日土县| 兴安县| 嵊泗县| 巴彦县| 临夏市| 遂平县| 静安区| 陆良县| 延寿县| 阳春市| 清徐县| 章丘市| 两当县| 景谷| 高阳县| 达拉特旗| 都安| 马公市|