struts2请求处理过程源代码分析(3)
2013-03-14 12:53 阅读(181)

接着上文:


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

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;
        ServletContext servletContext = getServletContext();

        String timerKey = "FilterDispatcher_doFilter: ";
        try {

            // FIXME: this should be refactored better to not duplicate work with the action invocation
            ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
            ActionContext ctx = new ActionContext(stack.getContext());
            ActionContext.setContext(ctx);

            UtilTimerStack.push(timerKey);
            request = prepareDispatcherAndWrapRequest(request, response);
            ActionMapping mapping;
            try {
                mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());
            } catch (Exception ex) {
                log.error("error getting ActionMapping", ex);
                dispatcher.sendError(request, response, servletContext, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex);
                return;
            }

            if (mapping == null) {
                // there is no action in this request, should we look for a static resource?
                String resourcePath = RequestUtils.getServletPath(request);

                if ("".equals(resourcePath) && null != request.getPathInfo()) {
                    resourcePath = request.getPathInfo();
                }

                if (staticResourceLoader.canHandle(resourcePath)) {
                    staticResourceLoader.findStaticResource(resourcePath, request, response);
                } else {
                    // this is a normal request, let it pass through
                    chain.doFilter(request, response);
                }
                // The framework did its job here
                return;
            }

            dispatcher.serviceAction(request, response, servletContext, mapping);

        } finally {
            try {
                ActionContextCleanUp.cleanUp(req);
            } finally {
                UtilTimerStack.pop(timerKey);
            }
        }
    }

request = prepareDispatcherAndWrapRequest(request, response)句。顾名思义,准备或配备Dispatcher以及封装request。F5进入:


protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {

        Dispatcher du = Dispatcher.getInstance();

        // Prepare and wrap the request if the cleanup filter hasn't already, cleanup filter should be
        // configured first before struts2 dispatcher filter, hence when its cleanup filter's turn,
        // static instance of Dispatcher should be null.
        if (du == null) {

            Dispatcher.setInstance(dispatcher);

            // prepare the request no matter what - this ensures that the proper character encoding
            // is used before invoking the mapper (see WW-9127)
            dispatcher.prepare(request, response);
        } else {
            dispatcher = du;
        }

        try {
            // Wrap request first, just in case it is multipart/form-data
            // parameters might not be accessible through before encoding (ww-1278)
            request = dispatcher.wrapRequest(request, getServletContext());
        } catch (IOException e) {
            String message = "Could not wrap servlet request with MultipartRequestWrapper!";
            log.error(message, e);
            throw new ServletException(message, e);
        }

        return request;
    }
第1句,通过Dispatcher的静态方法getInstance()获得Dispatcher实例。进入getInstance()方法看到实际是访问Dispatcher的内部静态属性instance,该静态属性是ThreadLocal类型,即线程局部变量。所以getInstance()方法的功能是从当前线程的局部变量中获得Dispatcher实例,如果没有该实例则通过Dispatcher.setInstance(dispatcher)将过滤器初始化过程中产生的dispatcher存入当前线程局部变量中,之后调用dispatcher.prepare(request, response)设置语言环境及编码。dispatcher.wrapRequest(request, getServletContext())句用于封装request对象,然后返回封装后的对象,F5进入dispatcher.wrapRequest():


public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
        // don't wrap more than once
        if (request instanceof StrutsRequestWrapper) {
            return request;
        }

        String content_type = request.getContentType();
        if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
            MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);
            request = new MultiPartRequestWrapper(multi, request, getSaveDir(servletContext));
        } else {
            request = new StrutsRequestWrapper(request);
        }

        return request;
    }
此时的方法参数request是web服务器(如tomcat、resin)创建的,wrapRequest()方法的功能就是将request封装到struts2自定义的请求对象中。目前这种请求对象有俩个:一个是StrutsRequestWrapper,另一个是MultiPartRequestWrapper。第1句if (request instanceof StrutsRequestWrapper) 用于判断request是否已经被封装进StrutsRequestWrapper,如果是则直接放回。如果不是,则首先获得请求的数据类型content_type。当请求类型是"multipart/form-data"时,先从容器中获得类型为MultiPartRequest.class的bean,然后连同request一起被封装进MultiPartRequestWrapper中。当请求类型不是"multipart/form-data"时,则将request封装成StrutsRequestWrapper类型。最后返回封装后的request。下面来分别分析下这俩个封装类,进入StrutsRequestWrapper定义源码:


public class StrutsRequestWrapper extends HttpServletRequestWrapper {

    /**
     * The constructor
     * @param req The request
     */
    public StrutsRequestWrapper(HttpServletRequest req) {
        super(req);
    }

