struct2源码解读之处理Action请求

   我们前面讨论过了struct2的初始化,我们先来回顾下

 public void init(FilterConfig filterConfig) throws ServletException {
        InitOperations init = new InitOperations();
        try {
            FilterHostConfig config = new FilterHostConfig(filterConfig);
            init.initLogging(config);
            //解析配置文件,并返回一个封装了封装配置信息的Dispacher对象
            Dispatcher dispatcher = init.initDispatcher(config);
            init.initStaticContentLoader(config, dispatcher);
           //返回一个处理action请求的对象
           prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
           //返回一个执行action请求的对象
           execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
           //黑名单列表
	   this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);

            postInit(dispatcher, filterConfig);
        } finally {
            init.cleanup();
        }
    }

    从上面可以看出,struct2的初始化其实就是封装配置文件信息到Disacher对象,然后根据这个对象实例化两个对象:一个是用来处理action请求的PrepareOperations对象;一个是用来执行action请求的

ExecuteOperations对象,当有request请求的时候,就会调用这两个对象的方法来处理和执行请求。我们知道,当客户端放送请求的时候,会执行过滤器的doFilter()方法。我们来看看,StrutsPrepareAndExecuteFilter的doFilter()方法。

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

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
         //1.处理aciton请求
        try {
            
             //设置编码和本地化信息
            prepare.setEncodingAndLocale(request, response);
            //创建actionContext上下文
            prepare.createActionContext(request, response);
            //把dispacher添加到线程
            prepare.assignDispatcherToThread();
            //判断是否在黑名单内
if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
                            //在黑名单,跳过,转到下一个过滤器
				chain.doFilter(request, response);
	} else {
	    //处理request请求
	    request = prepare.wrapRequest(request);
	  ActionMapping mapping = prepare.findActionMapping(request, response, true);
	  //如果找不到这个action
	  if (mapping == null) {
	     //执行静态文件
	  boolean handled = execute.executeStaticResourceRequest(request, response);
		if (!handled) {
		       //如果找不到这个静态文件,跳过,转到下一个过滤器
			chain.doFilter(request, response);
		}
	  } else {
	       //2.找到action就执行action请求
		execute.executeAction(request, response, mapping);
		}
	  }
        } finally {
            prepare.cleanupRequest(request);
        }
    }

      初始化为我们准备了PrepareOperations对象和ExecuteOperations对象还有Dispacher,我们下面来看看struct2是如何利用这些对象处理action请求的。这里分两步,一步是处理aciton请求,包括解析url和找到action对应的方法;另一步是执行这个action的方法。


一、处理aciton请求(PrepareOperations对象)

     处理aciton请求,都是调用PrepareOperations对象的方法来进行处理。


1.1.设置编码和国际化信息  

prepare.setEncodingAndLocale(request, response);

   在PrepareOperations.setEncodingAndLocale()方法中调用了dispatcher.prepare()方法,在这个方法中设置了编码信息和国际化信息。

public void prepare(HttpServletRequest request, HttpServletResponse response) {
        String encoding = null;
        /**
         * @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
         * public void setDefaultEncoding(String val) {
         *          defaultEncoding = val;
         *  }
         *编码为xml中STRUTS_I18N_ENCODING中设置的值,默认为utf-8
         */
    
        if (defaultEncoding != null) {
            encoding = defaultEncoding;
        }     
        Locale locale = null;
        /**
         * @Inject(value=StrutsConstants.STRUTS_LOCALE, required=false)
         *   public void setDefaultLocale(String val) {
         *   defaultLocale = val;
         *  }
         *国际化信息为STRUTS_LOCALE中设置的值,默认不设置
         */
        if (defaultLocale != null) {
        locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
        }
        if (encoding != null) {
            try {
            //设置编码
                request.setCharacterEncoding(encoding);
            } //异常信息
        }

        if (locale != null) {
        //设置国际化信息
            response.setLocale(locale);
        }
        if (paramsWorkaroundEnabled) {
            request.getParameter("foo"); 
        }
    }

   在初始化的时候,通过container.inject(this)对dispacher进行了依赖注入,defaultEncoding和defaultLocale都标有@inject注解,这两个值就在这个时候设置好了。前面解析配置文件的时候,把这些值以key-value封装到了propertyContainer对象中,因此依赖注入的时候,通过value=key找到这些值并进行赋值。


