struct2源码解读之执行action请求(1)

   上篇博文我们讨论了struct2是如何用PrepareOperations的方法去处理action请求的:

    ①设置编码和本地化信息

    ②设置值栈,并把request,session,application等信息放到ActionContext.context中,构建起action的运行环境。

    ③把dispacher放到threadLoack变量中。

    ④过滤action请求,也就是处理黑名单。

    ⑤把不是黑名单的请求(url)封装成actionMapping对象

代码清单:actionMappingpublic class ActionMapping {    private String name;    private String namespace;    private String method;    private String extension;    private Map
 params;    private Result result;    }

   因为actionMapping封装了url的信息,因此当处理了以前的信息后,就开始根据actionMapping执行相应的请求。

if (mapping == null) {	boolean handled = execute.executeStaticResourceRequest(request, response);	if (!handled) {		chain.doFilter(request, response);	}} else {         //执行正常的action请求	execute.executeAction(request, response, mapping);}

    如果uri为空,或者是action为空时,返回一个空的actionMapping对象,这时struct2会尝试去加载/struct/或者是/static/下的静态资源,如果不成功,则退出当前过滤器跳到下个过滤器;如果返回一个不是空的actionMapping对象,则取执行正常的action请求。

    上面就是struct2处理action请求的整个流程。下面就详细探讨下struct2是如何执行action请求的。

    执行action请求是通过调用ExecuteOperations的executeAction方法来执行。这个方法一共传进了3个参数:request,respone,mapping.刚好拿到了处理url的所有信息

execute.executeAction(request, response, mapping);

   从这个方法可以看出,调用PrepareOperations或者是ExecuteOperations的方法,其实最终调用的都是disapcher的方法,设计这两个类其实都是为了对同一功能的方法进行更进一步的封装,以方便管理。

这里最终调用了dispatcher.serviceAction()方法。

    public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,ActionMapping mapping) throws ServletException {    /**      *1.把所有web对象放进一个map中      */Map
 extraContext = createContextMap(request, response, mapping, context);        /**          *2.获得值栈valueStack          */        ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);        //nullStack 的值为stack==null的值        boolean nullStack = stack == null;        if (nullStack) {            //如果request的值栈为空,从ActionContext中取            ActionContext ctx = ActionContext.getContext();            if (ctx != null) {                stack = ctx.getValueStack();            }        }        if (stack != null) {  extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));        }            try {            /**              *3.获得url信息              */            String namespace = mapping.getNamespace();            String name = mapping.getName();            String method = mapping.getMethod();             /**               *4.获取配置信息               */            Configuration config = configurationManager.getConfiguration();             /**               *5.从容器中取出ActionProxyFactory对象,并实例化一个ActionProxy 对象               */                          ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, extraContext, true, false);            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());            /**              *6.执行action请求              */            if (mapping.getResult() != null) {                Result result = mapping.getResult();                result.execute(proxy.getInvocation());            } else {                proxy.execute();            }            /**              *7.把值栈添加到request中去              */            if (!nullStack) {                request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);            }        } catch (ConfigurationException e) {           //出现异常,发送404或者是500错误              }     }

