熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java開源技術 >> 正文

開發一個調試 JSP 的 Eclipse 插件

2022-06-13   來源: Java開源技術 

  JAVA 調試框架(JPDA)簡介

  JPDA 是一個多層的調試框架包括 JVMDIJDWPJDI 三個層次JAVA 虛擬機提供了 JPDA 的實現其開發工具作為調試客戶端可以方便的與虛擬機通訊進行調試Eclipse 正是利用 JPDA 調試 JAVA 應用事實上所有 JAVA 開發工具都是這樣做的SUN JDK 還帶了一個比較簡單的調試工具以及示例

  JVMDI 定義了虛擬機需要實現的本地接口

  JDWP 定義了JVM與調試客戶端之間的通訊協議

  調試客戶端和JVM 既可以在同一台機器上也可以遠程調試JDK 會包含一個默認的實現 jdwpdllJVM 允許靈活的使用其他協議代替 JDWPSUN JDK 有兩種方式傳輸通訊協議Socket 和共享內存(後者僅僅針對 Windows)一般我們都采用 Socket 方式

  你可以用下面的參數以調試模式啟動JVM

  Xdebug Xnoagent Xrunjdwp:transport=dt_socketaddress=server=ysuspend=n Xrunjdwp JVM 加載 jdwpdll 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 \ | backend | \ || / | comm channel ( | < JDWP Java Debug Wire Protocol \ | || | frontend | || < JDI Java Debug Interface | UI | ||

  Eclipse作為一個基於 JAVA 的調試客戶端利用 orgeclipsejdtdebug Plugin 提供了JDI 的具體實現JDI 接口主要包含下面 個包

  comsunjdi

  nnect

  comsunjdievent

  comsunjdirequest

  本文不對 JDI 進行深入闡述這裡重點介紹 JDI 中與斷點相關的接口

  comsunjdi

  主要是JVM(VirtualMachine) 線程(ThreadReference) 調用棧(StackFrame) 以及類型實例的描述利用這組接口調試客戶端可以用類似類反射的方式得到所有類型的定義動態調用 Class 的方法

  comsunjdievent

  封裝了JVM 產生的事件 JVM 正是將這些事件通知給調試客戶端的例如 BreakpointEvent 就是 JVM 執行到斷點的時候發出的事件;ClassPrepareEvent就是 Class 被加載時發出的事件

  comsunjdirequest

  封裝了調試客戶端可以向 JVM發起的請求例如 BreakpointRequest 向 JVM 發起一個添加斷點的請求;ClassPrepareRequest 向 JVM 注冊一個類加載請求JVM 在加載指定 Class 的時候就會發出一個 ClassPrepareEvent 事件

  JSR規范

  JSR(Debugging Support for Other Languages)為那些非 JAVA 語言寫成卻需要編譯成 JAVA 代碼運行在 JVM 中的程序提供了一個進行調試的標准機制也許字面的意思有點不好理解什麼算是非 JAVA 語言呢?其實 JSP 就是一個再好不過的例子JSR 的樣例就是一個 JSP

  JSP的調試一直依賴於具體應用服務器的實現沒有一個統一的模式JSR 針對這種情況提供了一個標准的模式我們知道JAVA 的調試中主要根據行號作為標志進行定位但是 JSP 被編譯為 JAVA 代碼之後JAVA 行號與 JSP 行號無法一一對應怎樣解決呢?

  JSR 是這樣規定的JSP 被編譯成 JAVA 代碼時同時生成一份 JSP 文件名和行號與 JAVA 行號之間的對應表(SMAP)JVM 在接受到調試客戶端請求後可以根據這個對應表(SMAP)從 JSP 的行號轉換到 JAVA 代碼的行號;JVM 發出事件通知前 也根據對應表(SMAP)進行轉化直接將 JSP 的文件名和行號通知調試客戶端

  我們用 Tomcat 做個測試有兩個 JSPHellojsp 和 greetingjsp前者 include 後者Tomcat會將他們編譯成 JAVA 代碼(Hello_jspjava)JAVA Class(Hello_jspclass) 以及 JSP 文件名/行號和 JAVA 行號之間的對應表(SMAP)

  Hellojsp:

   <HTML> <HEAD> <TITLE>Hello Example</TITLE> </HEAD> <BODY> <%@ include file=greetingjsp %> </BODY> </HTML>

  greetingjsp:

   Hello There!<P> Goodbye on <%= new javautilDate() %>

  JSP 編譯後產生的Hello_jspjava 如下:

  Hello_jspjava: package orgapachejsp; import javaxservlet*; import javaxservlethttp*; import javaxservletjsp*; public final class Hello_jsp extends orgapachejasperruntimeHttpJspBase implements orgapachejasperruntimeJspSourceDependent { private static javautilVector _jspx_dependants; static { _jspx_dependants = new javautilVector(); _jspx_dependantsadd(/greetingjsp); } public javautilList getDependants() { return _jspx_dependants; } public void _jspService(HttpServletRequest request HttpServletResponse response) throws javaioIOException ServletException { JspFactory _jspxFactory = null; PageContext pageContext = null; HttpSession session = null; ServletContext application = null; ServletConfig config = null; JspWriter out = null; Object page = this; JspWriter _jspx_out = null; try { _jspxFactory = JspFactorygetDefaultFactory(); responsesetContentType(text/html); pageContext = _jspxFactorygetPageContext(this request response null true true); application = pageContextgetServletContext(); config = pageContextgetServletConfig(); session = pageContextgetSession(); out = pageContextgetOut(); _jspx_out = out; outwrite(<HTML> \r\n); outwrite(<HEAD> \r\n); outwrite(<TITLE>Hello Example); outwrite(</TITLE> \r\n); outwrite(</HEAD> \r\n); outwrite(<BODY> \r\n); outwrite(Hello There!); outwrite(<P> \r\nGoodbye on ); outwrite(StringvalueOf( new javautilDate() )); outwrite( \r\n); outwrite( \r\n); outwrite(</BODY> \r\n); outwrite(</HTML> \r\n); } catch (Throwable t) { if (!(t instanceof javaxservletjspSkipPageException)){ out = _jspx_out; if (out != null && outgetBufferSize() != ) outclearBuffer(); if (pageContext != null) pageContexthandlePageException(t); } } finally { if (_jspxFactory != null) _jspxFactoryreleasePageContext ( pageContext); } } }

  Tomcat 又將這個 JAVA 代碼編譯為 Hello_jspclass他們位於 $Tomcat_install_path$\work\Standalone\localhost\_ 目錄下但是 JSP 文件名/行號和 JAVA 行號的對應表(以下簡稱SMAP) 在哪裡呢?答案是它保存在 Class 中如果用 UltraEdit 打開這個 Class 文件就可以找到 SourceDebugExtension 屬性這個屬性用來保存 SMAP

  JVM 規范定義了 ClassFile 中可以包含 SourceDebugExtension 屬性保存 SMAP

  SourceDebugExtension_attribute { u attribute_name_index; u attribute_length; u debug_extension[attribute_length]; }

  我用 javassist 做了一個測試(javassist可是一個好東東它可以動態改變Class的結構JBOSS 的 AOP就利用了javassist這裡我們只使用它讀取ClassFile的屬性)

  public static void main(String[] args) throws Exception{ String[]files = { E:\\Tomcat__\\work\\Catalina\\localhost\\_\\org\\apache\\jsp\\Hello_jspclass }; for(int k = ; k < fileslength; k++){ String file = files[k]; Systemoutprintln(Class : + file); ClassFile classFile = new ClassFile(new DataInputStream(new FileInputStream(file))); AttributeInfo attributeInfo = classFilegetAttribute(SourceDebugExtension); Systemoutprintln(attribute name : + attributeInfogetName() + ]\n\n); byte[]bytes = attributeInfoget(); String str = new String(bytes); Systemoutprintln(str); } }

  這段代碼顯示了SourceDebugExtension 屬性你可以看到SMAP 的內容編譯JSP後SMAP 就被寫入 Class 中 你也可以利用 javassist 修改 ClassFile 的屬性

  下面就是 Hello_jspclass 中保存的 SMAP 內容:

  SMAP E:\Tomcat__\work\Catalina\localhost\_\org\apache\jsp\Hello_jspjava JSP *S JSP *F + Hellojsp /Hellojsp + greetingjsp /greetingjsp *L : : : : : : #: : : #: : *E

  首先注明JAVA代碼的名稱Hello_jspjava然後是 stratum 名稱 JSP隨後是兩個JSP文件的名稱 Hellojspgreetingjsp兩個JSP文件共產生的Hello_jsp共行代碼最後也是最重要的內容就是源文件文件名/行號和目標文件行號的對應關系(*L 與 *E之間的部分)

  在規范定義了這樣的格式

  源文件行號 # 源文件代號重復次數 : 目標文件開始行號目標文件行號每次增加的數量

  (InputStartLine # LineFileID RepeatCount : OutputStartLine OutputLineIncrement)

  源文件行號(InputStartLine) 目標文件開始行號(OutputStartLine) 是必須的下面是對這個SMAP具體的說明

  : : : : : :(沒有源文件代號默認為Hellojsp) 開始行號 結束行號 Hellojsp: > Hello_jspjava: > > > > #: : :(#表示 greetingjsp 的第行) greetingjsp: > Hello_jspjava: > #: :(#表示 Hellojsp 的第行) Hellojsp: > Hello_jspjava: >

  開發一個JSP編輯器

  Eclipse 提供了 TextEditor作為文本編輯器的父類由於 Editor 的開發不是本文的重點不做具體論述我們可以利用 Eclipse 的 Plugin 項目向導生成一個簡單的 JSP 編輯器

  ()點擊 File 菜單New > Project > Plugin Project ;

  ()輸入項目名稱 JSP_DEBUG下一步;

  ()輸入 plugin ID comjspdebug

  Plugin Class name comjspdebugJSP_DebugPlugin

  ()選擇用模板創建

  使用 Plugin with editor輸入

  Java Package Name comjspeditors

  Editor Class Name JSPEditor

  File extension jsp

  一個 jsp editor 就產生了

  運行這個Plugin新建一個JAVA項目新建一個 Hellojsp 和 greetingjsp在 Navigator 視圖雙擊 jsp這個editor就打開了

  在JSP編輯器中設置斷點

  在編輯器中添加斷點的操作方式有兩種一種是在編輯器左側垂直標尺上雙擊另一種是在左側垂直標尺上點擊鼠標右鍵選擇菜單添加/刪除斷點

  在 Eclipse 的實現中添加斷點實際上就是為 IFile 添加一個marker 類型是IBreakpointBREAKPOINT_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 添加到文本編輯器左側標尺的鼠標右鍵菜單並且能夠處理左側標尺的鼠標雙擊事件在 pluginxml 中加入定義

  處理雙擊事件

  <extension point=orgeclipseuieditorActions> <editorContribution targetID=comjiaolyeditorsJSPEditor id=comjiaolydebugManageBreakpointRulerActionDelegate> <action label=添加/刪除斷點 class=comjiaolydebugManageBreakpointRulerActionDelegate actionID=RulerDoubleClick id=comjiaolydebugManageBreakpointRulerActionDelegate> </action> </editorContribution> </extension>

  添加右鍵菜單

  <extension point=orgeclipseuipopupMenus> <viewerContribution targetID=#TextRulerContext id=comjiaolydebugManageBreakpointRulerActionDelegate> <action label=添加/刪除斷點 class=comjiaolydebugManageBreakpointRulerActionDelegate menubarPath=addition id=comjiaolydebugManageBreakpointRulerActionDelegate> </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){ thisrulerInfo = ruler; thistextEditor = editor; BPmarkerType = IBreakpointBREAKPOINT_MARKER; addBP = 添加/刪除斷點; //$NONNLS$ setText(thisaddBP); } public void update() { thisallMarkers = thisfetchBPMarkerList(); } public void run(){ if(thisallMarkersisEmpty()) thisaddMarker(); else thisremoveMarkers(thisallMarkers); } }

  update 方法會在點擊時首先調用這時就可以收集當前選中行是否有marker了(調用fetchBPMarkerList方法)如果有就保存在 變量allMarkers 中由於ManageBreakpointRulerAction每一次都產生一個新的實例因此不會產生沖突

  下面是update的調用棧可以看出update方法是在鼠標點擊事件中被調用的

  ManageBreakpointRulerActionupdate() line: ManageBreakpointRulerActionDelegate(AbstractRulerActionDelegate)update() line: ManageBreakpointRulerActionDelegate(AbstractRulerActionDelegate) mouseDown(MouseEvent) line:

  updae被調用後會執行 run 方法就可以根據 allMarkersisEmpty() 確定要刪除還是添加 marker 了

  添加斷點的時候首先利用 IVerticalRulerInfo獲取鼠標點擊的行號根據行號從 Document 模型中取得該行的描述IRegion得到開始字符位置和結束字符位置創建一個 JSP 斷點

  protected void addMarker() { IEditorInput editorInput= thisgetTextEditor()getEditorInput(); IDocument document= thisgetDocument(); //the line number of the last mouse button activity int rulerLine= thisgetRulerInfo()getLineOfLastMouseButtonActivity(); try{ int lineNum = rulerLine + ; if(lineNum > ){ //Returns a description of the specified line IRegion iregion = documentgetLineInformation(lineNum ); int charStart = iregiongetOffset(); int charEnd = (charStart + iregiongetLength()) ; JSPDebugUtilitycreateJspLineBreakpoint(thisgetResource() lineNum charStart charEnd); } }catch(CoreException coreexception){ coreexceptionprintStackTrace(); } catch(BadLocationException badlocationexception){ badlocationexceptionprintStackTrace(); } }

  注冊 JSP 斷點為支持 JSR 規范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 的時候做了兩件事情

  () 創建斷點類型的 marker 並且設置了marker的屬性

  resourcecreateMarker(markerType);

  () 將斷點注冊到斷點管理器

  DebugPlugingetDefault()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(resourcecreateMarker(markerType)); // modify pattern String pattern = classNamePattern; if (pattern != null && patternlength() == ) { 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) { DebugPlugingetDefault()getBreakpointManager()addBreakpoint(this); } else { setRegistered(false); } }

  移除斷點的時候根據 marker 找到相應的 IBreakpoint從 BreakpointManager 中移除 BreakpointManager 會自動刪除 marker通知 JVM Target

  breakpointManager = DebugPlugingetDefault()getBreakpointManager(); IBreakpoint breakpoint = breakpointManagergetBreakpoint(IMarker); breakpointManagerremoveBreakpoint(breakpoint true);

  JSPBreakpoint 重載了父類的addToTarget(JDIDebugTarget target) 方法重載這個方法的目的是根據不同的應用服務器設置不同的 referenceTypeName和sourcePath我們知道每種應用服務器編譯 JSP 產生Java Class 名稱的規則都不相同例如Tomcat編譯Hellojsp 產生的Java 類名為 orgapachejsp Hello_jsp而WebSphere 卻是 comibm_jsp_Hello只有確定服務器類型才能知道referenceTypeName 和souecePath應該是什麼目前通過啟動 JVM 時target 名稱來判斷應用服務器類型 String targetString = targetgetLaunch()getLaunchConfiguration()getName(); 如果targetString 包含 Tomcat 就認為是 Tomcat

  產生 referenceTypeName 後首先創建一個 ClassPrepareRequest 通知然後從vm中取出所有的classes如果是當前的 Class再創建一個添加斷點通知之所以這樣做是因為有可能這個 Class 還沒有被 JVM 加載直接通知 JVM 沒有任何意義在 Class 被加載的時候JVM 會通知 Eclipse這個時候才產生添加斷點通知需要指出的是本文示例代碼獲取 referenceTypeName 的方法不是很完善

  () 僅僅實現了Tomcat 讀者有興趣可以實現更多的Web容器例如 JBoss 以上WebSphere

  () 一些特殊情況沒有處理例如 路徑名為package的jsp路徑名或文件名帶有數字的jsp

  public void addToTarget(JDIDebugTarget target) throws CoreException { IMarker marker = thisgetMarker(); IResource resource = markergetResource(); String targetString = targetgetLaunch()getLaunchConfiguration()getName(); IJSPNameUtil util = JSPDebugUtilitygetJSPNameUtil(targetString); // prenotification fireAdding(target); String referenceTypeName; try { referenceTypeName = getPattern(); //如果沒有設置 Pattern 根據 Server 的類型 產生新的 Pattern if(referenceTypeName == null || equals(referenceTypeNametrim()) || *equals(referenceTypeNametrim())){ referenceTypeName = utilreferenceTypeName(resource); } } catch (CoreException e) { JDIDebugPluginlog(e); return; } thisensureMarker()setAttribute(TYPE_NAME referenceTypeName); String sourcePath = utilsourcePath(resource); thisensureMarker()setAttribute(JSPBreakpointSOURCE_PATH sourcePath); String classPrepareTypeName= referenceTypeName; //如果這時 class 還沒有被加載 注冊一個 ClassPrepareRequest 請求 // //當 class 加載的時候 首先會觸發 JavaBreakpoint 的 handleClassPrepareEvent 方法 //調用 createRequest(target eventreferenceType()) > newRequest() > // createLineBreakpointRequest() 創建 enable或disable 斷點的請求 // // 設置 enable/disable 動作在 configureRequest() > updateEnabledState(request) 方法中 // 根據 getMarker()getAttribute(ENABLED false) 確定斷點是否有效 registerRequest(targetcreateClassPrepareRequest(classPrepareTypeName) target); // create breakpoint requests for each class currently loaded VirtualMachine vm = targetgetVM(); if (vm == null) { targetrequestFailed(Unable_to_add_breakpoint__VM_disconnected_) null); } List classes = null; try { classes= vmallClasses(); } catch (RuntimeException e) { targettargetRequestFailed(JavaPatternBreakpoint) e); } if (classes != null) { Iterator iter = erator(); while (iterhasNext()) { ReferenceType type= (ReferenceType)iternext(); if (installableReferenceType(type target)) { createRequest(target type); } } } }

  調試JSP

  現在我們可以調試 JSP 了

  ()運行 JSP_DEBUG plugin

  首先在 run > run 中添加一個 Runtime Workbench點擊 run 按鈕Eclipse 的Plugin開發環境會啟動一個新的Eclipse這個新啟動的 Eclipse 中我們創建的 JSP_DEBUG plugin 就可以使用了新建 一個 JAVA 項目 Test (注意一定要是JAVA項目)新建一個 Hellojsp 和 greetingjsp打開Hellojsp在編輯器左側標尺雙擊就出現了一個斷點

  ()以 Debug 模式啟動Tomcat

  windows 開始 > 運行鍵入 cmd啟動一個命令行窗口

  cd E:\Tomcat__\bin

  (我的 Tomcat 安裝在 E:\Tomcat__ 目錄JDK 安裝在 D:\jsdk)

  D:\jsdk\bin\java Xdebug Xnoagent Xrunjdwp:transport=dt_socketaddress=server=y suspend=n Djavaendorseddirs=\common\endorsed classpath D:\jsdk\lib\toolsjar;\bin\bootstrapjar Dcatalinabase= Dcatalinahome= Djavaiotmpdir=\temp orgapachecatalinastartupBootstrap start

  Xdebug Xnoagent Xrunjdwp:transport=dt_socketaddress=server=ysuspend=n 表示以調試方式啟動端口號是 classpath中要加入 D:\jsdk\lib\toolsjar因為我是 Tomcat如果是就不需要了

  () 測試Hellojsp

  將 Hellojsp 和 greetingjsp 拷貝到 E:\Tomcat__\webapps\ROOT 目錄從浏覽器訪問 Hellojsp 成功的話就可以繼續下面的工作了如果失敗檢查你的Tomcat設置

  ()啟動遠程調試

  在 Eclipse 中啟動遠程調試將 Eclipse 作為一個 Debug 客戶端連接到 Tomcat 在 Java 透視圖中點擊 Run > Debug 添加一個 Remote Java Application名稱是 Start Tomcat Server(不能錯因為我們要根據這個名稱判斷當前的 Web Server 類型)

  project是創建的 Test 項目

  Port 為 和啟動 Tomcat 時設置的一樣

  點擊 Debug 按鈕就可以連接到 Tomcat 上了切換到 Debug 透視圖在Debug 視圖中能夠看到所有 Tomcat 中線程的列表

  ()調試Hellojsp

  為 Hellojsp 添加斷點然後從浏覽器訪問Hellojsp就可以在斷點處掛起了你可以使用單步執行也可以在Variables視圖查看jsp中的變量信息

  由於 Eclipse 自身的實現現在的 JSP Editor 有一個問題單步執行到 include jsp 行後會從Hellojsp的行再次執行這是因為 Eclipse JDT Debug視圖緩存了 StackFrame 中已經打開的EditorStackFrame不改變時不會再重新計算當前調試的是否是其他Resource本來應該打開 greetingjsp的現在卻從 Hellojsp 的第 行開始執行了

  結束語

  很多集成開發環境都支持 JSP 的調試在 Eclipse 中也有 MyEclipse 這樣的插件完成類似的功能但是在 JSR 規范產生前每種應用服務器對 JSP Debug 的實現是不一樣的例如 WebSphere 就是在 JSP 編譯產生的 JAVA 代碼中加入了兩個數組表示源文件和行號的對應信息Tomcat 率先實現了 JSR 規范WebSphere 現在也采取這種模式 有興趣的話可以查看 WebSphere 編譯的 Class和 Tomcat 不一樣SMAP 文件會和java代碼同時產生

  但是啟動server前需要設置 JVM 參數 wasdebugmode = true

  同時在 ibmwebextxmi 中設置

  <jspAttributes xmi:id=JSPAttribute_ name=keepgenerated value=true/> <jspAttributes xmi:id=JSPAttribute_ name=createDebugClassfiles value=true/> <jspAttributes xmi:id=JSPAttribute_ name=debugEnabled value=true/>

  利用本文的基本原理我們也可以開發其他基於 JAVA 腳本語言的編輯器(例如 Groovy)為這個編譯器加入 Debug 的功能


From:http://tw.wingwit.com/Article/program/Java/ky/201311/28908.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.