1.2.创建actionContext上下文

prepare.createActionContext(request, response);

   actionContext,顾名思义,指的是action请求的环境

 public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
        ActionContext ctx;
        //计数器,初始值为1,一次请求+1,请求执行完后保存到request的CLEANUP_RECURSION_COUNTER变量中
        Integer counter = 1;
        Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
        if (oldCounter != null) {
            counter = oldCounter + 1;
        }
        //从线程变量ThreadLocal中取出ActionContext 
        ActionContext oldContext = ActionContext.getContext();
        if (oldContext != null) {
            // detected existing context, so we are probably in a forward
        ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
        } else {
            //实例化一个值栈对象
            ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
            stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
            ctx = new ActionContext(stack.getContext());
        }
        //保存计数器的值
        request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
        //把actionContext保存到线程变量ThreadLocal中
        ActionContext.setContext(ctx);
        return ctx;
    }

   actionContext是一个Theadloacl类型,因此每个请求都是线程安全的。通过get()和set()对这个变量进行存取值。在创建actionContext的过程中,实例化了一个valueStack值栈对象。通过 dispatcher.getContainer()获得Container,然后通过Container.getInstance()取出容器中的对象,这个ValueStackFactory在strcut2-default.xml中就已配置好了。

<bean type="com.opensymphony.xwork2.util.ValueStackFactory" name="struts" class="com.opensymphony.xwork2.ognl.OgnlValueStackFactory" />

  查看OgnlValueStackFactory对象的createValueStack()方法

 public ValueStack createValueStack() {
         //实例化一个ValueStack对象 
        ValueStack stack = new OgnlValueStack(xworkConverter, compoundRootAccessor, textProvider, allowStaticMethodAccess);
        //对valuestack依赖注入
        container.inject(stack);
        //把container以一个map的形式保存到valuestack的context 属性中
        stack.getContext().put(ActionContext.CONTAINER, container);
        return stack;
    }

    值栈的context其实就是一个map,除了把container放进这个map中

dispatcher.createContextMap(request, response, null, servletContext)

   这句话也把request,response,servletContext,application,session,params也放进了context这个map中.由此看出,struct2把所有值都放进了值栈valueStack.context中,而context是actionContext的一个属性,因此这个actionContext就涵盖了struct2运行所需的值,也就搭起了struct2的运行环境。


1.3.添加到线程

 prepare.assignDispatcherToThread();

   这句话,其实是把dispacher设置到一个线程变量ThreadLoacl<dispacher>中

 private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
 public static void setInstance(Dispatcher instance) {
        Dispatcher.instance.set(instance);
    }


1.4.判断是否是黑名单

if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
    //如果设置了黑名单而且在请求在黑名单内,跳过,执行下一个过滤器
	chain.doFilter(request, response);
} else {
   //如果没设置黑名单或者是不在黑名单内,处理aciton请求
	
}

    这个excludedPatterns是一个list<Pattern>集合,是一个正则表达式经编译后的表现模式.在struct2初始化的时候,我们对这个值进行设定

this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);

    而这个黑名单,来源于我们在strcuts.xml文件中对struts.action.excludePattern的配置