一、封装参数到一个map中

  public Map
 createContextMap(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping, ServletContext context) {        // request        Map requestMap = new RequestMap(request);        // parameters         Map params = new HashMap(request.getParameterMap());        // session        Map session = new SessionMap(request);        // application        Map application = new ApplicationMap(context);        /**          *封装所有参数到一个map中,特别注入,key为request的保存的是一个requestMap对象          *key值为StrutsStatics.HTTP_REQUEST的保存的才是一个request对象,session,                  *applicaiton也是如此          */        Map
 extraContext = createContextMap(requestMap, params, session, application, request, response, context);        //mapping        if (mapping != null) {            extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);        }        return extraContext;    }

二、获得值栈valueStack

   valueStack最终会以structs.valueStack为key值保存到request中,所以会先从request中获取valueStack,如果获取不到,则从 ActionContext中去取(ActionContext中的valueStack在PrepareOperations创建ActionContext的时候已经实例化)

 //实例化一个OgnlValueStack对象 ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext)); ctx = new ActionContext(stack.getContext());

     如果request中存在一个valueStack对象,则对这个valueStack进一步封装成OgnlValueStack对象

 public ValueStack createValueStack(ValueStack stack) {        ValueStack result = new OgnlValueStack(stack, xworkConverter, compoundRootAccessor, allowStaticMethodAccess);        container.inject(result);        stack.getContext().put(ActionContext.CONTAINER, container);        return result;    }

    并把这个对象以ValueStack.VALUE_STACK为key值,保存到上面的那个map中。

三、获取配置信息

    struct2在初始化的时候已经把所有配置信息到都封装到了configuration对象,而这个configuration对象交给了configurationManager对象管理,这个configurationManager又是dispacher的一个属性,因此这里就可以通过configurationManager获得了配置文件的信息。

Configuration config = configurationManager.getConfiguration();

四、实例化一个ActionProxy对象

4.1.实例化一个ActionInvocation对象对象

   struct2把执行action请求的所有方法都封装到了ActionProxy接口,所以这里需要实现ActionProxy接口,通过ActionProxyFactory生成一个ActionProxy对象(通过DefaultActionProxyFactory的createActionProxy方法)

public ActionProxy createActionProxy(String namespace, String actionName, String methodName, Map
 extraContext, boolean executeResult, boolean cleanupContext) {        //实例化一个ActionInvocation对象         ActionInvocation inv = new DefaultActionInvocation(extraContext, true);        //对ActionInvocation依赖注入        container.inject(inv);        return createActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);    }

    在这个方法中先实例化一个ActionInvocation对象,然后重载createActionProxy方法.struct2实现的是StrutsActionProxyFactory

4.2.实例化一个ActionProxy对象

public ActionProxy createActionProxy(ActionInvocation inv, String namespace, String actionName, String methodName, boolean executeResult, boolean cleanupContext) {        //实例化一个ActionProxy对象         StrutsActionProxy proxy = new StrutsActionProxy(inv, namespace, actionName, methodName, executeResult, cleanupContext);        //对ActionProxy对象依赖注入        container.inject(proxy);        //初始化        proxy.prepare();        return proxy;    }

     这里其实是对参数进行了进一步的封装,把运行环境封装到了ActionInvocation对象,把nameSpace,actionName等信息封装到了ActionProxy。

     上面都对ActionInvocation对象和ActionProxy对象进行了依赖注入,这里注入了哪些对象呢?

   @Inject    public void setObjectFactory(ObjectFactory factory) {        this.objectFactory = factory;    }      @Inject    public void setConfiguration(Configuration config) {        this.configuration = config;    }

    上面列了两条特别重要的,需要注入的bean.这个ObjectFactory,看它的名字,对象工厂,表示是一个存放了对象的容器,在buildAction的时候,会往action里面注入action所需要的bean.正因为是容器,spring也是ioc容器,因此在struct2和spring整合的时候,就可以从这里做为切入点,依赖注入ObjectFactory的时候,注入的是spring的factory,具体实现原理,后面再分析。

   准备了所有对象(环境信息)后,ActionProxy还做了一些准备工作。

4.3.ActionProxy初始化

  protected void prepare()  {               try {       //根据url中的namespace和actionName获取配置信息中的 ActionConfig 对象  config = configuration.getRuntimeConfiguration().getActionConfig(namespace, actionName)             if (config == null && unknownHandlerManager.hasUnknownHandlers()) {               config = unknownHandlerManager.handleUnknownAction(namespace, actionName);            }            if (config == null) {               //如果找不到,抛出异常信息            }             //默认方法            resolveMethod();                   //异常信息            //初始化ActionInvocation            invocation.init(this);        } finally {            UtilTimerStack.pop(profileKey);        }    }

    在初始化的时候,我们把package中的action标签信息都封装成ActionConfig,这里就通过namespace匹配package,通过actionName匹配action,如果url中的action在配置文件中找不到,则抛出异常。action中默认执行的excute方法也是在这里指定的

private void resolveMethod() {        // 如果url方法为空        if (StringUtils.isEmpty(this.method)) {            //取配置文件中的方法            this.method = config.getMethodName();              //如果配置文件中的方法为空            if (StringUtils.isEmpty(this.method)) {                //使用excute                this.method = "execute";            }        }    }

在proxy的准备工作,也对ActionInvocation进行了初始化。

4.4.   ActionInvocation初始化

   public void init(ActionProxy proxy) {        this.proxy = proxy;        Map
 contextMap = createContextMap();        ActionContext actionContext = ActionContext.getContext();        if (actionContext != null) {            actionContext.setActionInvocation(this);        }         //对Action类依赖注入        createAction(contextMap);                 //把action压入值顶        if (pushAction) {            stack.push(action);            contextMap.put("action", action);        }        invocationContext = new ActionContext(contextMap);        invocationContext.setName(proxy.getActionName());        // 拦截器        List
 interceptorList = new ArrayList
(proxy.getConfig().getInterceptors());        interceptors = interceptorList.iterator();    }

   4.4.1.依赖注入

   ActionInvocation初始化工作主要是对请求的action类进行依赖注入

 protected void createAction(Map
 contextMap) {            try {            action = objectFactory.buildAction(proxy.getActionName(), proxy.getNamespace(), proxy.getConfig(), contextMap);        } //异常信息        if (actionEventListener != null) {            action = actionEventListener.prepare(action, stack);        }    }

    action是一个object类型,因为actionName有不同的类型,所以这里用到了所有类的超类。通过objectFactory的buildAction对actionName对应的类进行依赖注入。objectFactory是一个接口,struct2自带的实现类为StrutsObjectFactory

代码清单:struct-default.xml配置

如果和spring整合后,struct-plugin.xml的配置为

 

    constant的配置会替换它本身的配置。所以spring和struct2整合后,调用的是StrutsSpringObjectFactory的buildAction方法。但是StrutsSpringObjectFactory没有这个方法,我们从它的父类SpringObjectFactory中查找这个方法

  @Override    public Object buildBean(String beanName, Map
 extraContext, boolean injectInternal) throws Exception {        Object o = null;        try {            //依赖注入            o = appContext.getBean(beanName);        } //异常信息        if (injectInternal) {            injectInternalBeans(o);        }        return o;    }

    getBean()就是spring容器对一个类进行依赖注入的起点。具体实现原理,我们在介绍spring的时候再作具体介绍。这里就把actionName对应的类进行了依赖注入,并把这个类压入了栈顶。接着就获取这个action里面的拦截器。

  4.4.2.拦截器

List
 interceptorList = new ArrayList
(proxy.getConfig().getInterceptors());interceptors = interceptorList.iterator();

   这个拦截器是从proxy.getConfig也就是ActionConfig中取出的,这里用到了一个新的list集合,以防止有人在改动这个集合后,在循环遍历的时候不会出现问题。

  那么这个拦截器包含哪些拦截器呢?大家还记得在初始化的时候有个rebuildRuntimeConfiguration()方法,对packageconfig里面的值设置了初始值,包括ActionConfig,寻寻觅觅,我们找到rebuildRuntimeConfiguration()方法buildFullActionConfig()这个方法,其中一小段代码,设置了这个ActionConfig的初始值

   //baseConfig.getInterceptors()先判断action中是否有拦截器   List
 interceptors = new ArrayList
(baseConfig.getInterceptors());       //如果没有,使用默认拦截器        if (interceptors.size() <= 0) {        //使用package中的默认拦截器栈,当前package没有,去父package中找      String defaultInterceptorRefName = packageContext.getFullDefaultInterceptorRef();            if (defaultInterceptorRefName != null) {                //如果找到默认拦截器栈,则把栈中的拦截器放到interceptors集合中                interceptors.addAll(InterceptorBuilder.constructInterceptorReference(new PackageConfig.Builder(packageContext), defaultInterceptorRefName,                        new LinkedHashMap
(), packageContext.getLocation(), objectFactory));            }        }        //如果有,直接使用action中的拦截器     return new ActionConfig.Builder(baseConfig)            .addParams(params)            .addResultConfigs(results)            .defaultClassName(packageContext.getDefaultCla***ef())  // fill in default if non class has been provided            .interceptors(interceptors)  //actionConfig中添加拦截器             .addExceptionMappings(packageContext.getAllExceptionMappingConfigs())            .build();

  从这里可以看出,如果action中配有拦截器,则默认的拦截器栈中的拦截器是不生效的,因此在自定义拦截器之后,需要加上默认拦截器栈中的拦截器

      

五、执行action请求

           if (mapping.getResult() != null) {                Result result = mapping.getResult();                 //执行结果                result.execute(proxy.getInvocation());            } else {               //执行action请求                proxy.execute();            }

    下篇博文详解。

六、总结

    本篇博文,介绍了执行action请求前的一些操作:比如把http参数封装到一个map中;创建一个值栈对象;找到配置信息中的actionConfig对象;并创建两个执行这个action请求的对象:actionProxy和actionInvocation,在创建这连个对象过程中,对执行这个action请求的类进行了依赖注入并调出了相关的拦截器。