    /**
     * Gets the object, looking in the value stack if not found
     *
     * @param s The attribute key
     */
    public Object getAttribute(String s) {
        if (s != null && s.startsWith("javax.servlet")) {
            // don't bother with the standard javax.servlet attributes, we can short-circuit this
            // see WW-953 and the forums post linked in that issue for more info
            return super.getAttribute(s);
        }

        ActionContext ctx = ActionContext.getContext();
        Object attribute = super.getAttribute(s);
        if (ctx != null) {
            if (attribute == null) {
                boolean alreadyIn = false;
                Boolean b = (Boolean) ctx.get("__requestWrapper.getAttribute");
                if (b != null) {
                    alreadyIn = b.booleanValue();
                }
    
                // note: we don't let # come through or else a request for
                // #attr.foo or #request.foo could cause an endless loop
                if (!alreadyIn && s.indexOf("#") == -1) {
                    try {
                        // If not found, then try the ValueStack
                        ctx.put("__requestWrapper.getAttribute", Boolean.TRUE);
                        ValueStack stack = ctx.getValueStack();
                        if (stack != null) {
                            attribute = stack.findValue(s);
                        }
                    } finally {
                        ctx.put("__requestWrapper.getAttribute", Boolean.FALSE);
                    }
                }
            }
        }
        return attribute;
    }
}

StrutsRequestWrapper继承自javax.servlet.http.HttpServletRequestWrapper。HttpServletRequestWrapper是由web服务器提供的一个封装request的类,该封装类又继承了javax.servlet.ServletRequestWrapper类。进入HttpServletRequestWrapper定义:


public class HttpServletRequestWrapper extends ServletRequestWrapper
  implements HttpServletRequest
{
  public HttpServletRequestWrapper(HttpServletRequest request)
  {
    super(request);
  }

  private HttpServletRequest _getHttpServletRequest() {
    return (HttpServletRequest)super.getRequest();
  }

  public String getAuthType()
  {
    return _getHttpServletRequest().getAuthType();
  }

  public Cookie[] getCookies()
  {
    return _getHttpServletRequest().getCookies();
  }

  public long getDateHeader(String name)
  {
    return _getHttpServletRequest().getDateHeader(name);
  }

  public String getHeader(String name)
  {
    return _getHttpServletRequest().getHeader(name);
  }
  .
  .
  .
  //省略
}
在它的构造方法中直接调用了super(request)即将request又传给了父类(ServletRequestWrapper),所以上文中new StrutsRequestWrapper(request)的request最终是传到了ServletRequestWrapper内部,F5看下ServletRequestWrapper定义:


public class ServletRequestWrapper
  implements ServletRequest
{
  private ServletRequest request;

  public ServletRequestWrapper(ServletRequest request)
  {
    if (request == null) {
      throw new IllegalArgumentException("Request cannot be null");
    }
    this.request = request;
  }

  public ServletRequest getRequest()
  {
    return this.request;
  }

  public void setRequest(ServletRequest request)
  {
    if (request == null) {
      throw new IllegalArgumentException("Request cannot be null");
    }
    this.request = request;
  }

  public Object getAttribute(String name)
  {
    return this.request.getAttribute(name);
  }

  public Enumeration getAttributeNames()
  {
    return this.request.getAttributeNames();
  }

  public String getCharacterEncoding()
  {
    return this.request.getCharacterEncoding();
  }

  public void setCharacterEncoding(String enc)
    throws UnsupportedEncodingException
  {
    this.request.setCharacterEncoding(enc);
  }

  public int getContentLength()
  {
    return this.request.getContentLength();
  }

  public String getContentType()
  {
    return this.request.getContentType();
  }
  .
  .
  .
  //省略
}

它的构造方法将构造方法形参赋值给内部定义的ServletRequest类型的request属性。而内部定义的所有类似于setXXX()、getXXX()的方法最终调用的都是属性request方法。如:getAttribute()、setAttribute()、getCharacterEncoding()等,都只是简单的调用了request的getAttribute()、setAttribute()、getCharacterEncoding()方法。所以调用封装类ServletRequestWrapper的方法与调用原始的request并无区别。

接着在返回StrutsRequestWrapper,把代码在粘下,如下:


public class StrutsRequestWrapper extends HttpServletRequestWrapper {

    /**
     * The constructor
     * @param req The request
     */
    public StrutsRequestWrapper(HttpServletRequest req) {
        super(req);
    }