public List<Pattern> buildExcludedPatternsList( Dispatcher dispatcher ) {
         //获得struts.action.excludePattern的值
        return buildExcludedPatternsList(dispatcher.getContainer().getInstance(String.class, StrutsConstants.STRUTS_ACTION_EXCLUDE_PATTERN));
    }

   dispatcher.getContainer()获得Container,然后通过Container.getInstance()取出容器中的struts.action.excludePattern,这个在我们解析*.xml的时候已封装好。然后把这个值进一步封装到list<Pattern> 中

 private List<Pattern> buildExcludedPatternsList( String patterns ) {
        if (null != patterns && patterns.trim().length() != 0) {
            List<Pattern> list = new ArrayList<Pattern>();
            //以逗号分隔
            String[] tokens = patterns.split(",");
            //循环遍历
            for ( String token : tokens ) {
                  //编译一个正规表达式,并把编译结果添加到一个list集合中
                list.add(Pattern.compile(token.trim()));
            }
            return Collections.unmodifiableList(list);
        } else {
            return null;
        }
    }

    如果设置了黑名单,把黑名单填加到一个list集合中后,PrepareOperations提供了一个isUrlExcluded()方法来对黑名单进行筛选

public boolean isUrlExcluded( HttpServletRequest request, List<Pattern> excludedPatterns ) {     //如果设置了黑名单
        if (excludedPatterns != null) {
           //获得请求url
            String uri = getUri(request);
            //循环遍历
            for ( Pattern pattern : excludedPatterns ) {
                //pattern.matcher(uri)生成一个给定命名的Matcher对象,调用该对象的matches()方法进行匹配检测,只有整个目标字符串完全匹配时才返回真值
                if (pattern.matcher(uri).matches()) {
                    return true;
                }
            }
        }
        return false;
    }


1.5.处理request请求

   如果通过了黑名单的匹配检测,则处理request请求

request = prepare.wrapRequest(request);

   我们知道,普通的request请求和文件上传时的request请求时的request请求类型是不一样的,这一步就是对不同类型的request请求,进行封装

   public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
        // 默认为StrutsRequestWrapper类型
        if (request instanceof StrutsRequestWrapper) {
            return request;
        }
        //获得request请求的类型
        String content_type = request.getContentType();
        //如果request类型不为空且为multipart/form-data类型
        if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
            MultiPartRequest mpr = null;
            //check for alternate implementations of MultiPartRequest
        Set<String> multiNames = getContainer().getInstanceNames(MultiPartRequest.class);
            if (multiNames != null) {
                for (String multiName : multiNames) {
                    if (multiName.equals(multipartHandlerName)) {
                     mpr = getContainer().getInstance(MultiPartRequest.class, multiName);
                    }
                }
            }
            if (mpr == null ) {
                mpr = getContainer().getInstance(MultiPartRequest.class);
            }
            //把request封装成MultiPartRequestWrapper对象
        request = new MultiPartRequestWrapper(mpr, request, getSaveDir(servletContext));
        } else {
           //其它的封装成StrutsRequestWrapper对象
            request = new StrutsRequestWrapper(request);
        }

        return request;
    }


1.6.处理action请求


ActionMapping mapping = prepare.findActionMapping(request, response, true);

  当前面设置好编码和黑名单都检测通过后,就开始要处理action请求了,处理action请求,其实就是

把url信息封装到一个actionMapping对象。

