jsp

位置:IT落伍者 >> jsp >> 浏览文章

开发一个调试 JSP 的 Eclipse 插件


发布日期:2018年04月04日
 
开发一个调试 JSP 的 Eclipse 插件

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\endorsedclasspath 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 的功能

               

上一篇:JSP、Struts避免Form重复提交的几种方案

下一篇:轻松应对JSP连接MySQL数据库问题[2]