    /**
     * Gets the object, looking in the value stack if not found
     *
     * @param s The attribute key
     */
    public Object getAttribute(String s) {
        if (s != null && s.startsWith("javax.servlet")) {
            // don't bother with the standard javax.servlet attributes, we can short-circuit this
            // see WW-953 and the forums post linked in that issue for more info
            return super.getAttribute(s);
        }

        ActionContext ctx = ActionContext.getContext();
        Object attribute = super.getAttribute(s);
        if (ctx != null) {
            if (attribute == null) {
                boolean alreadyIn = false;
                Boolean b = (Boolean) ctx.get("__requestWrapper.getAttribute");
                if (b != null) {
                    alreadyIn = b.booleanValue();
                }
    
                // note: we don't let # come through or else a request for
                // #attr.foo or #request.foo could cause an endless loop
                if (!alreadyIn && s.indexOf("#") == -1) {
                    try {
                        // If not found, then try the ValueStack
                        ctx.put("__requestWrapper.getAttribute", Boolean.TRUE);
                        ValueStack stack = ctx.getValueStack();
                        if (stack != null) {
                            attribute = stack.findValue(s);
                        }
                    } finally {
                        ctx.put("__requestWrapper.getAttribute", Boolean.FALSE);
                    }
                }
            }
        }
        return attribute;
    }
}

其只重写了getAttribute()。看下getAttribute()方法,参数s是属性名,if (s != null && s.startsWith("javax.servlet")) 判断访问的是否是以"javax.servlet"为前缀的,即是否要访问web服务器内置的属性,如:javax.servlet.include.servlet_path,如果是则调用return super.getAttribute(s)直接放回,不是则先获得ActionContext,然后掉用super.getAttribute(s)直接调用,接下来判断:因为上文中我们知道ActionContext我们已经放入到线程局部变量中,所以此时ctx必定不为空,执行if (attribute == null),即如果该属性在request中无法获得时,下面将要从值栈ValueStack中获得,大家看下我就不一句一句分析了。所以最后总结起来StrutsRequestWrapper封装类的作用是,当要调用getAttribute()方法获得request的属性值时,如果该值在request中未找到或其值为null,则会继续在值栈中访问。这样实际上就将getAttribute()方法进行了扩展,这也就是一般为什么要定义封装类的目的。


另外,ctx.get("__requestWrapper.getAttribute")句中的属性名"__requestWrapper.getAttribute"作用是实现该段代码的互斥访问,在代码段开始时调用ctx.put("__requestWrapper.getAttribute", Boolean.TRUE)对下面代码段上锁,在代码段结束后调用ctx.put("__requestWrapper.getAttribute", Boolean.FALSE)给解锁,因为if (!alreadyIn && s.indexOf("#") == -1)的限制alreadyIn 为true时是无法进入代码段的。 而未啥要互斥我也没弄明白。

接着再来分析下另个封装类MultiPartRequestWrapper,进入定义:


public class MultiPartRequestWrapper extends StrutsRequestWrapper {
    protected static final Logger LOG = LoggerFactory.getLogger(MultiPartRequestWrapper.class);

    Collection<String> errors;
    MultiPartRequest multi;

    /**
     * Process file downloads and log any errors.
     *
     * @param request Our HttpServletRequest object
     * @param saveDir Target directory for any files that we save
     * @param multiPartRequest Our MultiPartRequest object
     */
    public MultiPartRequestWrapper(MultiPartRequest multiPartRequest, HttpServletRequest request, String saveDir) {
        super(request);
        
        multi = multiPartRequest;
        try {
            multi.parse(request, saveDir);
            for (Object o : multi.getErrors()) {
                String error = (String) o;
                addError(error);
            }
        } catch (IOException e) {
            addError("Cannot parse request: "+e.toString());
        } 
    }
   .
   .
   .
   //省略
}

MultiPartRequestWrapper封装类是承自封装类StrutsRequestWrapper。它与StrutsRequestWrapper的区别只在于:在生成MultiPartRequestWrapper实例时,在构造方法内要完成文件上传处理,将表单上传的文件存放到web服务器的一个临时的存储区中。由上文知道当request的 contentType为"multipart/form-data"时该封装类会创建。我们知道如果表单(form)中要想上传文件,属性enctype一定要设成"multipart/form-data"。构造方法中有三个参数:multiPartRequest、request、saveDir。

saveDir是临时存储区的路径,一般是在web服务器的work目录下的当前应用中

multiPartRequest是struts-default.xml中配置的,如下:

<bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="struts" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="default"/>

<bean type="org.apache.struts2.dispatcher.multipart.MultiPartRequest" name="jakarta" class="org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest" scope="default" />


具体采用哪个可通过default.properties中的常量struts.multipart.parser配置的,默认是jakarta。这俩个bean就是处理上传文件的,由org.apache.struts2.dispatcher.multipart.JakartaMultiPartRequest类负责,该类的parse()方法中通过commons-fileupload.jar上传文件工具完成。