[置顶] struts2执行流程
2018-02-14 20:01
549 查看
写在前面:struts2在web应用层面属于表示层框架,在MVC开发模式中属于C(Controller控制器),负责对M(Model模型)和V(View视图)进行解耦。struts2是在struts1和webwork的技术基础上进行了合并的全新的框架。struts2虽然和struts1在名字上很相似,但是却不是后者的升级版。struts2其实是以另一个表示层框架webwork为核心,采用拦截器的机制来处理用户的请求,这样的设计也使得业务逻辑控制器能够与ServletAPI完全脱离开,所以struts2也可以理解为webwork的更新产品。从它们的处理请求的执行流程就可以看出相似点。
直观感受一下:
webwork:
struts2:
struts2和webwork都是通过一个FilterDispatcher过滤器来匹配客户端发送的所有请求(当然,现在struts2的过滤器名是StrutsPreparedAndExecuteFilter),不同于struts1是通过一个servlet来匹配所有请求,好了,这里先简单的了解一下struts2和struts1的区别, 在文章末尾会详细的介绍struts2和struts1的区别,开始进入主题。
struts2的执行流程(结合流程图分析):
客户端发送一个HTTP请求
该请求被struts2的核心过滤器StrutsPreparedAndExecuteFilter匹配(只要是在过滤器的url-pattern中配置了/*,那么任何请求都会进入该过滤器,无论该请求是否需要struts2来处理),当然,在进入这个过滤器之前会依次进入在web.xml中配置的位置在struts2过滤器之前的其他Filter或Servlet
struts2的过滤器会询问(形象一点的说法,其实就是调用方法)ActionMapper该请求是否有与之对应的业务控制类,如果没有,则放行,如果有,进入下一步执行流程
struts2通过ActionProxy实例化ActionInvocation,当然在这之前ActionProxy还会通过ConfigurationManager按序加载struts2的配置文件:default.properties, struts-default.xml, struts.properties, struts.xml…(先加载struts默认的,然后才是自己定义的),正是因为加载了这些配置文件所以struts才能找到相应的拦截器以及业务控制类。
ActionProxy初始化一个ActionInvocation并通过它的invoke来正式执行一系列的拦截器以及Action,在执行完Action之后会根据使用的模板(jsp, velocity, freemarker…)组装结果集Result,渲染页面
返回给客户端响应
接下来详细的分析一下:
这个excludedPatterns是一个List< Pattern >集合,里面包含了不被struts2过滤器拦截的url。看这一句:prepare.isUrlExcluded(request, excludedPatterns),判断这个请求里面是否包含这样的url,跟进源码查看一具体的实现:
知道了拦截器是通过excludedPatterns来判断哪个url不被拦截,那么这个excludedPatterns的值是从哪里来的呢?初步猜测是在StrutsPreparedAndExecuteFilter初始化(init)的时候设置的…果不其然,看源码:
跟进buildExcludedPatternsList方法:
struts2是根据STRUTS_ACTION_EXCLUDE_PATTERN常量的值来判断哪些请求不需要匹配,所以我们如果想要设置某些请求不被struts2匹配就可以设置这个常量struts.action.excludePattern,多个pattern之间用逗号隔开(pattern的写法和web.xml中配置的类似)
跟进prepare方法一看究竟:
设置编码的具体实现。嗯,代码写的蛮好…
2.4.1 创建Action计数器
这个计数器可是有点用处:它用来记录一个Action被调用的次数。那么为什么要记录它被调用的次数呢?这里先提前看一下doFilter方法的最后一步:prepare.cleanupRequest(request);这一步是用来清理掉该次请求所占用的内存,跟进源码:
相信看到这里,大家大致已经明白了这个计数器存在的意义:记录Action被请求的次数,如果请求的次数非常频繁,说明这个Action被调用的次数非常多,那么就暂时不释放掉它所占用的内存,反之,如果只请求了一次或者是几次,那么在这个Action执行完毕后就会释放掉它所占用的内存。
2.4.2 创建ActionContext
跟进源码看一下具体的实现:
这个ActionContext存储了一些与当前Action相关的信息。
再进入Dispatcher的setInstance方法中看一下:
再进入set方法中看一下:
终于找到根源了,获取到当前的线程,把dispatcher放入当前线程中。那么这个Dispatcher有什么作用呢?看一下源码,这个源码有点多,就摘抄一个重要的方法来看下吧:
Dispatcher可以用来初始化一系列的配置文件,并且是按序加载。
wrapRequest方法的源码如下:
为了防止这个请求是一个multipart/form-data(上传文件)类型的请求,将它包装一下。因为这种类型请求的参数如果不经过处理可能获取不到。继续进入dispatcher.wrapRequest的源码中:
发现这个ActionMapping其实是通过ActionMapper的getMapping方法来获得的
发现这个ActionMapper是一个接口,这里我启动我的项目debug发现是调用了它的实现类:DefaultActionMapper的getMapping方法,实现细节如下:
tips:在eclipse中,按Ctrl+T可以查看当前类的子类或者实现类
在getMaping方法中,获得当前请求中的uri以及配置文件中配置的action的name和namespace,看一下这个parseNameAndNamespace方法的实现细节:
从倒数两行代码可以看出了该方法的最终目的:将解析出来的name和namespace放入mapping,交给调用者来根据这个actionMapping判断请求是否有对应的业务控制类
查看executeStaticResourceRequest方法的具体实现:
简单点说就是:这个静态资源在项目中存在就返回,不存在就放行,交给其他的过滤器处理。
它又调用了Dispatcher的serviceAction方法,源码如下:
这一步主要做了三件事:创建Action的代理,封装结果集Result,设置值栈。
实际上是通过调用DefaultActionProxyFactory的createActionProxy方法来创建的Action的代理
在方法返回的时候调用重载的createActionProxy方法,进入源码看一下:
看这里:proxy.prepare();,proxy开始准备,继续跟源码:
准备的关键代码就是初始化invocation,点进去一看又是一个接口,Ctrl+T,选中DefaultActionInvocation这个实现类,查看它的init方法:
终于找到了创建Action的代码了,继续跟进源码:
经过这一步,终于通过了DefaultActionInvocation创建了Action。
然后我们回到上一步,成功创建Action之后就获取与之相关的拦截器列表,并用一个list集合装起来,依次迭代它们。
拦截器执行完毕后再执行Action,再封装结果集,再出拦截器,给客户端响应。
struts1是单例的,存在线程安全问题,struts2是多例的,不存在线程安全问题
struts1的业务控制类需要依赖servletAPI,struts2不需要
struts1对于页面请求的参数是通过一个ActionForm表单来收集的,struts2直接通过拦截器注入
struts1的业务流程是固定的(可以参考我的另一篇博客struts1原理),struts2可以通过拦截器改变这个流程
struts1是通过servlet来匹配所有的请求,struts2是通过filter来匹配所有的请求
直观感受一下:
webwork:
struts2:
struts2和webwork都是通过一个FilterDispatcher过滤器来匹配客户端发送的所有请求(当然,现在struts2的过滤器名是StrutsPreparedAndExecuteFilter),不同于struts1是通过一个servlet来匹配所有请求,好了,这里先简单的了解一下struts2和struts1的区别, 在文章末尾会详细的介绍struts2和struts1的区别,开始进入主题。
struts2的执行流程(结合流程图分析):
客户端发送一个HTTP请求
该请求被struts2的核心过滤器StrutsPreparedAndExecuteFilter匹配(只要是在过滤器的url-pattern中配置了/*,那么任何请求都会进入该过滤器,无论该请求是否需要struts2来处理),当然,在进入这个过滤器之前会依次进入在web.xml中配置的位置在struts2过滤器之前的其他Filter或Servlet
struts2的过滤器会询问(形象一点的说法,其实就是调用方法)ActionMapper该请求是否有与之对应的业务控制类,如果没有,则放行,如果有,进入下一步执行流程
struts2通过ActionProxy实例化ActionInvocation,当然在这之前ActionProxy还会通过ConfigurationManager按序加载struts2的配置文件:default.properties, struts-default.xml, struts.properties, struts.xml…(先加载struts默认的,然后才是自己定义的),正是因为加载了这些配置文件所以struts才能找到相应的拦截器以及业务控制类。
ActionProxy初始化一个ActionInvocation并通过它的invoke来正式执行一系列的拦截器以及Action,在执行完Action之后会根据使用的模板(jsp, velocity, freemarker…)组装结果集Result,渲染页面
返回给客户端响应
接下来详细的分析一下:
1. 客户端发送一个HTTP请求
可能是一个登陆请求,也可能是查询某个功能列表的请求…2. StrutsPreparedAndExecuteFilter过滤器拦截该请求
过滤器拦截到该请求的时候会调用doFilter方法,如下:public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { // 1.将ServletRequest和ServletResponse对象转换为HttpServletRequest和HttpServletResponse对象 HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { // 2.对不由struts2处理的请求放行,这个excludedPatterns是一个List<Pattern>集合,里面存储了不被struts2的过滤器匹配的url if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { // 3.设置请求和相应的编码以及国际化的相关信息 prepare.setEncodingAndLocale(request, response); // 4.创建一个Action的上下文,并初始化一个本地线程 // 源码注释:Creates the action context and initializes the thread local prepare.createActionContext(request, response); // 5.把dispatcher指派给本地线程 // 源码注释:Assigns the dispatcher to the dispatcher thread local prepare.assignDispatcherToThread(); // 6.包装一下request防止它是一个multipart/form-data类型的请求 // 源码注释:Wrap request first, just in case it is multipart/form-data request = prepare.wrapRequest(request); // 7.查找ActionMapping信息(包括name,namespace,method,extention,params,result) ActionMapping mapping = prepare.findActionMapping(request, response, true); // 8.没有找到请求对应的业务控制类 if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { // 9.找到了对应的业务控制类那就去执行该Action execute.executeAction(request, response, mapping); } } } finally { // 10.释放掉这个Request所占用的一些内存空间 prepare.cleanupRequest(request); } }
2.1. 首先将ServletRequest和ServletResponse对象转换为HttpServletRequest和HttpServletResponse对象
2.2. 判断是否设置了不被struts2过滤器拦截的请求
if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); }
这个excludedPatterns是一个List< Pattern >集合,里面包含了不被struts2过滤器拦截的url。看这一句:prepare.isUrlExcluded(request, excludedPatterns),判断这个请求里面是否包含这样的url,跟进源码查看一具体的实现:
public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) { if (excludedPatterns != null) { // 1.获取当前请求中的uri String uri = RequestUtils.getUri(request); // 2.查看集合中是否有与之匹配的,有就返回true for ( Pattern pattern : excludedPatterns ) { if (pattern.matcher(uri).matches()) { return true; } } } return false; }
知道了拦截器是通过excludedPatterns来判断哪个url不被拦截,那么这个excludedPatterns的值是从哪里来的呢?初步猜测是在StrutsPreparedAndExecuteFilter初始化(init)的时候设置的…果不其然,看源码:
public void init(FilterConfig filterConfig) throws ServletException { InitOperations init = new InitOperations(); Dispatcher dispatcher = null; try { FilterHostConfig config = new FilterHostConfig(filterConfig); init.initLogging(config); dispatcher = init.initDispatcher(config); init.initStaticContentLoader(config, dispatcher); prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher); execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher); // 就是这里,创建了不匹配的url列表 this.excludedPatterns = init.buildExcludedPatternsList(dispatcher); postInit(dispatcher, filterConfig); } finally { if (dispatcher != null) { dispatcher.cleanUpAfterInit(); } init.cleanup(); } }
跟进buildExcludedPatternsList方法:
public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) { // 由此可知是struts2是读取了STRUTS_ACTION_EXCLUDE_PATTERN常量的值来判断哪些请求不需要匹配 return buildExcludedPatternsList(dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN)); } private List<Pattern> buildExcludedPatternsList( String patterns ) { if (null != patterns && patterns.trim().length() != 0) { List<Pattern> list = new ArrayList<Pattern>(); String[] tokens = patterns.split(","); for ( String token : tokens ) { list.add(Pattern.compile(token.trim())); } return Collections.unmodifiableList(list); } else { return null; } }
struts2是根据STRUTS_ACTION_EXCLUDE_PATTERN常量的值来判断哪些请求不需要匹配,所以我们如果想要设置某些请求不被struts2匹配就可以设置这个常量struts.action.excludePattern,多个pattern之间用逗号隔开(pattern的写法和web.xml中配置的类似)
2.3 设置请求和响应的编码以及国际化的相关信息
prepare.setEncodingAndLocale(request, response),是由Dispatcher在准备的过程中完成的,源码如下:/** * Sets the request encoding and locale on the response * 设置请求和响应的国际化编码 */ public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) { dispatcher.prepare(request, response); }
跟进prepare方法一看究竟:
/** * Prepare a request, including setting the encoding and locale. * * @param request The request * @param response The response */ public void prepare(HttpServletRequest request, HttpServletResponse response) { String encoding = null; if (defaultEncoding != null) { encoding = defaultEncoding; } // check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) { encoding = "UTF-8"; } Locale locale = null; if (defaultLocale != null) { locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale()); } if (encoding != null) { applyEncoding(request, encoding); } if (locale != null) { response.setLocale(locale); } if (paramsWorkaroundEnabled) { request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request } }
设置编码的具体实现。嗯,代码写的蛮好…
2.4 创建一个Action的上下文,并初始化一个本地线程
/** * Creates the action context and initializes the thread local */ public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) { ActionContext ctx; // 计数器,作用:记录这个Action被访问的次数,与后续释放ActionContext的内存空间有关,初始化为1 Integer counter = 1; Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); // 如果request域里面已经有一个计数器,说明这个Action已经被实例化调用过了,那么就将这个计数器的值加1 if (oldCounter != null) { counter = oldCounter + 1; } // 和上面的计数器类似,尝试从request获取这个Action的上下文对象,如果存在就直接使用这个ActionContext ActionContext oldContext = ActionContext.getContext(); if (oldContext != null) { // detected existing context, so we are probably in a forward ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap())); } else { // 不存在就创建一个值栈(存储了与这个Action相关的一些属性)和一个ActionContext ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext)); ctx = new ActionContext(stack.getContext()); } // 把这个计数器放到request域里面 request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); // 跟进这个方法后有这么个注释:Sets the action context for the current thread(把这个Action上下文对象放入当前线程) ActionContext.setContext(ctx); return ctx; }
2.4.1 创建Action计数器
这个计数器可是有点用处:它用来记录一个Action被调用的次数。那么为什么要记录它被调用的次数呢?这里先提前看一下doFilter方法的最后一步:prepare.cleanupRequest(request);这一步是用来清理掉该次请求所占用的内存,跟进源码:
/** * Cleans up a request of thread locals */ public void cleanupRequest(HttpServletRequest request) { // 获取request域中计数器 Integer counterVal = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); if (counterVal != null) { // 这个计数器的值不为空就把它减一 counterVal -= 1; // 重新放入request域,用CLEARUP_RECURSION_COUNTER这个常量保存 request.setAttribute(CLEANUP_RECURSION_COUNTER, counterVal); // 关键:如果这个计数器在减一之后的值仍然大于0,那么就不释放它所占用的内存,记录一条日志就直接返回了 if (counterVal > 0 ) { if (log.isDebugEnabled()) { log.debug("skipping cleanup counter="+counterVal); } return; } } // 否则就clearUp掉 // always clean up the thread request, even if an action hasn't been executed try { dispatcher.cleanUpRequest(request); } finally { ActionContext.setContext(null); Dispatcher.setInstance(null); } }
相信看到这里,大家大致已经明白了这个计数器存在的意义:记录Action被请求的次数,如果请求的次数非常频繁,说明这个Action被调用的次数非常多,那么就暂时不释放掉它所占用的内存,反之,如果只请求了一次或者是几次,那么在这个Action执行完毕后就会释放掉它所占用的内存。
2.4.2 创建ActionContext
跟进源码看一下具体的实现:
public class ActionContext implements Serializable { static ThreadLocal<ActionContext> actionContext = new ThreadLocal<ActionContext>(); // 处理请求的Action的name public static final String ACTION_NAME = "com.opensymphony.xwork2.ActionContext.name"; // 与这个Action相关的值栈 public static final String VALUE_STACK = ValueStack.VALUE_STACK; // session相关 public static final String SESSION = "com.opensymphony.xwork2.ActionContext.session"; // application域 public static final String APPLICATION = "com.opensymphony.xwork2.ActionContext.application"; ...
这个ActionContext存储了一些与当前Action相关的信息。
2.5 把dispatcher指派给本地线程-prepare.assignDispatcherToThread()
其实从上一步我们就不难看出struts2为每一个Action创建一个线程,这也体现了struts2相比于struts1的优势:线程安全,因为每一个Action都由一个单独的线程来负责,不存在共享数据,所以安全。我们进入PrepareOperations类的assignDispatcherToThread()方法的源码看一下:public void assignDispatcherToThread() { // 将当前的这个dispatcher实例化 Dispatcher.setInstance(dispatcher); }
再进入Dispatcher的setInstance方法中看一下:
public static void setInstance(Dispatcher instance) { // 调用了这个Dispatcher的instance属性的set方法 // ps:这个instance是一个ThreadLocal对象 Dispatcher.instance.set(instance); }
再进入set方法中看一下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
终于找到根源了,获取到当前的线程,把dispatcher放入当前线程中。那么这个Dispatcher有什么作用呢?看一下源码,这个源码有点多,就摘抄一个重要的方法来看下吧:
/** * 初始化一系列的配置文件:default.properties, struts-default.xml, struts.properties, struts.xml...文件,并且是按顺序加载 */ public void init() { if (configurationManager == null) { configurationManager = createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME); } try { init_FileManager(); init_DefaultProperties(); // [1]default.properties init_TraditionalXmlConfigurations(); // [2]struts-default.xml... init_LegacyStrutsProperties(); // [3]struts.properties init_CustomConfigurationProviders(); // [5]struts.xml init_FilterInitParameters() ; // [6]初始化过滤器配置的参数 init_AliasStandardObjects() ; // [7]别名什么的..这里就不深究了 Container container = init_PreloadConfiguration(); container.inject(this); init_CheckWebLogicWorkaround(container); if (!dispatcherListeners.isEmpty()) { for (DispatcherListener l : dispatcherListeners) { l.dispatcherInitialized(this); } } } catch (Exception ex) { if (LOG.isErrorEnabled()) LOG.error("Dispatcher initialization failed", ex); throw new StrutsException(ex); } }
Dispatcher可以用来初始化一系列的配置文件,并且是按序加载。
2.6 包装一下request
request = prepare.wrapRequest(request);
wrapRequest方法的源码如下:
public HttpServletRequest wrapRequest(HttpServletRequest oldRequest) throws ServletException { HttpServletRequest request = oldRequest; try { // Wrap request first, just in case it is multipart/form-data // 为了防止这个请求是一个multipart/form-data(上传文件)类型的请求 // parameters might not be accessible through before encoding (ww-1278) // 因为这种类型请求的参数如果不经过处理可能获取不到 request = dispatcher.wrapRequest(request, servletContext); } catch (IOException e) { throw new ServletException("Could not wrap servlet request with MultipartRequestWrapper!", e); } return request; }
为了防止这个请求是一个multipart/form-data(上传文件)类型的请求,将它包装一下。因为这种类型请求的参数如果不经过处理可能获取不到。继续进入dispatcher.wrapRequest的源码中:
public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException { // don't wrap more than once // 只包装一次 if (request instanceof StrutsRequestWrapper) { return request; } String content_type = request.getContentType(); // 如果请求类型不为空并且是multipart/form-data类型的请求,那么就包装一下 if (content_type != null && content_type.contains("multipart/form-data")) { MultiPartRequest mpr = getMultiPartRequest(); LocaleProvider provider = getContainer().getInstance(LocaleProvider.class); request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext), provider); } else { request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); } return request; }
2.7 查找该请求相关的信息actionMapping
prepare.findActionMapping(request, response, true),源码如下:public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) { ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY); if (mapping == null || forceLookup) { try { // 这里就开始询问ActionMaper是否存在与该请求对应的业务控制类,详见下面的第三大步 mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager()); if (mapping != null) { request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping); } } catch (Exception ex) { dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex); } } return mapping; }
发现这个ActionMapping其实是通过ActionMapper的getMapping方法来获得的
ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager);
发现这个ActionMapper是一个接口,这里我启动我的项目debug发现是调用了它的实现类:DefaultActionMapper的getMapping方法,实现细节如下:
tips:在eclipse中,按Ctrl+T可以查看当前类的子类或者实现类
public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) { ActionMapping mapping = new ActionMapping(); // 通过request工具类获取当前请求的uri String uri = RequestUtils.getUri(request); int indexOfSemicolon = uri.indexOf(";"); uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri; // 去掉后缀.action... uri = dropExtension(uri, mapping); if (uri == null) { return null; } // 解析出configManager里面设置的actionName和namespace并放入ActionMapping parseNameAndNamespace(uri, mapping, configManager); handleSpecialParameters(request, mapping); return parseActionName(mapping); }
在getMaping方法中,获得当前请求中的uri以及配置文件中配置的action的name和namespace,看一下这个parseNameAndNamespace方法的实现细节:
protected void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) { String namespace, name; int lastSlash = uri.lastIndexOf("/"); ... // 将解析出来的name和namespace放入mapping mapping.setNamespace(namespace); mapping.setName(cleanupActionName(name)); }
从倒数两行代码可以看出了该方法的最终目的:将解析出来的name和namespace放入mapping,交给调用者来根据这个actionMapping判断请求是否有对应的业务控制类
2.8 没有找到请求对应的业务控制类所进行的操作
说明并没有为这个请求配置相应的业务控制类Action,就说明这个请求可能是一个静态的资源请求,于是就有了如下代码:if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); // 这里会根据上一步的返回值来确定是否执行 if (!handled) { chain.doFilter(request, response); } }
查看executeStaticResourceRequest方法的具体实现:
public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { // there is no action in this request, should we look for a static resource? // 老外还挺逗:这个请求没有对应的action来处理,我们应不应该看看它是不是请求一个静态资源? String resourcePath = RequestUtils.getServletPath(request); if ("".equals(resourcePath) && null != request.getPathInfo()) { resourcePath = request.getPathInfo(); } StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class); // 如果这个请求请求的资源在这个项目的资源路径下,返回true if (staticResourceLoader.canHandle(resourcePath)) { staticResourceLoader.findStaticResource(resourcePath, request, response); // The framework did its job here // 属于struts2管辖范围的静态资源,由struts2来处理 return true; } else { // this is a normal request, let it pass through // 一个普通的请求,放行 return false; } }
简单点说就是:这个静态资源在项目中存在就返回,不存在就放行,交给其他的过滤器处理。
2.9 如果找到了这个请求对应的业务控制类Action
那就调用ExecuteOperations的executeAction方法去执行这个Action,源码如下:public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException { dispatcher.serviceAction(request, response, servletContext, mapping); }
它又调用了Dispatcher的serviceAction方法,源码如下:
public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context, ActionMapping mapping) throws ServletException { Map<String, Object> extraContext = createContextMap(request, response, mapping, context); // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY); boolean nullStack = stack == null; if (nullStack) { ActionContext ctx = ActionContext.getContext(); if (ctx != null) { stack = ctx.getValueStack(); } } if (stack != null) { extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack)); } String timerKey = "Handling request from Dispatcher"; try { UtilTimerStack.push(timerKey); // 获取ActionMapping中的namespace,name,method String namespace = mapping.getNamespace(); String name = mapping.getName(); String method = mapping.getMethod(); Configuration config = configurationManager.getConfiguration(); // 创建ActionProxy代理 ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy( namespace, name, method, extraContext, true, false); request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack()); // if the ActionMapping says to go straight to a result, do it! // 如果mapping中有结果集,那么就去执行结果集 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 onto the request // 之前已经有值栈了,就把它放入当前request域里面 if (!nullStack) { request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack); } } catch (ConfigurationException e) { logConfigurationException(request, e); sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e); } catch (Exception e) { if (handleException || devMode) { sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e); } else { throw new ServletException(e); } } finally { UtilTimerStack.pop(timerKey); } }
这一步主要做了三件事:创建Action的代理,封装结果集Result,设置值栈。
实际上是通过调用DefaultActionProxyFactory的createActionProxy方法来创建的Action的代理
public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) { // 创建ActionInvocation负责迭代拦截器和执行Action ActionInvocation inv = new DefaultActionInvocation(extraContext, true); // 放入容器中 container.inject(inv); // 调用这个重载的createActionProxy方法 return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext); }
在方法返回的时候调用重载的createActionProxy方法,进入源码看一下:
public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) { DefaultActionProxy proxy = new DefaultActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext); container.inject(proxy); // 这里,proxy开始准备 proxy.prepare(); return proxy; }
看这里:proxy.prepare();,proxy开始准备,继续跟源码:
protected void prepare() { String profileKey = "create DefaultActionProxy: "; try { UtilTimerStack.push(profileKey); config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName); if (config == null && unknownHandlerManager.hasUnknownHandlers()) { config = unknownHandlerManager.handleUnknownAction(namespace, actionName); } if (config == null) { throw new ConfigurationException(getErrorMessage()); } resolveMethod(); if (!config.isAllowedMethod(method)) { throw new ConfigurationException("Invalid method: " + method + " for action " + actionName); } // 关键:开始初始化invocation invocation.init(this); } finally { UtilTimerStack.pop(profileKey); } }
准备的关键代码就是初始化invocation,点进去一看又是一个接口,Ctrl+T,选中DefaultActionInvocation这个实现类,查看它的init方法:
public void init(ActionProxy proxy) { this.proxy = proxy; Map<String, Object> contextMap = createContextMap(); // Setting this so that other classes, like object factories, can use the ActionProxy and other // contextual information to operate ActionContext actionContext = ActionContext.getContext(); if (actionContext != null) { actionContext.setActionInvocation(this); } // 创建Action(关键) createAction(contextMap); if (pushAction) { stack.push(action); contextMap.put("action", action); } invocationContext = new ActionContext(contextMap); invocationContext.setName(proxy.getActionName()); // get a new List so we don't get problems with the iterator if someone changes the list // 看到了吧,在这里获得了与action相关的拦截器,共19个 List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors()); // 迭代(就是执行所有的拦截器) interceptors = interceptorList.iterator(); }
终于找到了创建Action的代码了,继续跟进源码:
protected void createAction(Map<String, Object> contextMap) { // load action String timerKey = "actionCreate: " + proxy.getActionName(); try { UtilTimerStack.push(timerKey); // 可以看到在这里通过工厂类来创建Action action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap); } catch (InstantiationException e) { ... }
经过这一步,终于通过了DefaultActionInvocation创建了Action。
然后我们回到上一步,成功创建Action之后就获取与之相关的拦截器列表,并用一个list集合装起来,依次迭代它们。
// get a new List so we don't get problems with the iterator if someone changes the list List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors()); interceptors = interceptorList.iterator();
拦截器执行完毕后再执行Action,再封装结果集,再出拦截器,给客户端响应。
2.10 最终Action执行完毕一定要clear掉
防止内存泄漏(内存泄漏是指分配出去的内存不再使用,但是无法回收),当然在clear的时候还是要根据前面提到的计数器来判断是否清除。struts1和struts2的区别
struts1的业务控制类必须继承ActionSupport,struts2可以不用继承struts1是单例的,存在线程安全问题,struts2是多例的,不存在线程安全问题
struts1的业务控制类需要依赖servletAPI,struts2不需要
struts1对于页面请求的参数是通过一个ActionForm表单来收集的,struts2直接通过拦截器注入
struts1的业务流程是固定的(可以参考我的另一篇博客struts1原理),struts2可以通过拦截器改变这个流程
struts1是通过servlet来匹配所有的请求,struts2是通过filter来匹配所有的请求
相关文章推荐
- struts2的具体执行流程
- Struts2执行流程
- Struts2的执行流程及其工作原理
- struts2执行原理(执行流程)
- Struts2执行流程
- struts2 执行流程及工作原理
- Struts2第七篇【介绍拦截器、自定义拦截器、执行流程、应用】
- Struts2的执行流程
- Struts2 执行流程
- struts2执行流程
- Struts2执行流程
- struts2执行流程(zz)
- struts2的执行流程
- Servlet、Struts2、SpringMVC执行流程
- Struts2的执行流程全图
- Struts2的执行流程
- Struts2源码阅读之Action和Interceptor的执行流程
- Struts2执行流程
- struts2执行流程
- struts2执行流程及环境搭建