求教听力如果有说中国国际postcodee的话

当前位置: >>
struts2源代码分析(个人觉得非常经典)
本章讲述 Struts2 的工作原理。 读者如果曾经学习过 Struts1.x 或者有过 Struts1.x 的开发经验,那么千万不要想当然地以为这一章 可以跳过。实际上 Struts1.x 与 Struts2 并无我们想象的血缘关系。虽然 Struts2 的开发小组极力保留 S truts1.x 的习惯,但因为 Struts2 的核心设计完全改变,从思想到设计到工作流程,都有了
很大的不同。 Struts2 是 Struts 社区和 WebWork 社区的共同成果,我们甚至可以说,Struts2 是 WebWork 的 升级版,他采用的正是 WebWork 的核心,所以,Struts2 并不是一个不成熟的产品,相反,构建在 We bWork 基础之上的 Struts2 是一个运行稳定、性能优异、设计成熟的 WEB 框架。 本章主要对 Struts 的源代码进行分析,因为 Struts2 与 WebWork 的关系如此密不可分,因此,读 者需要下载 xwork 的源代码, 访问 /xwork/download.action 即可 自行下载。 下载的 Struts2 源代码文件是一个名叫 struts-2.1.0-src.zip 的压缩包,里面的目录和文件非常多, 读者可以定位到 struts-2.1.0-src&struts-2.0.10&src&core&src&main&java 目录下查看 Struts2 的源 文件,如图 14 所示。(图 14) 主要的包和类 Struts2 框架的正常运行,除了占核心地位的 xwork 的支持以外,Struts2 本身也提供了许多类,这 些类被分门别类组织到不同的包中。从源代码中发现,基本上每一个 Struts2 类都访问了 WebWork 提供 的功能,从而也可以看出 Struts2 与 WebWork 千丝万缕的联系。但无论如何,Struts2 的核心功能比如 将请求委托给哪个 Action 处理都是由 xwork 完成的, Struts2 只是在 WebWork 的基础上做了适当的简 化、加强和封装,并少量保留 Struts1.x 中的习惯。 以下是对各包的简要说明: 包名 说明 该包封装视图组件,Struts2 在视图组件上有了很大加强,不仅增加 了组件的属性个数,更新增了几个非常有用的组件,如 updownselect、doubleselect、datetimepicker、 token、tree 等。 org.apache.struts2. components 另外,Struts2 可视化视图组件开始支持主题(theme),缺省情 况下,使用自带的缺省主题,如果要自定义页面效果,需要将组件的 theme 属性设置为 simple。 该包定义与配置相关的接口和类。 实际上, 工程中的 xml 和 properties org.apache.struts2. config 文件的读取和解析都是由 WebWork 完成的, Struts 只做了少量的工 作。 org.apache.struts2.dispatcher Struts2 的核心包,最重要的类都放在该包中。 该包只定义了 3 个类,他们是 StrutsActionProxy、 org.apache.struts2.impl StrutsActionProxyFactory、StrutsObjectFactory,这三个类都是 对 xwork 的扩展。 org.apache.struts2.interceptor 定义内置的截拦器。 org.apache.struts2.util 实用包。org.apache.struts2.validators 只定义了一个类:DWRValidator。 org.apache.struts2.views 下表是对一些重要类的说明: 类名 说明 该类有两个作用: 提供 freemarker、jsp、velocity 等不同类型的页面呈现。org.apache.struts2.dispatcher. Dispatcher1、初始化 2、调用指定的 Action 的 execute() 方法。 这是一个过滤器。文档中已明确说明,如果 没有经验,配置时请将 url-pattern 的值设 成/*。该类有四个作用: org.apache.struts2.dispatcher. FilterDispatcher 1、执行 Action 2、清理 ActionContext,避免内存泄 漏 3、处理静态内容(Serving static content) 4、为请求启动 xwork’s 的截拦器链。 com.opensymphony.xwork2. ActionProxy com.opensymphony.xwork2. ctionProxyFactory com.opensymphony.xwork2.ActionInvocation Action 的代理接口。 生产 ActionProxy 的工厂。 负责调用 Action 和截拦器。 com.opensymphony.xwork2.config.providers. XmlConfigurationProvider Struts2 的工作机制 3.1Struts2 体系结构图 Strut2 的体系结构如图 15 所示:负责 Struts2 的配置文件的解析。(图 15) 3.2Struts2 的工作机制 从图 15 可以看出,一个请求在 Struts2 框架中的处理大概分为以下几个步骤: 1、客户端初始化一个指向 Servlet 容器(例如 Tomcat)的请求; 2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做 ActionContextCleanUp 的 可选过滤器,这个过滤器对于 Struts2 和其他框架的集成很有帮助,例如:SiteMesh Plugin); 3、接着 FilterDispatcher 被调用,FilterDispatcher 询问 ActionMapper 来决定这个请求是否需要 调用某个 Action; 4、如果 ActionMapper 决定需要调用某个 Action,FilterDispatcher 把请求的处理交给 ActionPr oxy; 5、ActionProxy 通过 Configuration Manager 询问框架的配置文件,找到需要调用的 Action 类; 6、ActionProxy 创建一个 ActionInvocation 的实例。 7、ActionInvocation 实例使用命名模式来调用,在调用 Action 的过程前后,涉及到相关拦截器(I ntercepter)的调用。 8、一旦 Action 执行完毕,ActionInvocation 负责根据 struts.xml 中的配置找到对应的返回结果。 返回结果通常是(但不总是,也可能是另外的一个 Action 链)一个需要被表示的 JSP 或者 FreeMarker 的模版。在表示的过程中可以使用 Struts2 框架中继承的标签。在这个过程中需要涉及到 ActionMappe r。 注:以上步骤参考至网上,具体网址已忘记。在此表示感谢! 3.3Struts2 源代码分析 和 Struts1.x 不同,Struts2 的启动是通过 FilterDispatcher 过滤器实现的。下面是该过滤器在 we b.xml 文件中的配置: 代码清单 6:web.xml(截取)&filter& &filter-name&struts2&/filter-name& &filter-class& org.apache.struts2.dispatcher.FilterDispatcher &/filter-class& &/filter& &filter-mapping& &filter-name&struts2&/filter-name& &url-pattern&/*&/url-pattern& &/filter-mapping&Struts2 建议,在对 Struts2 的配置尚不熟悉的情况下,将 url-pattern 配置为/*,这样该过滤器将 截拦所有请求。 实际上, FilterDispatcher 除了实现 Filter 接口以外, 还实现了 StrutsStatics 接口, 继承代码如下: 代码清单 7:FilterDispatcher 结构publicclass FilterDispatcher implements StrutsStatics, Filter { }StrutsStatics 并没有定义业务方法,只定义了若干个常量。Struts2 对常用的接口进行了重新封装, 比如 HttpServletRequest、HttpServletResponse、HttpServletContext 等。 cs 的定义: 代码清单 8:StrutsStatics.java 以下是 StrutsStati publicinterface StrutsStatics { /** *ConstantfortheHTTPrequestobject. */ publicstaticfinal String HTTP_REQUEST = &com.opensymphony.xwork2. dispatcher.HttpServletRequest&; /** *ConstantfortheHTTPresponseobject. */ publicstaticfinal String HTTP_RESPONSE = &com.opensymphony.xwork2. dispatcher.HttpServletResponse&; /** *ConstantforanHTTPrequest dispatcher}. */ publicstaticfinal String SERVLET_DISPATCHER = &com.opensymphony.x work2.dispatcher.ServletDispatcher&; /** *Constantfortheservlet context}object. */ publicstaticfinal String SERVLET_CONTEXT = &com.opensymphony.xwor k2.dispatcher.ServletContext&; /** *ConstantfortheJSPpage context}. */ publicstaticfinal String PAGE_CONTEXT = &com.opensymphony.xwork2.disp atcher.PageContext&; /**ConstantforthePortletContextobject*/ publicstaticfinal String STRUTS_PORTLET_CONTEXT = &struts.portlet. context&; }容器启动后,FilterDispatcher 被实例化,调用 init(FilterConfig filterConfig)方法。该方法创建 D ispatcher 类的对象, 并且将 FilterDispatcher 配置的初始化参数传到对象中 (详情请参考代码清单 10) , 并负责 Action 的执行。然后得到参数 packages,值得注意的是,还有另外三个固定的包和该参数进行拼 接, 分别是 org.apache.struts2.static、 template、 org.apache.struts2.interceptor.debugging, 和 中间用空格隔开,经过解析将包名变成路径后存储到一个名叫 pathPrefixes 的数组中,这些目录中的文 件会被自动搜寻。 代码清单 9:FilterDispatcher.init()方法 publicvoid init(FilterConfig filterConfig) throws ServletExceptio n { this.filterConfig = filterC dispatcher = createDispatcher(filterConfig); dispatcher.init(); String param = filterConfig.getInitParameter(&packages&); String packages = &org.apache.struts2.static template org.apa che.struts2.interceptor.debugging&; if (param != null) { packages = param + & & + } this.pathPrefixes = parse(packages); }代码清单 10:FilterDispatcher.createDispatcher()方法protected Dispatcher createDispatcher(FilterConfig filterConfig) { Map&String,String& params = new HashMap&String,String&(); for (Enumeration e = filterConfig.getInitParameterNames(); e. hasMoreElements(); ) { String name = (String) e.nextElement(); String value = filterConfig.getInitParameter(name); params.put(name, value); } returnnew Dispatcher(filterConfig.getServletContext(), param s); }当用户向 Struts2 发送请求时,FilterDispatcher 的 doFilter()方法自动调用,这个方法非常关键。 首先,Struts2 对请求对象进行重新包装,此次包装根据请求内容的类型不同,返回不同的对象,如果为 multipart/form-data 类型,则返回 MultiPartRequestWrapper 类型的对象,该对象服务于文件上传, 否则返回 StrutsRequestWrapper 类型的对象,MultiPartRequestWrapper 是 StrutsRequestWrap per 的子类,而这两个类都是 HttpServletRequest 接口的实现。包装请求对象如代码清单 11 所示: 代码清单 11:FilterDispatcher.prepareDispatcherAndWrapRequest()方法protectedHttpServletRequest prepareDispatcherAndWrapRequest( HttpServletRequest request, HttpServletResponse response) throws ServletException { Dispatcher du = Dispatcher.getInstance(); if (du == null) { Dispatcher.setInstance(dispatcher); dispatcher.prepare(request, response); } else { dispatcher = } try { request = dispatcher.wrapRequest(request, getServletConte xt()); } catch (IOException e) { String message = &Could not wrap servlet request with Mul tipartRequestWrapper!&; LOG.error(message, e); thrownew ServletException(message, e); } }request 对象重新包装后,通过 ActionMapper 的 getMapping()方法得到请求的 Action,Action 的配置信息存储在 ActionMapping 对象中,该语句如下:mapping = actionMapper.getMapping(r equest, dispatcher.getConfigurationManager());。下面是 ActionMapper 接口的实现类 DefaultA ctionMapper 的 getMapping()方法的源代码: 代码清单 12:DefaultActionMapper.getMapping()方法public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); String uri = getUri(request);//得到请求路径的 URI,如:testAtc ion.action 或 testAction!method uri = dropExtension(uri);//删除扩展名,默认扩展名为 action,在 代码中的定义是 List extensions = new ArrayList() {{ add(&action&);}}; if (uri == null) { } parseNameAndNamespace(uri, mapping, configManager);//从 uri 变 量中解析出 Action 的 name 和 namespace handleSpecialParameters(request, mapping);//将请求参数中的重 复项去掉 //如果 Action 的 name 没有解析出来,直接返回 if (mapping.getName() == null) { } //下面处理形如 testAction!method 格式的请求路径 if (allowDynamicMethodCalls) { // handle &name!method& convention. String name = mapping.getName(); int exclamation = name.lastIndexOf(&!&);//!是 Action 名称 和方法名的分隔符 if (exclamation != -1) { mapping.setName(name.substring(0, exclamation));//提 取左边为 name mapping.setMethod(name.substring(exclamation + 1));// 提取右边的 method } } }该代码的活动图如下: (图 16) 从代码中看出,getMapping()方法返回 ActionMapping 类型的对象,该对象包含三个参数:Actio n 的 name、namespace 和要调用的方法 method。 如果 getMapping()方法返回 ActionMapping 对象为 null,则 FilterDispatcher 认为用户请求不是 Action,自然另当别论,FilterDispatcher 会做一件非常有意思的事:如果请求以/struts 开头,会自动 查找在 web.xml 文件中配置的 packages 初始化参数,就像下面这样(注意粗斜体部分): 代码清单 13:web.xml(部分)&filter& &filter-name&struts2&/filter-name& &filter-class& org.apache.struts2.dispatcher.FilterDispatcher &/filter-class& &init-param& &param-name&packages&/param-name& &param-value&com.lizanhong.action&/param-value& &/init-param& &/filter&FilterDispatcher 会将 com.lizanhong.action 包下的文件当作静态资源处理,即直接在页面上显示 文件内容,不过会忽略扩展名为 class 的文件。比如在 com.lizanhong.action 包下有一个 aaa.txt 的文 本文件,其内容为“中华人民共和国”,访问 http://localhost:8081/Struts2Demo/struts/aaa.txt 时 会有如图 17 的输出:(图 17) 查找静态资源的源代码如清单 14: 代码清单 14:FilterDispatcher.findStaticResource()方法protectedvoid findStaticResource(String name, HttpServletRequest request, HttpServletResponse response) throws IOException { if (!name.endsWith(&.class&)) {//忽略 class 文件 //遍历 packages 参数 for (String pathPrefix : pathPrefixes) { InputStream is = findInputStream(name, pathPrefix);// 读取请求文件流 if (is != null) { ……(省略部分代码) // set the content-type header String contentType = getContentType(name);//读取 内容类型 if (contentType != null) { response.setContentType(contentType);//重新设 置内容类型 } ……(省略部分代码) try { //将读取到的文件流以每次复制 4096 个字节的方式循 环输出 copy(is, response.getOutputStream()); } finally { is.close(); } } } } }如果用户请求的资源不是以/struts 开头――可能是.jsp 文件, 也可能是.html 文件, 则通过过滤器链 继续往下传送,直到到达请求的资源为止。 如果 getMapping()方法返回有效的 ActionMapping 对象,则被认为正在请求某个 Action,将调用 Dispatcher.serviceAction(request, response, servletContext, mapping)方法,该方法是处理 Ac tion 的关键所在。上述过程的源代码如清单 15 所示。 代码清单 15:FilterDispatcher.doFilter()方法publicvoid doFilter(ServletRequest req, ServletResponse res, Filt erChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) HttpServletResponse response = (HttpServletResponse) ServletContext servletContext = getServletContext(); String timerKey = &FilterDispatcher_doFilter: &; try { UtilTimerStack.push(timerKey); request = prepareDispatcherAndWrapRequest(request, respon se);//重新包装 request ActionM try { mapping = actionMapper.getMapping(request, dispatcher. getConfigurationManager());//得到存储 Action 信息的 ActionMapping 对象 } catch (Exception ex) { ……(省略部分代码) } if (mapping == null) {//如果 mapping 为 null,则认为不是请 求 Action 资源 String resourcePath = RequestUtils.getServletPath(re quest); if (&&.equals(resourcePath) && null != request.getPat hInfo()) { resourcePath = request.getPathInfo(); } //如果请求的资源以/struts 开头,则当作静态资源处理 if (serveStatic && resourcePath.startsWith(&/struts&)) { String name = resourcePath.substring(&/struts&.le ngth()); findStaticResource(name, request, response); } else { //否则,过滤器链继续往下传递 chain.doFilter(request, response); } // The framework did } //如果请求的资源是 Action,则调用 serviceAction 方法。 dispatcher.serviceAction(request, response, servletContex t, mapping); } finally { try { ActionContextCleanUp.cleanUp(req); } finally { UtilTimerStack.pop(timerKey); } } }这段代码的活动图如图 18 所示:(图 18) 在 Dispatcher.serviceAction()方法中,先加载 Struts2 的配置文件,如果没有人为配置,则默认 加载 struts-default.xml、struts-plugin.xml 和 struts.xml,并且将配置信息保存在形如 com.opens ymphony.xwork2.config.entities.XxxxConfig 的类中。 类 com.opensymphony.xwork2.config.providers.XmlConfigurationProvider 负责配置文件的 读取和解析, addAction()方法负责读取&action&标签,并将数据保存在 ActionConfig 中;addResul tTypes()方法负责将&result-type&标签转化为 ResultTypeConfig 对象;loadInterceptors()方法负 责将&interceptor&标签转化为 InterceptorConfig 对象;loadInterceptorStack()方法负责将&inter ceptor-ref&标签转化为 InterceptorStackConfig 对象;loadInterceptorStacks()方法负责将&inter ceptor-stack&标签转化成 InterceptorStackConfig 对象。 而上面的方法最终会被 addPackage()方法 调用,将所读取到的数据汇集到 PackageConfig 对象中,细节请参考代码清单 16。 代码清单 16:XmlConfigurationProvider.addPackage()方法protected PackageConfig addPackage(Element packageElement) throws ConfigurationException { PackageConfig newPackage = buildPackageContext(packageElemen t); if (newPackage.isNeedsRefresh()) { return newP } if (LOG.isDebugEnabled()) { LOG.debug(&Loaded & + newPackage); } // add result types (and default result) to this package addResultTypes(newPackage, packageElement); // load the interceptors and interceptor stacks for this pack age loadInterceptors(newPackage, packageElement); // load the default interceptor reference for this package loadDefaultInterceptorRef(newPackage, packageElement); // load the default class ref for this package loadDefaultClassRef(newPackage, packageElement); // load the global result list for this package loadGlobalResults(newPackage, packageElement); // load the global exception handler list for this package loadGlobalExceptionMappings(newPackage, packageElement); // get actions NodeList actionList = packageElement.getElementsByTagName(&ac tion&); for (int i = 0; i & actionList.getLength(); i++) { Element actionElement = (Element) actionList.item(i); addAction(actionElement, newPackage); } // load the default action reference for this package loadDefaultActionRef(newPackage, packageElement); configuration.addPackageConfig(newPackage.getName(), newPacka ge); return newP } 活动图如图 19 所示: (图 19) 配置信息加载完成后,创建一个 Action 的代理对象――ActionProxy 引用,实际上对 Action 的调用 正是通过 ActionProxy 实现的,而 ActionProxy 又由 ActionProxyFactory 创建,ActionProxyFactor y 是创建 ActionProxy 的工厂。 注:ActionProxy 和 ActionProxyFactory 都是接口,他们的默认实现类分别是 DefaultActionPro xy 和 DefaultActionProxyFactory,位于 com.opensymphony.xwork2 包下。 在这里,我们绝对有必要介绍一下 com.opensymphony.xwork2.DefaultActionInvocation 类, 该类是对 ActionInvocation 接口的默认实现,负责 Action 和截拦器的执行。 在 DefaultActionInvocation 类中,定义了 invoke()方法,该方法实现了截拦器的递归调用和执行 Action 的 execute()方法。其中,递归调用截拦器的代码如清单 17 所示: 代码清单 17:调用截拦器,DefaultActionInvocation.invoke()方法的部分代码if (interceptors.hasNext()) { //从截拦器集合中取出当前的截拦器 final InterceptorMapping interceptor = (InterceptorMap ping) interceptors.next(); UtilTimerStack.profile(&interceptor: &+interceptor.get Name(), new UtilTimerStack.ProfilingBlock&String&() { public String doProfiling() throws Exception { //执行截拦器(Interceptor)接口中定义的 in tercept 方法 resultCode = interceptor.getInterceptor ().intercept(DefaultActionInvocation.this); } }); }从代码中似乎看不到截拦器的递归调用,其实是否递归完全取决于程序员对程序的控制,先来看一下 Interceptor 接口的定义: 代码清单 18:Interceptor.javapublicinterface Interceptor extends Serializable { void destroy(); void init(); String intercept(ActionInvocation invocation) throws E }所有的截拦器必须实现 intercept 方法,而该方法的参数恰恰又是 ActionInvocation,所以,如果在 intercept 方法中调用 invocation.invoke(),代码清单 17 会再次执行,从 Action 的 Intercepor 列表 中找到下一个截拦器,依此递归。下面是一个自定义截拦器示例: 代码清单 19:CustomIntercepter.javapublicclass CustomIntercepter extends AbstractInterceptor { @Override public String intercept(ActionInvocation actionInvocation) throws Exception { actionInvocation.invoke(); return&李赞红&; } }截拦器的调用活动图如图 20 所示:(图 20) 如果截拦器全部执行完毕,则调用 invokeActionOnly()方法执行 Action,invokeActionOnly()方 法基本没做什么工作,只调用了 invokeAction()方法。 为了执行 Action, 必须先创建该对象, 该工作在 DefaultActionInvocation 的构造方法中调用 init() 方法早早完成。调用过程是:DefaultActionInvocation()-&init()-&createAction()。创建 Action 的 代码如下: 代码清单 20:DefaultActionInvocation.createAction()方法 protectedvoid createAction(Map contextMap) { try { action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap); } catch (InstantiationException e) { ……异常代码省略 } }Action 创建好后,轮到 invokeAction()大显身手了,该方法比较长,但关键语句实在很少,用心点 看不会很难。 代码清单 20:DefaultActionInvocation.invokeAction()方法protected String invokeAction(Object action, ActionConfig actionConfi g) throws Exception { //获取 Action 中定义的 execute()方法名称,实际上该方法是可以随便定义 的 String methodName = proxy.getMethod(); String timerKey = &invokeAction: &+proxy.getActionName(); try { UtilTimerStack.push(timerKey); M try { //将方法名转化成 Method 对象 method = getAction().getClass().getMethod(methodName, new Class[0]); } catch (NoSuchMethodException e) { // hmm -- OK, try doXxx instead try { //如果 Method 出错,则尝试在方法名前加 do,再转成 Metho d 对象 String altMethodName = &do& + methodName.substrin g(0, 1).toUpperCase() + methodName.substring(1); method = getAction().getClass().getMethod(altMeth odName, new Class[0]); } catch (NoSuchMethodException e1) { // throw the
} } //执行方法 Object methodResult = method.invoke(action, new Object [0]); //处理跳转 if (methodResult instanceof Result) { this.result = (Result) methodR } else { return (String) methodR } } catch (NoSuchMethodException e) { ……省略异常代码 } finally { UtilTimerStack.pop(timerKey); } }刚才使用了一段插述,我们继续回到 ActionProxy 类。 我们说 Action 的调用是通过 ActionProxy 实现的,其实就是调用了 ActionProxy.execute()方法, 而该方法又调用了 ActionInvocation.invoke()方法。归根到底,最后调用的是 DefaultActionInvocati on.invokeAction()方法。 以下是调用关系图:其中: ? ? ActionProxy:管理 Action 的生命周期,它是设置和执行 Action 的起始点。 ActionInvocation:在 ActionProxy 层之下,它表示了 Action 的执行状态。它持有 Action 实例和所有的 Interceptor 以下是 serviceAction()方法的定义: 代码清单 21:Dispatcher.serviceAction()方法publicvoid serviceAction(HttpServletRequest request, HttpServ letResponse response, ServletContext context, ActionMapping mapping) throws ServletEx ception { Map&String, Object& extraContext = createContextMap(request, response, mapping, context); // If there was a previous value stack, then create a new cop y and pass it in to be used by the new Action ValueStack stack = (ValueStack) request.getAttribute(ServletA ctionContext.STRUTS_VALUESTACK_KEY); if (stack != null) { extraContext.put(ActionContext.VALUE_STACK, ValueStackFac tory.getFactory().createValueStack(stack)); } String timerKey = &Handling request from Dispatcher&; try { UtilTimerStack.push(timerKey); String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); Configuration config = configurationManager.getConfigurat ion(); ActionProxy proxy = config.getContainer().getInstance(Act ionProxyFactory.class).createActionProxy( namespace, name, extraContext, true, false); proxy.setMethod(method); request.setAttribute(ServletActionContext.STRUTS_VALUESTA CK_KEY, proxy.getInvocation().getStack()); // if the ActionMapping says to go straight to a result, do it! if (mapping.getResult() != null) { Result result = mapping.getResult(); result.execute(proxy.getInvocation()); } else { proxy.execute(); } // If there was a previous value stack then set it back o nto the request if (stack != null) { request.setAttribute(ServletActionContext.STRUTS_VALU ESTACK_KEY, stack); } } catch (ConfigurationException e) { LOG.error(&Could not find action or result&, e); sendError(request, response, context, HttpServletResponse. SC_NOT_FOUND, e); } catch (Exception e) { thrownew ServletException(e); } finally { UtilTimerStack.pop(timerKey); } }最后,通过 Result 完成页面的跳转。 3.4 本小节总结总体来讲,Struts2 的工作机制比 Struts1.x 要复杂很多,但我们不得不佩服 Struts 和 WebWork 开发小组的功底,代码如此优雅,甚至能够感受看到两个开发小组心神相通的默契。两个字:佩服。 以下是 Struts2 运行时调用方法的顺序图:
(图 21) 四、 总结阅读源代码是一件非常辛苦的事,对读者本身的要求也很高,一方面要有扎实的功底,另一方面要有 超强的耐力和恒心。本章目的就是希望能帮助读者理清一条思路,在必要的地方作出简单的解释,达到事 半功倍的效果。 当然,笔者不可能为读者解释所有类,这也不是我的初衷。Struts2+xwork 一共有 700 余类,除了 为读者做到现在的这些,已无法再做更多的事情。读者可以到 Struts 官方网站下载帮助文档,慢慢阅读和 理解,相信会受益颇丰。 本章并不适合 java 语言初学者或者对 java 博大精深的思想理解不深的读者阅读,这其中涉及到太多 的术语和类的使用,特别不要去钻牛角尖,容易使自信心受损。基本搞清楚 Struts2 的使用之后,再回过 头来阅读本章,对一些知识点和思想也许会有更深的体会。 如果读者的 java 功底比较浑厚,而且对 Struts2 充满兴趣,但又没太多时间研究,不妨仔细阅读本 章,再对照 Struts 的源代码,希望对您有所帮助。

我要回帖

更多关于 uk postcode 的文章

 

随机推荐