这部分着重分析从我们发出一个uri请求,一直到代码运行到我们自己写的action类为止,struts的控制部分的代码(还有数据流部分,我们后面再分析)





已经用了快1年多的struts2了,一直认为对开源框架的学习,应该到源码级别才行,否则开源岂不是没有意义了。

struts的运行图如下:

1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;

  2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);

  3、接着StrutsPrepareAndExecuteFilter被调用,StrutsPrepareAndExecuteFilter询问ActionMapper来决定这个请求是否需要调用某个Action;

  4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;

  5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;

  6、ActionProxy创建一个ActionInvocation的实例。

  7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。

  8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper。

先看我自己画的时序图:

struts在web.xml中的配置如下:

<filter>
    <filter-name>struts2</filter-name>
    <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

很显然,我们得去StrutsPrepareAndExecuteFilter里看看,这是一个filter。

现在,我们先不看代码,根据以往关于filter的学习经历,我们能知道,filter至少有3个方法:

init(FilterConfig filterConfig)

doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

destroy()

init方法是初始化的,只运行一次,destroy一样,也只运行一次。

doFilter是真正的struts的业务逻辑。

我们猜一下,init应该就是struts的环境初始化,目前我们先不管它。(下一节,我们再探讨init方法)

我们就按照前面说的struts运行的八个步骤来看看doFilter。

 

 //StrutsPrepareAndExecuteFilter.java
 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
                chain.doFilter(request, response);
            } else {
                prepare.setEncodingAndLocale(request, response);
                prepare.createActionContext(request, response);
                prepare.assignDispatcherToThread();
                request = prepare.wrapRequest(request);
                 //之前的代码 我们先不管
		//第三步 找到action的信息
                ActionMapping mapping = prepare.findActionMapping(request, response, true);
                if (mapping == null) {
                    boolean handled = execute.executeStaticResourceRequest(request, response);
                    if (!handled) {
                        chain.doFilter(request, response);
                    }
                } else {
                    execute.executeAction(request, response, mapping);
                }
            }
        } finally {
            prepare.cleanupRequest(request);
        }
    }

上面的ActionMapping mapping = prepare.findActionMapping(request, response, true);就是找到对应的action,怎么找的?看代码:

    //PrepareOperations.java
    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) {
                dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
            }
        }

        return mapping;
    }

这里有一个actionmapping,它是干什么的?

看代码:

public class ActionMapping {

    private String name;
    private String namespace;
    private String method;
    private String extension;
    private Map<String, Object> params;
    private Result result;
    //.....
    }

懂了么?,就是对应一个action

 mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());

上面的这行,就是先获取ActionMapper,再通过ActionMapper获取ActionMapping。

actionMapper是干什么的?

暂时不清楚

但是我能知道,getInstance返回的是ActionMapper的实现类:DefaultActionMapper

   

 //DefaultActionMapper.java 下面的就是时序图中的第四步
    public ActionMapping getMapping(HttpServletRequest request, ConfigurationManager configManager) {
        ActionMapping mapping = new ActionMapping();
	//去除工程名等等 获得uri
        String uri = RequestUtils.getUri(request);

	//url的带; jsessionid 问题
        int indexOfSemicolon = uri.indexOf(";");
        uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;

	//删除扩展名,如.action或者.do
        uri = dropExtension(uri, mapping);
        if (uri == null) {
            return null;
        }

	//把uri中的信息分析到mapping中
        parseNameAndNamespace(uri, mapping, configManager);
	//处理特殊参数
        handleSpecialParameters(request, mapping);
	//主要是用来处理形如/userAction!getAll.action的请求,分离action名和方法名:
        return parseActionName(mapping);
    }

对DefaultActionMapper的getMapping方法,我们得边调试边看。

例如我访问下面这个路径:

http://localhost:8080/Struts2.3.16.1Hibernate4.3.4Spring4.0.2/login.jsp

RequestUtils.getUri(request)返回的string是:/login.jsp

另外,经过uri = dropExtension(uri, mapping)后,uri就是null了。

然后就直接返回一个null





我们换一个请求路径

http://localhost:8080/Struts2.3.16.1Hibernate4.3.4Spring4.0.2/user/login.action

此时RequestUtils.getUri(request)的返回值就是:/user/login.action

经过dropExtension(uri, mapping)后,uri就是:/user/login





