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

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

    ①设置编码和本地化信息

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

    ③把dispacher放到threadLoack变量中。

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

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

代码清单:actionMapping
public class ActionMapping {
    private String name;
    private String namespace;
    private String method;
    private String extension;
    private Map<String, Object> 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<String, Object> 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<String,Object> 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<String,Object> 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<String, Object> 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<String, Object> 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<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
        interceptors = interceptorList.iterator();
    }

   4.4.1.依赖注入

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

 protected void createAction(Map<String, Object> 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配置
<bean type="com.opensymphony.xwork2.ObjectFactory" name="struts" class="org.apache.struts2.impl.StrutsObjectFactory" />

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

 <bean type="com.opensymphony.xwork2.ObjectFactory" name="spring" class="org.apache.struts2.spring.StrutsSpringObjectFactory" />
<constant name="struts.objectFactory" value="spring" />

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

  @Override
    public Object buildBean(String beanName, Map<String, Object> 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<InterceptorMapping> interceptorList = new ArrayList<InterceptorMapping>(proxy.getConfig().getInterceptors());
interceptors = interceptorList.iterator();

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

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

   //baseConfig.getInterceptors()先判断action中是否有拦截器
   List<InterceptorMapping> interceptors = new ArrayList<InterceptorMapping>(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<String, String>(), packageContext.getLocation(), objectFactory));
            }
        }
        //如果有,直接使用action中的拦截器
     return new ActionConfig.Builder(baseConfig)
            .addParams(params)
            .addResultConfigs(results)
            .defaultClassName(packageContext.getDefaultClassRef())  // fill in default if non class has been provided
            .interceptors(interceptors)  //actionConfig中添加拦截器 
            .addExceptionMappings(packageContext.getAllExceptionMappingConfigs())
            .build();

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

<interceptors>
	<!-- 声明拦截器 -->
      <interceptor name="checkPrivilege"
	class="cn.thinmore.oa.utils.CheckPrivilegeInteceptor"></interceptor>

	<!-- 重新定义默认的拦截器栈 -->
	<interceptor-stack name="defaultStack">
		<interceptor-ref name="checkPrivilege"></interceptor-ref>
		<interceptor-ref name="defaultStack"></interceptor-ref>
	</interceptor-stack>
</interceptors>


五、执行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请求的类进行了依赖注入并调出了相关的拦截器。