本文通过分析祥细的分析strut2.31的源代码来加深对struts2的认识。使我们在使用struts2框架的时候更有把握更得心应手。(独到的分析,非常祥细,原创)
struts2的入口就是一个StrutsPrepareAndExecuteFilter 过滤器,网上的也有很多分析struts2的文章了,但在filter入口这里却一笔略过,
其实我觉得先让大家都深入了解filter的原理这后,再一条线索往下,也许这样更能使你的思路更清淅,
所以我借助tomcat中filter的源代码从源头上开始分析,正所谓技术是日新月异的,也许哪天他的实现方式改变了,但是原理这东西是沉甸下来的,原理就是原理。
请耐心地看,也许我有哪些地方分析得不是很好,但我期望指正,分享学习也是一种快乐,让我们一起前进。
Servlet2.3中引入Filter其实它就是使用了职责链模式(Chain Of Responsibility)的抽象设计了。责链设计模式的意图就是使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
其实tomcat中管道和过滤器模式就是职责链模式的抽象,把它应用到软件体系架构中。
用一个通俗例子来说,就好比警察抓犯人,在案件发生后经侦察已经确认此案由一团伙共5个犯人所作,数量已经确定了,打个比方这个犯罪团伙是这样组成的,这5个犯人中只有一个主犯 E,其余四个从犯中只有D这个人认识并能找到主犯,D找了个只有他才认识的C犯,C又找了只有他才认识的B犯,B也是找了只有他才认识的A犯, 所以他们组成了一个犯罪团伙了。正好警察是从抓到 A开始的,接着A招供了B,B招供了C,C招供了D,D最后招供了主犯E了。(A->B->C->D->>E),呵呵,可能比喻得有点勉强了,但你能清楚了解就好,其实为什么突出有个主犯E呢,就好类比tomcat中的Filter 和servlet的执行顺序。(A->B->C->D)都好比如是Filter.doFilter(),链式的执行后,而主犯E就是最后用户定义的Servlet.service()了。
上面的都是比喻性地说明了一下,从下面开始就结合源代码来分析说明了。
init(FilterConfig):
当容器如tomcat启动的时候就会调用的filter初始化方法。在容器启动的时候。它加载应用程序中的配置描述符 web.xml 文件,解析过滤器配置信息。 并获取文件中指定的filter初始化参数。filter顺序是按<filter-mapping>的顺序依次排列保存下来,所以此时filter的数量 n 已经能被计算确定下来了。
在Tomcat处理请求的Servlet实例之前先要处理与之相关联的所有Filter的,在StandardWrapperValve类的#invoke()方法中调用filterChain.doFilter(request.getRequest(), response.getResponse()); 从这开始调用Filter了,
以下是在ApplicationFilterChain类两个方法的源代码:
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (Globals.IS_SECURITY_ENABLED) { final ServletRequest req = request; final ServletResponse res = response; try { java.security.AccessController .doPrivileged(new java.security.PrivilegedExceptionAction() { public Object run() throws ServletException, IOException { internalDoFilter(req, res); return null; } }); } catch (PrivilegedActionException pe) { ...... } } else { internalDoFilter(request, response); //这个方法无论怎么样都会被调用 } } private void internalDoFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { if (pos < n) { //n 就是web.xml定义的Filter的数量,init时已经计算出来,filter的排列顺序按<filter-mapping>的顺序已经定下来, 而且保存在filterConfig中了。 ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = null; try { // 得到当前Filter filter = filterConfig.getFilter(); support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT, filter, request, response); if (Globals.IS_SECURITY_ENABLED) { final ServletRequest req = request; final ServletResponse res = response; Principal principal = ((HttpServletRequest) req).getUserPrincipal(); Object[] args = new Object[] { req, res, this }; SecurityUtil.doAsPrivilege("doFilter", filter, classType, args); args = null; } else { // 这里调用Filter的#doFilter()方法。 注意看这个filter.doFilter(request, response, this); 中 //的this引用指向的就是ApplicationFilterChain类的对象,所以传递给Filter的chain实例就是ApplicationFilterChain类的对象。 filter.doFilter(request, response, this); //还记得前面比喻的例子吗, 犯人A记住B,B->C,C->D吗, //在Filter的#doFilter()方法里都会调用#chain.doFilter(request,response); 这个方法才能往下继续执行下去, //就好比只要有一犯人记不住下一个犯人,这条线索就中断的不会继续往下了。 // 再来看这个chain的定义,它是ApplicationFilterChain类的的引用,而这个internalDoFilter(..)方法所在的类就是ApplicationFilterChain类中 //于是程序执行到Filter 中的chain.doFilter(request,response)相当于程序又再次调用本方法,也可以说是一种变相的递归了。 } support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT, filter, request, response); } catch (Exception e) { ............... } ........省略 return; //说明只要符合if(pos < n)这条件,这方法只执行到这了。 } try { if (Globals.STRICT_SERVLET_COMPLIANCE) { lastServicedRequest.set(request); lastServicedResponse.set(response); } // 当Filter全部调用完毕后,而且在最后的一个Filter中有调用#chain.doFilter(request,response);这一行, //才会把请求真正的传递给Servlet了,调用它的#service()方法。 support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT, servlet, request, response); if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse)) { if (Globals.IS_SECURITY_ENABLED) { final ServletRequest req = request; final ServletResponse res = response; Principal principal = ((HttpServletRequest) req).getUserPrincipal(); Object[] args = new Object[] { req, res }; SecurityUtil.doAsPrivilege("service", servlet, classTypeUsedInService, args, principal); args = null; } else { servlet.service((HttpServletRequest) request, (HttpServletResponse) response); } } else { servlet.service(request, response); } support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT, servlet, request, response); } catch (Exception e) { ............ }............ finally { if (Globals.STRICT_SERVLET_COMPLIANCE) { lastServicedRequest.set(null); lastServicedResponse.set(null); } } }
在Tomcat怎么处理Filter的流程说完了,接下来开始讨论Struts2的了,
Struts2就是由一个 StrutsPrepareAndExecuteFilter也就是一个filter作为控制器入口。所以它要在web.xml中配置。当浏览器发来的请求到达这个 StrutsPrepareAndExecuteFilter了,
就会调用 它的#doFilter()方法:
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { prepare.setEncodingAndLocale(request, response);//设置编码和Locale prepare.createActionContext(request, response); prepare.assignDispatcherToThread();//获取Dispatcher的实例然后设置ThreadLocal<Dispatcher> instance中,可见Struts2框架为每一个线程都提供了一个Dispatcher对象,所以在编写Action的时候不需要考虑多线程的问题了。 if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { /* (1) 对请求进行包装 */ request = prepare.wrapRequest(request); /* (2) 获得Action Mapping ,其中包含namespace处理*/ ActionMapping mapping = prepare.findActionMapping(request, response, true); /* (3)当Mapping为空时,检查是否访问的为静态资源 */ if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { /* (4) 如果请求的是action资源,这会执行此方法,在这方法中会调用很多相关的方法做了很多工作(例如execute)和拦截器执行等 */ execute.executeAction(request, response, mapping); //注意到这里了吗,没有chain.doFilter(request, response);也就是说如果执行的请求的资源是Action,就不会再往下执行到Servlet.service()了。 } } } finally { prepare.cleanupRequest(request); } }
从上面(1)(2)(3)(4)点分别分开重点分明;
(1) 对请求进行包装
(2) 获得Action Mapping 以及怎么处理namespace的。
(3)如何检查是否访问的为静态资源,还有如果是正常的请求如jsp,js等,则让它真接通过chain.doFilter(..)链下去。
(4)调用被请求的Action的执行方法。
下面形如依次分析说明了。
(1)对请求进行包装:request = prepare.wrapRequest(request);
public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException {
HttpServletRequest request = oldRequest;
try {
request = dispatcher.wrapRequest(request, servletContext);
}catch{..}
}
以下是Dispatcher中#wrapRequest()源代码:
public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException { // don't wrap more than once if (request instanceof StrutsRequestWrapper) {// 判断request是否是StrutsRequestWrapper的对象,保证对request只包装一次。 return request; } String content_type = request.getContentType(); //判断Content-Type是否是multipart/form-data,如果是的话返回一个MultiPartRequestWrapper的对象处理文件上传, //否则返回StrutsRequestWrapper的对象处理普通请求。 if (content_type != null && content_type.contains("multipart/form-data")) { MultiPartRequest mpr = null; //check for alternate implementations of MultiPartRequest Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class); if (multiNames != null) { for (String multiName : multiNames) { if (multiName.equals(multipartHandlerName)) { mpr = getContainer().getInstance(MultiPartRequest.class, multiName); } } } if (mpr == null ) { mpr = getContainer().getInstance(MultiPartRequest.class); } request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext)); } else { request = new StrutsRequestWrapper(request); } return request; }
(2) 获得Action Mapping 以及怎么处理namespace的。
ActionMapping mapping = prepare.findActionMapping(request, response, true);
//ActionMapping类,它内部封装了如下6个字段:
//ActionMapping类,它内部封装了如下6个字段: public class ActionMapping { private String name;// Action名 private String namespace;// Action所在的名称空间 private String method;// 执行方法 private String extension;//扩展名如(.action) private Map<String, Object> params;// 可以通过set方法设置的参数 private Result result;// 返回的结果类型 } public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) { ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY); if (mapping == null || forceLookup) { try { mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager()); if (mapping != null) { request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); } } catch (Exception ex) { ..... } } return mapping; }
实质调用的是DefaultActionMapper类的getMapping(..)
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); String uri = getUri(request);//下面祥细介绍 int indexOfSemicolon = uri.indexOf(";"); uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; uri = dropExtension(uri, mapping);//下面祥细介绍 if (uri == null) { return null; } parseNameAndNamespace(uri, mapping, configManager);//下面祥细介绍 handleSpecialParameters(request, mapping);//此方法用于处理Struts框架定义的四种特殊的prefix:Method prefix,Action prefix,Redirect prefix,Redirect-action prefix if (mapping.getName() == null) { return null; } parseActionName(mapping); return mapping; } protected String getUri(HttpServletRequest request) { // handle http dispatcher includes. String uri = (String) request .getAttribute("javax.servlet.include.servlet_path"); if (uri != null) { return uri; } uri = RequestUtils.getServletPath(request); if (uri != null && !"".equals(uri)) { return uri; } uri = request.getRequestURI(); return uri.substring(request.getContextPath().length()); }
getUri()这个方法首先判断请求是否来自于一个jsp的include,
如果是,那么请求request.getAttribute("javax.servlet.include.servlet_path")属性可以获得include的页面uri,
否则通过一般的方法获得请求的uri,最后返回去掉ContextPath的请求路径,
比如http://127.0.0.1:8080/struts2/text/index.jsp?param=1,返回的为/text/index.jsp。去掉了ContextPath和查询字符串等。
uri = dropExtension(uri);
负责去掉Action的"扩展名"(默认为"action")并设置在mapping里,源代码如下:
protected String dropExtension(String name, ActionMapping mapping) { if (extensions == null) { return name; } for (String ext : extensions) { if ("".equals(ext)) { int index = name.lastIndexOf('.'); if (index == -1 || name.indexOf('/', index) >= 0) { return name; } } else { String extension = "." + ext; if (name.endsWith(extension)) { name = name.substring(0, name.length() - extension.length()); mapping.setExtension(ext);//设置扩展名 return name; } } } return null; }
此方法用于解析Action的名称和命名空间,并赋给ActionMapping对象。源代码如下:
protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) { String namespace, name; int lastSlash = uri.lastIndexOf("/"); //首先如果经过前面处理后的uri为空“” ,就如请求contextPath/后就没有“/”了,则设置 namespace = ""; if (lastSlash == -1) { namespace = ""; name = uri; } else if (lastSlash == 0) { //如果经过前面处理后的uri为/everything ,则设置 namespace = "/"; namespace = "/"; name = uri.substring(lastSlash + 1); } else if (alwaysSelectFullNamespace) { // alwaysSelectFullNamespace默认为false,代表是否将最后一个"/"前的字符串作为名称空间。 namespace = uri.substring(0, lastSlash);// 获得字符串 namespace name = uri.substring(lastSlash + 1);// 获得字符串 name } else { //尝试去找在.xml中已经定义好的namespace,默认为空""字符。 Configuration config = configManager.getConfiguration(); String prefix = uri.substring(0, lastSlash); namespace = ""; boolean rootAvailable = false; // Find the longest matching namespace, defaulting to the default //按最长匹配原则,如果struts.xml中的配置的有几个package并且namespace分别为 (如namespace=/first 。namespace=/first/second) //请求的uri如果是/first/second/text.action 则先找到最长匹配namespace=/first/second的package for (Object cfg : config.getPackageConfigs().values()) { String ns = ((PackageConfig) cfg).getNamespace(); if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) { if (ns.length() > namespace.length()) { namespace = ns; } } if ("/".equals(ns)) { rootAvailable = true; } } name = uri.substring(namespace.length() + 1); //// allowSlashesInActionNames代表是否允许"/"出现在Action的名称中,默认为false if (rootAvailable && "".equals(namespace)) { namespace = "/"; } } if (!allowSlashesInActionNames && name != null) { int pos = name.lastIndexOf('/'); if (pos > -1 && pos < name.length() - 1) { name = name.substring(pos + 1); } } mapping.setNamespace(namespace); mapping.setName(name); }
其实当这次namespace设置在mapping中后,在execute.executeAction(request, response, mapping)处理流程中会去相应的package中查找请求的action,
如果根据这次的namespace中对应的package中没有找到请求的action,会再次在DefaultConfiguration#getActionConfig(String namespace, String name)
中去找这个namespace的config,如果还是没有在这个namespace中找到对应name的Action则最终去默认为""空字符串的namespace中找。
public synchronized ActionConfig getActionConfig(String namespace, String name) { ActionConfig config = findActionConfigInNamespace(namespace, name); // try wildcarded namespaces if (config == null) { NamespaceMatch match = namespaceMatcher.match(namespace); if (match != null) { config = findActionConfigInNamespace(match.getPattern(), name); // If config found, place all the matches found in the namespace processing in the action's parameters if (config != null) { config = new ActionConfig.Builder(config) .addParams(match.getVariables()) .build(); } } } //如果还是找不到在namespace的配置Actionconfig,则去namespace为""空的package中找Action为name为ActionConfig if ((config == null) && (namespace != null) && (!"".equals(namespace.trim()))) { config = findActionConfigInNamespace("", name); } return config; } //解析ActionName,并通过是否存在动态方法调用如:"name!method"形式的action调用 protected ActionMapping parseActionName(ActionMapping mapping) { if (mapping.getName() == null) { return mapping; } if (allowDynamicMethodCalls) {//如果允许动态方法调用,则去判断ActionName中是否为name!method形式, //如果有!分隔则解析出name和method并保存在mapping; String name = mapping.getName(); int exclamation = name.lastIndexOf("!"); if (exclamation != -1) { mapping.setName(name.substring(0, exclamation)); mapping.setMethod(name.substring(exclamation + 1)); } } return mapping;//此时mapping的6个属性已经组装完成了,就返回; }
(3)如何检查是否访问的为静态资源,还有如果是正常的请求如jsp等,
则让它真接通过chain.doFilter(..)链下去直到它会调用Servlet.service(),注:jsp最终也被解析为一个Servlet。
一就如的doFilter()中的源代码:
if (mapping == null) { //当前面获取mapping后还是null时,则检查是否访问的为静态资源 boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { //请求如.jsp .html .js,让它通过则让给容器如tomcat处理。 chain.doFilter(request, response); } } public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { //该请求不是一个Action请求时,先查找是不是访问的为静态资源 String resourcePath = RequestUtils.getServletPath(request); if ("".equals(resourcePath) && null != request.getPathInfo()) { resourcePath = request.getPathInfo(); } StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class); if (staticResourceLoader.canHandle(resourcePath)) { staticResourceLoader.findStaticResource(resourcePath, request, response); //如果访问的为静态资源则The framework did its job here return true; } else { //如果是一个正常的请求如.jsp .html .js,让它通过 return false; } }
(4)调用被请求的Action的执行方法。
-----由于太长了,所以分开在struts2的源代码分析及struts2的工作流程(二)
相关推荐
Struts2 是一个基于MVC 模式的Web 应用程序框架,它的源码分析可以帮助我们更好地理解框架的内部机制和工作流程。下面是Struts2 源码分析的相关知识点: 1. Struts2 架构图 Struts2 的架构图主要包括 Filter chain...
Struts2的设计思路和工作流程与Struts1.x有很大的区别,这使得深入理解其源代码变得至关重要。 在分析Struts2的源代码之前,你需要首先获取Struts2的源代码,可以通过访问...
struts2源代码 正宗的 源码struts2源代码 正宗的 源码struts2源代码 正宗的 源码struts2源代码 正宗的 源码
struts2源码详细解析51CTO下载-struts2源代码分析(个人觉得非常经典)
在深入理解Struts2的工作原理时,源码分析是必不可少的步骤。Struts2的核心设计理念和设计模式相比Struts1.x有了显著的变化,这使得它成为一个独立且成熟的框架。 首先,Struts2的架构基于WebWork的核心,这意味着...
本文将深入探讨Struts2的源码分析,特别是关于StrutsPrepareAndExecuteFilter的初始化过程,这是Struts2的核心组件之一,负责处理HTTP请求。 首先,我们来看`StrutsPrepareAndExecuteFilter`的初始化。这个过滤器...
尽管描述中提到不包含XWork源代码,但XWork是Struts2的基础,它处理Action的执行和异常管理。 在Struts2.3.4源代码中,我们可以深入理解以下关键知识点: 1. **FilterDispatcher**: 这是Struts2框架的入口点,负责...
本篇文章将深入探讨Struts2的运行原理,通过源码分析来揭示其内部工作机制。 首先,我们从核心组件开始。Struts2的核心组件包括Action、FilterDispatcher、Interceptor和Result。Action是业务逻辑的载体,它接收...
在MyEclipse9中,你可以导入这些源码,通过调试和阅读代码,理解Struts2的工作原理。对于初学者来说,这是一条深入理解MVC框架的好途径。你可以逐步分析Action类如何被调用,拦截器如何影响Action的执行流程,以及...
通过分析这个"北大青鸟 struts2 项目源代码",你可以了解到Struts2框架的实战应用,包括请求处理流程、配置文件的编写、Action的设计和实现、视图的渲染以及拦截器的使用。这有助于你更深入地理解和掌握Struts2框架...
Struts2是一个流行的Java Web应用程序框架,用于构建MVC(模型-视图-控制器)架构的应用。源码阅读对于理解其工作原理至关重要。本文将深入探讨Struts2的核心概念、类和请求处理流程。 首先,我们来看Struts2的架构...
接下来,我们详细分析Struts2的工作流程: 1. **请求接收**: 当用户在浏览器中发送HTTP请求到服务器时,Struts2框架首先通过一个前端控制器(Front Controller),即`StrutsPrepareAndExecuteFilter`,拦截这个...
这个"Struts2源代码项目"显然包含了Struts2框架的源代码,这对于开发者深入理解其内部工作原理,以及进行定制化开发和调试是极其有价值的。 首先,我们来看看`.classpath`和`.project`这两个文件。它们是Eclipse ...
"strut2课程源代码第一天及说明"可能包含了逐步的教程,指导你从零开始搭建和运行一个简单的Struts2应用。 标签"struts2例子代码"表明这些源代码包含了具体的操作示例,比如Action类的编写、配置文件的设置、拦截器...
本文将深入剖析Struts2的源码,揭示其工作原理,并汇总常见问题,帮助开发者更好地理解和使用这个框架。 一、Struts2框架基础 1. 框架结构:Struts2的核心组件包括Action、Result、Interceptor(拦截器)等。...