我们看到parseNameAndNamespace,这里干的就是从uri中分离得到请求的action名、命名空间。

这里面的代码,比较复杂,我们只需要知道它干了什么事就OK,它不是我们的重点。

之后的parseActionName干的就是处理动态方法调用的事,感兴趣的,大家可以看看代码

之后,我们就回到了StrutsPrepareAndExecuteFilter的dofilter方法,此时调用的是:

 execute.executeAction(request, response, mapping); (时序图中的第五步)

 查看代码,最终我们到达了Dispatcher的serviceAction方法:

 serviceAction方法,如下,我已经删掉了一些数据相关的代码,例如valuestack

//Dispatcher.java  时序图中的第六步
 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
                              ActionMapping mapping) throws ServletException {

       //处理栈的问题

       String namespace = mapping.getNamespace();
       String name = mapping.getName();
       String method = mapping.getMethod();

       Configuration config = configurationManager.getConfiguration();
        //时序图中的第7,8步
       ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
                    namespace, name, method, extraContext, true, false);

       //时序图中的第9步
       //4、如果ActionMapper决定需要调用某个Action,
       //FilterDispatcher把请求的处理交给ActionProxy;
       proxy.execute();
}
 //StrutsActionProxy.java  时序图中的第10步
    public String execute() throws Exception {
        ActionContext previous = ActionContext.getContext();
        ActionContext.setContext(invocation.getInvocationContext());
        try {
            return invocation.invoke();
        } finally {
            if (cleanupContext)
                ActionContext.setContext(previous);
        }
    }

StrutsActionProxy.execute中的invocation是DefaultActionInvocation的实例,其invoke方法如下:

 

  //DefaultActionInvocation.java
   public String invoke() throws Exception {

            if (interceptors.hasNext()) {
                final InterceptorMapping interceptor = interceptors.next();
                 resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
            } else {
                resultCode = invokeActionOnly();
            }
            return resultCode;
        }

    }

关于interceptor.getInterceptor().intercept(DefaultActionInvocation.this),这是一个典型的责任链模式,如果对它不清楚,大家可以参考

说说struts2中拦截器的请求流程一(模拟大致流程)

DefaultActionInvocation中有很多过滤器,对每一个请求,都是首先经过那个一个个过滤器然后进入action,之后再通过过滤器





invokeActionOnly之后会调用invokeAction,马上就要进入我们自己写的action里面了。

 //DefaultActionInvocation.java  代码有省略
 protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
        String methodName = proxy.getMethod();

        String timerKey = "invokeAction: " + proxy.getActionName();        

            boolean methodCalled = false;
            Object methodResult = null;
            Method method = null;
            try {
                method = getAction().getClass().getMethod(methodName, EMPTY_CLASS_ARRAY);
            } 

            if (!methodCalled) {
		//OK,我们已经进入 自己写的action了
                methodResult = method.invoke(action, EMPTY_OBJECT_ARRAY);
            }

            return saveResult(actionConfig, methodResult);
    }

这里我们只是分析了控制部分的代码,action中的成员变量的值是什么时候写入的,我们写的action返回一个success之后struts又干了什么,struts的启动过程是怎么样的,这都是一个一个的大问题,我会在后面的博客里和大家一一道来。





另外,今天glt回来了

glt是谁?

我女朋友

呵呵 她刚从成都飞回来

辛苦了~~~







参考资料

http://www.cnblogs.com/liuling/p/2013-8-10-01.html