public ActionMapping findActionMapping(HttpServletRequest request, HttpServletResponse response, boolean forceLookup) {
        //因为封装后的ActionMapping对象,最后会保存到request的STRUTS_ACTION_MAPPING_KEY变量中,因此在封装前要先判断
        ActionMapping mapping = (ActionMapping) request.getAttribute(STRUTS_ACTION_MAPPING_KEY);
        if (mapping == null || forceLookup) {
            try {
               //获得ActionMapper对象,调用其getMapping()方法封装url信息
                mapping = dispatcher.getContainer().getInstance(ActionMapper.class).getMapping(request, dispatcher.getConfigurationManager());
                 
                if (mapping != null) {
                 //mapping值不为空时,设置到request的STRUTS_ACTION_MAPPING_KEY变量中
                    request.setAttribute(STRUTS_ACTION_MAPPING_KEY, mapping);
                }
            } //异常信息
            }
        }

        return mapping;
    }

    特别注意,ActionMapper是一个操作者,而ActionMapping是一个保存信息的对象,通过ActionMapper.getMapping()方法,获得一个ActionMapping对象

  public ActionMapping getMapping(HttpServletRequest request,
            ConfigurationManager configManager) {
        ActionMapping mapping = new ActionMapping();
        //获得uri,如url="http:\\localhost:8080\playwell\back\login.action",则uri为"playwell\back\login.action?id=1!method"
        String uri = getUri(request);
        //以分号分割
        int indexOfSemicolon = uri.indexOf(";");
        //如果有分号,全第一个分号前面的,否则的话,取原值
        uri = (indexOfSemicolon > -1) ? uri.substring(0, indexOfSemicolon) : uri;
        //删除后缀,extension默认为aciton和"",如果为空,找最后一个“.”,找不到这个点才返回原值,如果是action,判断uri是否endwith action,是的话,取“.”前面的值,然后mapping.setExtension();
        uri = dropExtension(uri, mapping);
        if (uri == null) {
            return null;
        }
        /*
         *
         *设置命名空间和action名。这里对url作逻辑判断,分离出命名空间和action名
         * mapping.setNamespace(namespace);
         * mapping.setName(name);
         *
         */
        parseNameAndNamespace(uri, mapping, configManager);
        //处理params
        handleSpecialParameters(request, mapping);

        if (mapping.getName() == null) {
            return null;
        }
        //mapping.setMethod(),处理!method
        parseActionName(mapping);

        return mapping;
    }

   这一步主要是对url进行解析工作,把url的每一个字段分割出来,并封装到actionMapping对象中,大多都是字符串操作,在这就不过多叙述了,下面附上actionMapping的部分代码以加深大家对这一步操作的理解

public class ActionMapping {

    private String name;  //记录action名
    private String namespace; //记录命名空间
    private String method; //记录!method
    private String extension; //记录后缀
    private Map<String, Object> params;
    private Result result;
    
 }

二、执行action请求

    解析完url,把url封装到actionMapping对象中后,就开始执行action请求。这里又为分actionMapping为空和actionMapping不为空两种情况。什么情况下actionMapping为空呢?我们看回actionMapping的封装过程

        if (uri == null) {
            return null;
        }
        if (mapping.getName() == null) {
            return null;
        }

    当uri地址为空或者是actionname为空时返回一个空的actionMapping对象

2.1.找不到action

    当返回一个空的actionMapping对象时,struct2会寻找静态资源,如果连静态文件都找不到,则会调过当前过滤器,去到下一个过滤器

if (mapping == null) {
            //执行静态资源
	boolean handled = execute.executeStaticResourceRequest(request, response);
	if (!handled) {
	       //跳到下一个过滤器
		chain.doFilter(request, response);
	}
}

    在这个过程中,struct2会加载哪些静态资源呢?

public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        
        String resourcePath = RequestUtils.getServletPath(request);

        if ("".equals(resourcePath) && null != request.getPathInfo()) {
            resourcePath = request.getPathInfo();
        }
          //取得StaticContentLoader对象
        StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
        if (staticResourceLoader.canHandle(resourcePath)) {
            staticResourceLoader.findStaticResource(resourcePath, request, response);
            //加载静态资源,否则的话抛出404错误。response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return true;

        } else {
            // 给跳到下一个过滤器设置标志
            return false;
        }
    }

     canHandle里面表明,以/structs或者是/static开头的url的静态资源才会被加载。

 public boolean canHandle(String resourcePath) {
        return serveStatic && (resourcePath.startsWith("/struts") || resourcePath.startsWith("/static"));
    }

    

2.2.找到action

    找到action后就开始正常执行action请求。这个在下篇博文继续探讨。


三、总结

   本篇博文探讨了strcut2是如何处理action请求的:利用PrepareOperations对象的方法,设置了编码信息和本地化信息,设置了action的上下文actionContext,并处理了黑名单信息,最后封装了url信息到 actionMapping对象,利用actionMapping对象的去执行相应的请求,如actionMapping为空时就尝试去加载静态资源,不为空就取执行相应的操作。