




已阅读5页,还剩23页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【摘 要】本文通过开发一个JSP 编辑器插件的示例,介绍了 Eclipse 中设置 JSP 断点的方法,以及如何远程调试 JSP。作为基础知识,本文的前两部分描述了 JAVA Debug 和 JSR-45 的基本原理。本文通过开发一个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 | |-|参见:/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 2 3 Hello Example 4 5 6 7 8 greeting.jsp: 1 Hello There! 2 Goodbye on 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( rn); 46 out.write( rn); 47 out.write(Hello Example); 48 out.write( rn); 49 out.write( rn); 50 out.write( rn); 51 out.write(Hello There!); 52 out.write( rnGoodbye on ); 53 out.write(String.valueOf( new java.util.Date() ); 54 out.write( rn); 55 out.write( rn); 56 out.write( rn); 57 out.write( rn); 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$workStandalonelocalhost_ 目录下。但是 JSP 文件名/行号和 JAVA 行号的对应表(以下简称SMAP) 在哪里呢?答案是,它保存在 Class 中。如果用 UltraEdit 打开这个 Class 文件,就可以找到 SourceDebugExtension 属性,这个属性用来保存 SMAP。JVM 规范定义了 ClassFile 中可以包含 SourceDebugExtension 属性,保存 SMAP:SourceDebugExtension_attribute u2 attribute_name_index; u4 attribute_length; u1 debug_extensionattribute_length; 我用 javassist 做了一个测试(javassist可是一个好东东,它可以动态改变Class的结构,JBOSS 的 AOP就利用了javassist,这里我们只使用它读取ClassFile的属性)public static void main(String args) throws Exception Stringfiles = E:Tomcat5_0_5workCatalinalocalhost_orgapachejspHello_jsp.class, ; for(int k = 0; k 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.editorsEditor Class Name :JSPEditorFile 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 中加入定义。处理双击事件: 添加右键菜单: 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: 139updae被调用后,会执行 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,路径名或文件名带有数字的jsppublic 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.targe
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
评论
0/150
提交评论