Struts2 源码剖析 控制部分-----1的更多相关文章

  1. SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现

    SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...

  2. SpringMVC源码剖析(二)- DispatcherServlet的前世今生

    上一篇文章<SpringMVC源码剖析(一)- 从抽象和接口说起>中,我介绍了一次典型的SpringMVC请求处理过程中,相继粉墨登场的各种核心类和接口.我刻意忽略了源码中的处理细节,只列 ...

  3. 源码剖析@ApiImplicitParam对@RequestParam的required属性的侵入性

    问题起源 使用SpringCloud构建项目时,使用Swagger生成相应的接口文档是推荐的选项,Swagger能够提供页面访问,直接在网页上调试后端系统的接口, 非常方便.最近却遇到了一个有点困惑的 ...

  4. jQuery之Deferred源码剖析

    一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...

  5. Nodejs事件引擎libuv源码剖析之:高效线程池(threadpool)的实现

    声明:本文为原创博文,转载请注明出处. Nodejs编程是全异步的,这就意味着我们不必每次都阻塞等待该次操作的结果,而事件完成(就绪)时会主动回调通知我们.在网络编程中,一般都是基于Reactor线程 ...

  6. Apache Spark源码剖析

    Apache Spark源码剖析(全面系统介绍Spark源码,提供分析源码的实用技巧和合理的阅读顺序,充分了解Spark的设计思想和运行机理) 许鹏 著   ISBN 978-7-121-25420- ...

  7. Struts2 源码分析——DefaultActionInvocation类的执行action

    本章简言 上一章讲到关于拦截器的机制的知识点,让我们对拦截器有了一定的认识.我们也清楚的知道在执行用户action类实例之前,struts2会先去执行当前action类对应的拦截器.而关于在哪里执行a ...

  8. Struts2 源码分析——拦截器的机制

    本章简言 上一章讲到关于action代理类的工作.即是如何去找对应的action配置信息,并执行action类的实例.而这一章笔者将讲到在执行action需要用到的拦截器.为什么要讲拦截器呢?可以这样 ...

  9. Struts2 源码分析——Action代理类的工作

    章节简言 上一章笔者讲到关于如何加载配置文件里面的package元素节点信息.相信读者到这里心里面对struts2在启动的时候加载相关的信息有了一定的了解和认识.而本章将讲到关于struts2启动成功 ...

随机推荐

  1. mybatis源码解读(一)——初始化环境

    本系列博客将对mybatis的源码进行解读,关于mybatis的使用教程,可以查看我前面写的博客——传送门. 为了便于后面的讲解,我们这里首先构造一个统一环境.也可以参考mybatis官网. 1.数据 ...

  2. 网站用户身份识别俩大招之cookie

    导航: 原理介绍 代码实现 过程分析 追踪Cookie 原理介绍 众所周知,http协议是无状态的协议,简单理解是用户的前一步操作和后一步操作之间没有关系,互相不知道,不干扰.而在很多场景下,浏览网页 ...

  3. ASP.NET Core 添加统一模型验证处理机制

    一.前言 模型验证自ASP.NET MVC便有提供,我们可以在Model(DTO)的属性上加上数据注解(Data Annotations)特性,在进入Action之前便会根据数据注解,来验证输入的数据 ...

  4. Matlab 编译EXE

    环境:vs2013  matlab2015b 一.生成独立可执行的程序(exe文件)步骤1.设置编译器.在matlab命令行输入mbuild –setup以及mex –setup,选择安装的c编译器. ...

  5. Go 语言数据类型

    在 Go 编程语言中,数据类型用于声明函数和变量. 数据类型的出现是为了把数据分成所需内存大小不同的数据,编程的时候需要用大数据的时候才需要申请大内存,就可以充分利用内存. Go 语言按类别有以下几种 ...

  6. 安卓高级9 shareSDK 第三方登陆和分享Mob

    ShareSDK推荐此官网地址 官网注册和申请key流程 注册 略 进入后台创建应用得到可以 下载SDK 解压下载的压缩包 第三方平台申请key 如果你想申请QQ登陆或者分享需要去腾讯开发者申请,申请 ...

  7. Linux命令行总结

    1.修改同一目录下所有图片的尺寸(宽x高) 长宽比不变&长宽比改变 find ./ -name '*.jpg' -exec convert -resize 600x480 {} {} \; f ...

  8. Windows 8 Cython 的配置(解决Unable to find vcvarsall.bat问题)

    关键是安装之前配置编译器. 1.下载MinGW 编译器 http://www.mingw.org/download.shtml 2.把编译器路径(例如C:\Program Files (x86)\Co ...

  9. Python尾递归-创始人为何不愿TRE以及我们如何模拟TRE

    TRE=Tail Recursion Elimination 创始人是不愿意实现TRE的.他专门用了一篇文章来阐述原因. http://neopythonic.blogspot.com/2009/04 ...

  10. java自动装箱拆箱总结

    对于java1.5引入的自动装箱拆箱,之前只是知道一点点,最近在看一篇博客时发现自己对自动装箱拆箱这个特性了解的太少了,所以今天研究了下这个特性.以下是结合测试代码进行的总结. 测试代码: int a ...