从Struts2源码学习Struts2的工作原理

今天我和我好基友啊斌通过探讨struts2的源码,总结了一下它的原理,代码是不会骗人的。
总的来说:struts的工作原理有7步:
1 客户端初始化一个指向Servlet容器的请求;
web应用程序启动时就会加载并初始化ActionServler。
用户提交表单时,一个配置好的ActionForm对象被创建,并被填入表单相应的数据,
ActionServler根据Struts-config.xml文件配置好的设置决定是否需要表单验证,
如果需要就调用ActionForm的Validate()验证后选择将请求发送到哪个Action

2 这个请求经过一系列的过滤器
在项目部署的时候,由tomcat容器读取项目的web.xml文件,测试的web.xml文件如下:

       <?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
  <display-name></display-name> 

  <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>*.action</url-pattern>
    </filter-mapping>

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

然后被org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter这个过滤器拦截,执行其的init方法

public class StrutsPrepareAndExecuteFilter implements StrutsStatics, Filter {
    //PrepareOperations 包含在执行前请求的准备操作
    protected PrepareOperations prepare;
    protected ExecuteOperations execute;
    protected List<Pattern> excludedPatterns = null;

    public void init(FilterConfig filterConfig) throws ServletException {
        InitOperations init = new InitOperations();
        Dispatcher dispatcher = null;
        try {
            //主机配置包裹filterconfig
            FilterHostConfig config = new FilterHostConfig(filterConfig);
            //初始化日志
            init.initLogging(config);
            dispatcher = init.initDispatcher(config);
            init.initStaticContentLoader(config, dispatcher);

            prepare = new PrepareOperations(dispatcher);
            execute = new ExecuteOperations(dispatcher);
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);

            postInit(dispatcher, filterConfig);
        } finally {
            if (dispatcher != null) {
            //清理任何资源用于初始化调度
                dispatcher.cleanUpAfterInit();
            }
            init.cleanup();
        }
    }

我们可以看到,传入的参数是一些配置信息,包括框架的版本,过滤器的名字,发布的项目等等
这里写图片描述

然后执行一系列初始化的的方法用于初始化环境:eg:
这里面通过执行此方法:创建和初始化调度
这里写图片描述
得到了很多有用的信息,例如:默认编码集,默认的defalutFrameworkBeanName,还有通过FreeMarker技术动态生成的一个错误页面
这里写图片描述
以前不知到报错的页面什么时候生成的,现在知道啦。
最后就是执行一些收尾工作:eg:
dispatcher.cleanUpAfterInit()清理任何资源用于初始化调度 –> init.cleanup()–> ActionContext.setContext(null); init–>FilterHostConfig(主机配置包裹filterconfig),

3: 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action
好,初始化之后一个请求冲了过来,过滤器直接拦截,并执行里面的doFilter()方法。
我们接着看源码:

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

        HttpServletRequest request = (HttpServletRequest) req; //request,response的转换
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
                chain.doFilter(request, response);
            } else {
            //prepare包含在执行前请求的准备操作
                prepare.setEncodingAndLocale(request, response);
                //设置好Action的上下文,这个非常的重要
                prepare.createActionContext(request, response);
                prepare.assignDispatcherToThread();
                //用Struts的包装处理多部分请求的请求
                request = prepare.wrapRequest(request);

                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);
        }
    }

其中在执行prepare.createActionContext(request, response)方法中设置很多有用的信息,eg:请求的类,属性,栈工厂等等;
这里写图片描述
然后就是通过返回的map判断是否有action需要调用某个Action.
这里写图片描述
我们这里的话返回的mapping不为空,具体值如下:
这里写图片描述
,所以直接执行executeAction方法.

4 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy(action的代理)
继续看我们的代码:executeAction方法中只有一句代码,就是
dispatcher.serviceAction(request, response, mapping);
然后我们进入serviceAction方法.

public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
            throws ServletException {
    //创建上下文的map
        Map<String, Object> extraContext = createContextMap(request, response, mapping);

        // 如果前面有一个值栈,然后创建一个新的副本,并将它传递给新的操作
        ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
        //根据stack的状态判断nullStack的状态
        boolean nullStack = stack == null;
        if (nullStack) {
        //返回特定于当前线程的Action上下文。
            ActionContext ctx = ActionContext.getContext();
            if (ctx != null) {
                //获取OGNL值栈。
                stack = ctx.getValueStack();
            }
        }
        if (stack != null) {
            extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
        }

        String timerKey = "Handling request from Dispatcher";
        try {
            UtilTimerStack.push(timerKey);
            //取得对应的值 eg:namespace=/ name=reg_student
            String namespace = mapping.getNamespace();
            String name = mapping.getName();
            String method = mapping.getMethod();
        //创建Action的动态代理,这个非常的重要
            ActionProxy proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
                    namespace, name, method, extraContext, true, false);

            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

            // 如果ActionMapping说直冲向结果,则实现它
            if (mapping.getResult() != null) {
                Result result = mapping.getResult();
                result.execute(proxy.getInvocation());
            } else {
                //执行动态代理(非常重要)
                proxy.execute();
            }
    }

执行动态代理的过程我在之前的博客有写到,这里就不过多写了。

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

6 ActionProxy创建一个ActionInvocation的实例。
7 ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果
返回结果通常是(但不总是,也可 能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。
在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper

总的来说看了源码之后对struts的工作原理加深了印象,看优秀的代码,能进步。感觉在看源码的过程中千万要坐的住,遇到看不懂的不要心烦意乱,要稳得住,坚持慢慢看,总会有所收获的。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值