`
jianfulove
  • 浏览: 119990 次
  • 性别: Icon_minigender_1
  • 来自: 湛江
社区版块
存档分类
最新评论

struts2的源代码分析及struts2的工作流程(二)

阅读更多

(4)调用被请求的Action的执行方法。

          execute.executeAction(request, response, mapping);
             public void executeAction(HttpServletRequest request, HttpServletResponse response,

                        ActionMapping mapping) throws ServletException {
                    dispatcher.serviceAction(request, response, servletContext, mapping);//这个方法里具体的处

                                                                       //理Action调用,包括调用Action前后调用拦截器(Interceptor);
               }
  
       以下是调用Dispatcher类中serviceAction()的源代码:

 

 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
                              ActionMapping mapping) throws ServletException {

        Map<String, Object> extraContext = createContextMap(request, response, mapping, context);//此方法首先创建了一个名称为extraContext的Map对象
                                                                                                 //它保存了request,session,application,mapping的信息,这些信息以后可以统一在此对象(extraContext)中查找。 

        
        //如果request中已经有了一个ValueStack对象,将其保存下来,留待以后恢复,并把它进行一些封装后也存入extraContext中。 
        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";

        //根据前封装的mapping的信息去找到请求处理的Action

        try {
            UtilTimerStack.push(timerKey);
            String namespace = mapping.getNamespace();
            String name = mapping.getName();
            String method = mapping.getMethod();

            //获取配置包装Configuration类,此时的config所有的信息在容器如tomcat启动时已经加载完成了。
            Configuration config = configurationManager.getConfiguration();

           //下面这一句是创建Action代理的核心,下面要祥细介绍 (1)
            ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
                    namespace, name, method, extraContext, true, false);


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

           
            //如果mapping中的result属性已经有值了,就直接跳到Result处理类
            if (mapping.getResult() != null) {
                Result result = mapping.getResult();
                result.execute(proxy.getInvocation());
            } else {

            //这个方法是调用Action处理方法的核心了,包括调用Action前后调用拦截器(Interceptor);后面要祥细介绍了。(2)
                proxy.execute();
            }

            // If there was a previous value stack then set it back onto the request
            if (!nullStack) {
                request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
            }
        } catch (ConfigurationException e) {
           ........
        } catch (Exception e) {
            sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
        } finally {
            UtilTimerStack.pop(timerKey);
        }
    }

 

 

  先来了解一下 , 创建一个Action的代理对象——ActionProxy引用,实际上对Action的调用正是通过ActionProxy实现的,而ActionProxy又由ActionProxyFactory创建,ActionProxyFactory是创建ActionProxy的工厂。
        配置信息加载完成后(这里说的配置信息加载是在容器启动时已经加载完成了,现在只要取得config对象的引用也就能获得所有配置信息了),创建一个Action的代理对象——ActionProxy引用,实际上对Action的调用正是通过ActionProxy实现的,而ActionProxy又由ActionProxyFactory创建,ActionProxyFactory是创建ActionProxy的工厂。
      注:ActionProxy和ActionProxyFactory都是接口,他们的默认实现类分别是DefaultActionProxy和DefaultActionProxyFactory,位于com.opensymphony.xwork2包下。

   祥细介绍 (1)
    调用了DefaultActionProxyFactory类#.createActionProxy(namespace, name, method, extraContext, true, false);方法,源代码如下
 

 public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map<String, Object> extraContext, boolean executeResult, boolean cleanupContext) {
        
        ActionInvocation inv = new DefaultActionInvocation(extraContext, true); //创建了ActionInvocation,并注入extraContext的Map对象,里面包含了request,session,application,mapping的信息
        container.inject(inv);
        return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);//创建ActionProxy对象
    }

 //以下是创建ActionProxy对象的方法的实现
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);//创建ActionProxy实现类DefaultActionProxy
        container.inject(proxy);
        proxy.prepare();//这方法找到请求action的config并做了相关init工作
        return proxy;
    }



     protected void prepare() {
        String profileKey = "create DefaultActionProxy: ";
        try {
            UtilTimerStack.push(profileKey);

            //根据namespace, actionName去configuration中找到是否有对应的ActionConfig
            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.init(this);//下面祥细分析

        } finally {
            UtilTimerStack.pop(profileKey);
        }
    }

 
    需要要先说明 一下关于Configuration config = configurationManager.getConfiguration();
在容器启动时先加载Struts2的配置文件,如果没有人为配置,则默认加载struts-default.xml、struts-plugin.xml和struts.xml,并且将配置信息保存在形如com.opensymphony.xwork2.config.entities.XxxxConfig的类中。
类com.opensymphony.xwork2.config.providers.XmlConfigurationProvider负责配置文件的读取和解析, addAction()方法负责读取<action>标签,并将数据保存在ActionConfig中;
addResultTypes()方法负责将<result-type>标签转化为ResultTypeConfig对象;

loadInterceptors()方法负责将<interceptor>标签转化为InterceptorConfi对象;
loadInterceptorStack()方法负责将<interceptor-ref>标签转化为InterceptorStackConfig对象;

loadInterceptorStacks()方法负责将<interceptor-stack>标签转化成InterceptorStackConfig对象。
而上面的方法最终会被addPackage()方法调用,将所读取到的数据汇集到PackageConfig对象中,要清楚明白这是容器启动时这工作,现在说的是struts处理请求的情况了。
所以根据namespace, actionName去configuration中找到是否有对应的ActionConfig

getActionConfig(namespace, actionName);在前面说mapping中的namespace时已经分析过了,如不清楚可以回前面看看。
invocation.init(this);是调用了DefaultActionInvocation的方法,在这方法里根据前面的信息,创建了一个请求的Action处理o

 

 

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

        createAction(contextMap);//创建Action了,如果有actionEventListener,则触发监听器的调用。

        if (pushAction) {
            stack.push(action);
            contextMap.put("action", action);
        }

        invocationContext = new ActionContext(contextMap);
        invocationContext.setName(proxy.getActionName());

       
        //把config加载的iterceptors放到一个新的ArrayList<InterceptorMapping>里
        List<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
        interceptors = interceptorList.iterator();
    }

  //createAction()方法的源代码
 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 (Exception e) {
        ...................
        } finally {
            UtilTimerStack.pop(timerKey);
        }
         
         //如果有actionEventListener,则触发监听器的调用#prepare(action, stack)。
        if (actionEventListener != null) {
            action = actionEventListener.prepare(action, stack);
        }
    }

    //通过反射创建了一个新的Action了,看到这应该很清楚Action的出生了吧。
    public Object buildBean(String className, Map<String, Object> extraContext, boolean injectInternal) throws Exception {
        Class clazz = getClassInstance(className);
        Object obj = clazz.newInstance();
        if (injectInternal) {
            injectInternalBeans(obj);
        }
        return obj;
    }

 

 

创建Action完成了,现再往下一步就是   proxy.execute();真正地去调用action的方法了。跟进去祥细介绍如下:

    执行的是StrutsActionProxy类的execute()方法,调用invocation.invoke();

 

 public String execute() throws Exception {
        ActionContext previous = ActionContext.getContext();
        ActionContext.setContext(invocation.getInvocationContext());
        try {

            return invocation.invoke();//核心方法调用
        } finally {
            if (cleanupContext)
                ActionContext.setContext(previous);
        }
    }

  最终还是回到了DefaultActionInvocation类执行invoke()方法,在DefaultActionInvocation类中,
  定义了invoke()方法,该方法实现了截拦器的递归调用和执行Action的execute()方法。


    先来看一下Interceptor接口的定义:

 

 

Interceptor.java
publicinterface Interceptor extends Serializable {
void destroy();
void init();
String intercept(ActionInvocation invocation) throws Exception;
}

 所有的截拦器必须实现intercept方法,而该方法的参数恰恰又是ActionInvocation,所以,如果在intercept方法中调用invocation.invoke(),
代码DefaultActionInvocation.invoke()方法的部分代码
if (interceptors.hasNext()) {  resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);}会再次执行,从Action的Intercepor列表中找到下一个截拦器,依此递归。
  DefaultActionInvocation.invoke()源代码如下:

 

 

 public String invoke() throws Exception {
        String profileKey = "invoke: ";
        try {
            UtilTimerStack.push(profileKey);

            if (executed) {
                throw new IllegalStateException("Action has already executed");
            }

            /*
             下面代码说明。整个方法主要由2个if从句分割,在(1)处的if从句中,主要实现了拦截器的"递归"调用,
             说它是递归调用,其实是一种非传统的递归。传统的递归应该是函数调用自身,最后达成一定条件后退出,
             但是这里是将自身的引用作为参数传递给intercept(),然后在intercept()内部再调用DefaultActionInvocation的invoke(),
             实现了递归调用。
            */

            if (interceptors.hasNext()) {
                final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
                String interceptorMsg = "interceptor: " + interceptor.getName();
                UtilTimerStack.push(interceptorMsg);
                try {
                                resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
                            }
                finally {
                    UtilTimerStack.pop(interceptorMsg);
                }
            } else {

            //执行完所有的拦截器后,调用这个方法里最终通过反射 methodResult = method.invoke(action, EMPTY_OBJECT_ARRAY);调用了action中的方法了。
                resultCode = invokeActionOnly();  //最后resultCode=(String) methodResult;
            }

           
            //执行完action方法并返回结果后,此处调用了在PreResultListener中的定义的一些执行Result前的操作
            if (!executed) {
                if (preResultListeners != null) {
                    for (Object preResultListener : preResultListeners) {
                        PreResultListener listener = (PreResultListener) preResultListener;

                        String _profileKey = "preResultListener: ";
                        try {
                            UtilTimerStack.push(_profileKey);
                            listener.beforeResult(this, resultCode);
                        }
                        finally {
                            UtilTimerStack.pop(_profileKey);
                        }
                    }
                }

                // now execute the result, if we're supposed to
                if (proxy.getExecuteResult()) {

                    executeResult();  //// 根据配置文件构建Result,现在有以下这几种Result的实现,如:ActionChainResult,ServletDispatcherResult,
                                       //FreemarkerResult,ServletRedirectResult,ServletActionRedirectResult,VelocityResult等。
                }

                executed = true;
            }

            return resultCode;
        }
        finally {
            UtilTimerStack.pop(profileKey);
        }
    }


    private void executeResult() throws Exception {
        result = createResult();

        String timerKey = "executeResult: " + getResultCode();
        try {
            UtilTimerStack.push(timerKey);
            if (result != null) {
                result.execute(this);//执行了result实现类的方法,表示对于请求的资源继续往下派发给别的去处理,
                                    //还是用相关的视图呈现给浏览器用户,这要看你配置的result-type,
                                     //以后有机会再一一分析各种result的处理流程程了。

            } else if(){。。。。。。。。}
            }
        } finally {
            UtilTimerStack.pop(timerKey);
        }
    }

 

 拦截器的调用活动图:

 

 

最后再说下有些朋友很关注struts2是在什么时候给action的各个属性setAttribute()的呢?其实这个过程是在调用ParametersInterceptor拦截器时
在执行doIntercept()中完成set值的。源码如下

public String doIntercept(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();
        if (!(action instanceof NoParameters)) {
            ActionContext ac = invocation.getInvocationContext();
            final Map<String, Object> parameters = retrieveParameters(ac);

            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting params " + getParameterLogMap(parameters));
            }

            if (parameters != null) {
                Map<String, Object> contextMap = ac.getContextMap();
                try {
                    ReflectionContextState.setCreatingNullObjects(contextMap, true);
                    ReflectionContextState.setDenyMethodExecution(contextMap, true);
                    ReflectionContextState.setReportingConversionErrors(contextMap, true);

                    ValueStack stack = ac.getValueStack();

                    setParameters(action, stack, parameters);//这是方法里就通过反射给Action  setAttribute的。此处不再一一说明了,知道原理就行了。
                } finally {
                    ReflectionContextState.setCreatingNullObjects(contextMap, false);
                    ReflectionContextState.setDenyMethodExecution(contextMap, false);
                    ReflectionContextState.setReportingConversionErrors(contextMap, false);
                }
            }
        }
        return invocation.invoke();
    }

 

 呵呵,先分析到这里的,如果朋友有什么地方还不清楚的,可以指出,我会尽我最大努力为你解决。让我们共同学习共同进步。

 

7
0
分享到:
评论

相关推荐

    struts2源代码分析

    Struts2的设计思路和工作流程与Struts1.x有很大的区别,这使得深入理解其源代码变得至关重要。 在分析Struts2的源代码之前,你需要首先获取Struts2的源代码,可以通过访问...

    struts2源代码 struts2源代码

    struts2源代码 正宗的 源码struts2源代码 正宗的 源码struts2源代码 正宗的 源码struts2源代码 正宗的 源码

    struts2.3.4源代码

    尽管描述中提到不包含XWork源代码,但XWork是Struts2的基础,它处理Action的执行和异常管理。 在Struts2.3.4源代码中,我们可以深入理解以下关键知识点: 1. **FilterDispatcher**: 这是Struts2框架的入口点,负责...

    struts源代码源代码源代码源代码

    在分析"struts源代码源代码源代码"这个标题时,我们可以推断出讨论的主题是关于Struts框架的源码。Struts 2是其最新版本,基于Action和拦截器的模型,提供了灵活的控制流和强大的插件架构。源代码的学习可以帮助我们...

    mldn 李兴华 struts2.0 源代码

    李兴华是一位知名的IT教育专家,他在讲解Struts2.0源代码时,通常会深入解析框架的核心机制、工作流程以及如何通过源码理解其实现原理。在“mldn 李兴华 struts2.0 源代码”这个主题中,我们可以学习到以下几个关键...

    struts2教程源代码

    "strut2课程源代码第一天及说明"可能包含了逐步的教程,指导你从零开始搭建和运行一个简单的Struts2应用。 标签"struts2例子代码"表明这些源代码包含了具体的操作示例,比如Action类的编写、配置文件的设置、拦截器...

    struts2 源代码

    struts2 源代码 struts2 源代码 struts2 源代码 struts2 源代码

    Struts2源代码项目

    这个"Struts2源代码项目"显然包含了Struts2框架的源代码,这对于开发者深入理解其内部工作原理,以及进行定制化开发和调试是极其有价值的。 首先,我们来看看`.classpath`和`.project`这两个文件。它们是Eclipse ...

    JAVAEE源代码以及struts2源代码

    学习和研究JavaEE源代码及Struts2源代码,有助于开发者深入理解Web应用的工作原理,提升解决问题的能力,同时也能为开发更复杂的企业级应用打下坚实的基础。通过对源代码的阅读和分析,我们可以学习到最佳实践,提高...

    struts2.0.11源代码

    这个源代码包提供了对Struts2核心组件、拦截器、插件以及其他相关模块的深入理解。 在Struts2的核心组件中,主要包括以下部分: 1. **Action类**:它是业务逻辑的载体,处理用户请求并返回结果。在Struts2中,...

    张孝祥Struts视频源代码

    "张孝祥Struts视频源代码"很可能是由知名IT讲师张孝祥制作的一系列关于Struts框架的教程,其中包含了相应的源代码,帮助学习者通过实际操作理解Struts的工作原理和应用。 在深入探讨Struts框架之前,我们首先需要...

    struts1.3.10源代码

    深入研究Struts 1.3.10源代码,可以帮助我们掌握Web应用开发的基本架构,理解MVC模式在实践中的应用,以及学习如何通过配置和编程控制请求流程。同时,通过分析源码,还可以学习到如何处理HTTP请求,验证用户输入,...

    struts2源代码分析(个人觉得非常经典).docx

    讲述Struts2的工作原理。struts2源代码分析(个人觉得非常经典),讲解全面且易懂。

Global site tag (gtag.js) - Google Analytics