接着上文:
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上传文件工具完成。