struts2一个请求的处理过程分析

 

strtus2的请求通过Filter过滤器拦截完成的,只要实现Filter接口,doFilter方法进行过滤,struts2的过滤器定义

在StrutsPrepareAndExecuteFilter

 

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

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        try {
            prepare.setEncodingAndLocale(request, response);
            prepare.createActionContext(request, response);
            prepare.assignDispatcherToThread();
			if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
				chain.doFilter(request, response);
			} else {
				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.setEncodingAndLocale(request, response);

请求前的预处理,进行编码设置和国际化设置。

进入代码详细可以看到详细处理是Dispatcher的预处理方法prepare

我们知道,Dispatcher的初始化init在前面已经说过,init完成后就是prepare,当

预处理完成就是处理请求,以后在说

 

public void prepare(HttpServletRequest request, HttpServletResponse response) {
        String encoding = null;
        if (defaultEncoding != null) {
            encoding = defaultEncoding;
        }
        // check for Ajax request to use UTF-8 encoding strictly http://www.w3.org/TR/XMLHttpRequest/#the-send-method
        if ("XMLHttpRequest".equals(request.getHeader("X-Requested-With"))) {
            encoding = "utf-8";
        }

        Locale locale = null;
        if (defaultLocale != null) {
            locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
        }

        if (encoding != null) {
            applyEncoding(request, encoding);
        }

        if (locale != null) {
            response.setLocale(locale);
        }

        if (paramsWorkaroundEnabled) {
            request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
        }
    }

 

如果没有设置,使用默认的编码defaultEncoding,如果是 ajax请求,则使用utf-8

然后是Locale的设置,进入Locale的设置可以看到

通过文件名和后缀组成,比如xxxxx_en.properties

 

public static Locale localeFromString(String localeStr, Locale defaultLocale) {
        if ((localeStr == null) || (localeStr.trim().length() == 0) || ("_".equals(localeStr))) {
            if (defaultLocale != null) {
                return defaultLocale;
            }
            return Locale.getDefault();
        }

        int index = localeStr.indexOf('_');
        if (index < 0) {
            return new Locale(localeStr);
        }

        String language = localeStr.substring(0, index);
        if (index == localeStr.length()) {
            return new Locale(language);
        }

        localeStr = localeStr.substring(index + 1);
        index = localeStr.indexOf('_');
        if (index < 0) {
            return new Locale(language, localeStr);
        }

        String country = localeStr.substring(0, index);
        if (index == localeStr.length()) {
            return new Locale(language, country);
        }

        localeStr = localeStr.substring(index + 1);
        return new Locale(language, country, localeStr);
    }

 

设置完后,然后放入response中

 

设置完成编码和国际化后,然后创建ActionContext上下文

 

ActionContext(com.opensymphony.xwork.ActionContext)是Action执行时的上下文,上下文可以看作是一个容器(其实我们这里的容器就是一个Map而已),它存放放的是Action在执行时需要用到的对象,比如:在使用WebWork时,我们的上下文放有请求的参数(Parameter)、会话(Session)、Servlet上下文(ServletContext)、本地化(Locale)信息等。

在每次执行Action之前都会创建新的ActionContext,ActionContext是线程安全的,也就是说在同一个线程里ActionContext里的属性是唯一的,这样我的Action就可以在多线程中使用。

一 般情况,我们的ActionContext都是通过:ActionContext context = (ActionContext) actionContext.get();来获取的。我们再来看看这里的actionContext对象的创建:static ThreadLocal actionContext = new ActionContextThreadLocal();,ActionContextThreadLocal是实现ThreadLocal的一个内部类。ThreadLocal可以命名为“线程局部变量”,它为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。这样,我们ActionContext里的属性只会在对应的当前请求线程中可见,从而保证它是线程安全的。

下面我们看看怎么通过ActionContext取得我们的HttpSession:

Map session = ActionContext.getContext().getSession();

原来我们取得的session却是Map类型的对象,这是为什么?原来,我们的WebWork框架将与Web相关的很多对象重新进行了包装,比如这里就将 HttpSession对象重新包装成了一个Map对象,供我们的Action使用,而不用直接和底层的HttpSession打交道。也正是框架的包装,让我们的Actoion可以完全的和Web层解藕。

ActionContext中保存的数据能够从请求对象中得到,这让人太不可思议了。其中的奥妙就在于Struts 2中的org.apache.struts2.dispatcher.StrutsRequestWrapper类,这个类是HttpServletRequest的包装类,它重写了getAttribute()方法(在页面中获取request对象的属性就要调用这个方法),在这个方法中,它首先在请求对象中查找属性,如果没有找到(如果你在ActionContext中保存数据,当然就找不到了),则到ActionContext中去查找。这就是为什么在ActionContext中保存的数据能够从请求对象中得到的原因。

除了利用ActionContext来获取request、session和application对象这种方式外,Action类还可以实现某些特定的接口,让Struts 2框架在运行时向Action实例注入request、session和application对象。与之对应的三个接口和它们的方法如下所示:

public class LoginAction2 implements Action, RequestAware, SessionAware, ApplicationAware

下面进入源代码深入了解

 

public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) {
        ActionContext ctx;
        Integer counter = 1;
        Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER);
        if (oldCounter != null) {
            counter = oldCounter + 1;
        }
        
        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.setContext(ctx);
        return ctx;
    }

 

进入actioncontext这个类中可以看到,actionContext是线程安全的,

static ThreadLocal actionContext = new ThreadLocal();

 

ThreadLoca又是什么呢?

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。

另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

如果ThreadLocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题。

ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

当然如果要把本来线程共享的对象通过ThreadLocal.set()放到线程中也可以,可以实现避免参数传递的访问方式,但是要注意get()到的是那同一个共享对象,并发访问问题要靠其他手段来解决。但一般来说线程共享的对象通过设置为某类的静态变量就可以实现方便的访问了,似乎没必要放到线程中。

ThreadLocal的应用场合,我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

回到代码中继续看

当创建并new一个actioncontext后,放入ActionContext中了。

同时request中也设置了actioncontext的变量,目的是为了clean的时候清理和下次请求的调用及判断

request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
        ActionContext.setContext(ctx);

 

这样actioncontext就创建完成了。

当然,actionContext第一次创建的时候一定是空的,所以如果为空,通过ValueStackFactory创建ValueStack

然后通过ValueStack创建

ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
            stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
            ctx = new ActionContext(stack.getContext());

 

 

进入代码详细观察

 

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

 

最关键的在这句话

stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));

详细的实现如下

public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
            ActionMapping mapping, ServletContext context) {

        // request map wrapping the http request objects
        Map requestMap = new RequestMap(request);

        // parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately
        Map params = new HashMap(request.getParameterMap());

        // session map wrapping the http session
        Map session = new SessionMap(request);

        // application map wrapping the ServletContext
        Map application = new ApplicationMap(context);

        Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);

        if (mapping != null) {
            extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
        }
        return extraContext;
    }

 

又回到熟悉的Dispatcher类,真正的ActionContext 通过dispatcher创建,并且创建的方法也很简单

添加了很多map,包括requst,response,session,application等。这样sevlet和struts2就解耦了。