本文共 20874 字,大约阅读时间需要 69 分钟。
我们接着讲解SpringMVC处理静态资源的方法,这一篇要讲解的是
配置处理静态资源的原理
不多说废话了,直接来到DefaultServletHandlerBeanDefinitionParser的parse方法
@Override public BeanDefinition parse(Element element, ParserContext parserContext) { Object source = parserContext.extractSource(element); String defaultServletName = element.getAttribute("default-servlet-name"); RootBeanDefinition defaultServletHandlerDef = new RootBeanDefinition(DefaultServletHttpRequestHandler.class); defaultServletHandlerDef.setSource(source); defaultServletHandlerDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); if (StringUtils.hasText(defaultServletName)) { defaultServletHandlerDef.getPropertyValues().add("defaultServletName", defaultServletName); } String defaultServletHandlerName = parserContext.getReaderContext().generateBeanName(defaultServletHandlerDef); parserContext.getRegistry().registerBeanDefinition(defaultServletHandlerName, defaultServletHandlerDef); parserContext.registerComponent(new BeanComponentDefinition(defaultServletHandlerDef, defaultServletHandlerName)); MapurlMap = new ManagedMap (); urlMap.put("/**", defaultServletHandlerName); RootBeanDefinition handlerMappingDef = new RootBeanDefinition(SimpleUrlHandlerMapping.class); handlerMappingDef.setSource(source); handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); handlerMappingDef.getPropertyValues().add("urlMap", urlMap); String handlerMappingBeanName = parserContext.getReaderContext().generateBeanName(handlerMappingDef); parserContext.getRegistry().registerBeanDefinition(handlerMappingBeanName, handlerMappingDef); parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, handlerMappingBeanName)); // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off" MvcNamespaceUtils.registerDefaultComponents(parserContext, source); return null; }
1.注册了一个DefaultServletHttpRequestHandler实例,beanName为defaultServletHandlerName。
2.注册了一个SimpleUrlHandlerMapping实例,并且注册了属性urlMap。
为urlMap添加了一个元素,key为”/**”,value为defaultServletHandlerName。
注意,这里没有指定SimpleUrlHandlerMapping的order,所以是默认的Integer.MAX,也就是优先级最低的。
3.注册默认的Mapping 以及Adapter等。
上一篇已经说过mvc:resources注册的SimpleUrlHandlerMapping实例的order为Integer.MAX-1,也就是说当这两种处理静态资源的方式都使用了的时候,会优先使用mvc:resources注册的。
那我们接下来就看看使用mvc:default-servlet-handler到底是如何处理静态资源请求的吧。
首先getHandler得到的是上述defaultServletHandlerName对应的Bean,DefaultServletHttpRequestHandler实例。
而ha.supports(handler);得到的HandlerAdpater肯定是
HttpRequestHandlerAdapter,这个之前都讲过。
那么走到
ha.handle(processedRequest, response, mappedHandler.getHandler());
之后的具体执行也是
@Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; }
其实都和mvc:resources的步骤一样,就不细说了,只是之类的handler不一样,这时的Handler是
DefaultServletHttpRequestHandler。所有的处理都在他的handleRequest方法中。
handleRequest(request, response)
@Override public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName); if (rd == null) { throw new IllegalStateException("A RequestDispatcher could not be located for the default servlet '" + this.defaultServletName +"'"); } rd.forward(request, response); }
看到这段代码,是不是又回到了刚开始学习Servlet的时候呢,真的有好久好久没看到forward(requset,response);方法了,是时候来回顾一下了。
我们先看看这段代码做了什么。
1.用defaultServletName得到相应的RequestDispatcher实现类。
2.调用RequestDispatcher的forward来处理请求。
我们这里的Web容器是Tomcat,对应的defaultServletName是default。至于这个defaullt的配置,在../lib/conf/web.xml
default org.apache.catalina.servlets.DefaultServlet debug 0 listings false 1
我们在这清楚的看到他是初始化时就要启动的一个servlet,而且知道了对应的类是org.apache.catalina.servlets.DefaultServlet。
接下来就要看这个方法的具体执行了。
RequestDispatcher rd = this.servletContext.getNamedDispatcher(this.defaultServletName);
这就需要回到我们的Tomcat源码中讲解了,Tomcat中serlvetContext的实现是ApplciationContext和ApplicationContextFacade,虽然我们给到用户的是后者,但是后者就是对前者的包装,实际的方法的实现都在ApplciationContext中,我们来看看这个类中的getNamedDispatcher方法
getNamedDispatcher(String name)
@Override public RequestDispatcher getNamedDispatcher(String name) { // Validate the name argument if (name == null) return (null); // Create and return a corresponding request dispatcher Wrapper wrapper = (Wrapper) context.findChild(name); if (wrapper == null) return (null); return new ApplicationDispatcher(wrapper, null, null, null, null, name); }
在我们已经分析过Tomcat源码后,对这个方法就不难理解了。
1.通过name获得对应Wrapper实例(对Servlet实现类的封装,这里既是对DefaultServlet的封装),所有的Servlet类都被当做Web容器的子容器。
2.返回一个ApplicationDispatcher实例,该实例封装了Wrapper实例。
所以最终得到的是一个ApplicationDispatcher实例。
那么接下来就是调用这个实例的forward方法了。
@Override public void forward(ServletRequest request, ServletResponse response) throws ServletException, IOException { if (Globals.IS_SECURITY_ENABLED) { try { PrivilegedForward dp = new PrivilegedForward(request,response); AccessController.doPrivileged(dp); } catch (PrivilegedActionException pe) { Exception e = pe.getException(); if (e instanceof ServletException) throw (ServletException) e; throw (IOException) e; } } else { doForward(request,response); } }
我们主要分析doForward方法
private void doForward(ServletRequest request, ServletResponse response) throws ServletException, IOException { // Reset any output that has been buffered, but keep headers/cookies if (response.isCommitted()) { throw new IllegalStateException (sm.getString("applicationDispatcher.forward.ise")); } try { response.resetBuffer(); } catch (IllegalStateException e) { throw e; } // Set up to handle the specified request and response State state = new State(request, response, false); if (WRAP_SAME_OBJECT) { // Check SRV.8.2 / SRV.14.2.5.1 compliance checkSameObjects(request, response); } wrapResponse(state); // Handle an HTTP named dispatcher forward if ((servletPath == null) && (pathInfo == null)) { ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state); HttpServletRequest hrequest = state.hrequest; wrequest.setRequestURI(hrequest.getRequestURI()); wrequest.setContextPath(hrequest.getContextPath()); wrequest.setServletPath(hrequest.getServletPath()); wrequest.setPathInfo(hrequest.getPathInfo()); wrequest.setQueryString(hrequest.getQueryString()); processRequest(request,response,state); } // Handle an HTTP path-based forward else { ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state); String contextPath = context.getPath(); HttpServletRequest hrequest = state.hrequest; if (hrequest.getAttribute( RequestDispatcher.FORWARD_REQUEST_URI) == null) { wrequest.setAttribute(RequestDispatcher.FORWARD_REQUEST_URI, hrequest.getRequestURI()); wrequest.setAttribute(RequestDispatcher.FORWARD_CONTEXT_PATH, hrequest.getContextPath()); wrequest.setAttribute(RequestDispatcher.FORWARD_SERVLET_PATH, hrequest.getServletPath()); wrequest.setAttribute(RequestDispatcher.FORWARD_PATH_INFO, hrequest.getPathInfo()); wrequest.setAttribute(RequestDispatcher.FORWARD_QUERY_STRING, hrequest.getQueryString()); } wrequest.setContextPath(contextPath); wrequest.setRequestURI(requestURI); wrequest.setServletPath(servletPath); wrequest.setPathInfo(pathInfo); if (queryString != null) { wrequest.setQueryString(queryString); wrequest.setQueryParams(queryString); } processRequest(request,response,state); } if (request.isAsyncStarted()) { // An async request was started during the forward, don't close the // response as it may be written to during the async handling return; } if (response instanceof ResponseFacade) { ((ResponseFacade) response).finish(); } else { // Close anyway try { PrintWriter writer = response.getWriter(); writer.close(); } catch (IllegalStateException e) { try { ServletOutputStream stream = response.getOutputStream(); stream.close(); } catch (IllegalStateException f) { // Ignore } catch (IOException f) { // Ignore } } catch (IOException e) { // Ignore } } }
这个方法很长,主要的实现其实就是处理请求,有兴趣的可以看看源码。所以最后就是使用DefaultServlet来处理静态资源的请求,具体处理过程有兴趣的可以看看源码。
其实上面对RequestDispatcher实例已经其forward方法的分析看下来,都没有出现我们在学习forward方法提到的这个方法有请求转发的贡功能,一直都是用这个Servlet实例来处理请求呀,并没有出现第二个Servlet呀?
这是因为我们是使用getNamedDispatcher来得到RequestDispatcher实例的,就是通过名字来找到对应的Servlet,然后使用这个servlet来处理请求,确实没有转发给第二个Servlet处理。
而我们常说的具有请求转发功能的RequestDispatcher是由getRequestDispatcher来得到的。
通常表现为:
//获取请求转发器对象,该转发器的指向通过getRequestDisPatcher()的参数设置 RequestDispatcher requestDispatcher =request.getRequestDispatcher("资源的URL"); //调用forward()方法,转发请求 requestDispatcher.forward(request,response);
我们发现这里是调用Request对象的getRequestDispatcher方法,而实际上内部调用的还是ApplicationContext的getRequestDispatcher方法。
@Override public RequestDispatcher getRequestDispatcher(final String path) { // Validate the path argument if (path == null) { return (null); } if (!path.startsWith("/")) { throw new IllegalArgumentException( sm.getString("applicationContext.requestDispatcher.iae", path)); } // Need to separate the query string and the uri. This is required for // the ApplicationDispatcher constructor. Mapping also requires the uri // without the query string. String uri; String queryString; int pos = path.indexOf('?'); if (pos >= 0) { uri = path.substring(0, pos); queryString = path.substring(pos + 1); } else { uri = path; queryString = null; } String normalizedPath = RequestUtil.normalize(uri); if (normalizedPath == null) { return (null); } if (getContext().getDispatchersUseEncodedPaths()) { // Decode String decodedPath; try { decodedPath = URLDecoder.decode(normalizedPath, "UTF-8"); } catch (UnsupportedEncodingException e) { // Impossible return null; } // Security check to catch attempts to encode /../ sequences normalizedPath = RequestUtil.normalize(decodedPath); if (!decodedPath.equals(normalizedPath)) { getContext().getLogger().warn( sm.getString("applicationContext.illegalDispatchPath", path), new IllegalArgumentException()); return null; } // URI needs to include the context path uri = URLEncoder.DEFAULT.encode(getContextPath(), "UTF-8") + uri; } else { // uri is passed to the constructor for ApplicationDispatcher and is // ultimately used as the value for getRequestURI() which returns // encoded values. Therefore, since the value passed in for path // was decoded, encode uri here. uri = URLEncoder.DEFAULT.encode(getContextPath() + uri, "UTF-8"); } pos = normalizedPath.length(); // Use the thread local URI and mapping data DispatchData dd = dispatchData.get(); if (dd == null) { dd = new DispatchData(); dispatchData.set(dd); } MessageBytes uriMB = dd.uriMB; uriMB.recycle(); // Use the thread local mapping data MappingData mappingData = dd.mappingData; // Map the URI CharChunk uriCC = uriMB.getCharChunk(); try { uriCC.append(context.getPath(), 0, context.getPath().length()); /* * Ignore any trailing path params (separated by ';') for mapping * purposes */ int semicolon = normalizedPath.indexOf(';'); if (pos >= 0 && semicolon > pos) { semicolon = -1; } uriCC.append(normalizedPath, 0, semicolon > 0 ? semicolon : pos); context.getMapper().map(uriMB, mappingData); if (mappingData.wrapper == null) { return (null); } /* * Append any trailing path params (separated by ';') that were * ignored for mapping purposes, so that they're reflected in the * RequestDispatcher's requestURI */ if (semicolon > 0) { uriCC.append(normalizedPath, semicolon, pos - semicolon); } } catch (Exception e) { // Should never happen log(sm.getString("applicationContext.mapping.error"), e); return (null); } Wrapper wrapper = (Wrapper) mappingData.wrapper; String wrapperPath = mappingData.wrapperPath.toString(); String pathInfo = mappingData.pathInfo.toString(); mappingData.recycle(); // Construct a RequestDispatcher to process this request return new ApplicationDispatcher(wrapper, uri, wrapperPath, pathInfo, queryString, null); }
这个方法也是巨长,我们简单说一下他做了什么:
1.通过用户(程序员)指定的path,我们先查看path是否带有参数(即是否有? ,当使用get方式请求时,参数会用?a=b&c=d形式呈现),如果有参数,则将参数放入到queryString属性中。
2.通过调用context.getMapper().map(uriMB, mappingData);得到我们要转发的Servlet(Servlet被封装成Warpper,Warpper封装在了MappingData中),具体怎么找的,我们在讲解Tomcat的时候说过。
3.将我们得到warpper实例,rui,queryString等注入到ApplicationDispatcher实例中,然后调用该实例的forward方法处理请求,就实现了将请求转发到另一个Servlet中处理的效果。
既然我们已经讲了在服务器端进行转发的forward方法,那我们刚好讲下在客户机上跳转的sendRedirect方法,一般以如下形式使用。
sendRedirect
//请求重定向到另外的资源 response.sendRedirect("资源的URL");
我们使用的Web容器是Tomcat,所以我们就找到org.apache.catalina.connector.Response的sendRedirect方法
@Override public void sendRedirect(String location) throws IOException { sendRedirect(location, SC_FOUND); } /** * Internal method that allows a redirect to be sent with a status other * than {@link HttpServletResponse#SC_FOUND} (302). No attempt is made to * validate the status code. */ public void sendRedirect(String location, int status) throws IOException { if (isCommitted()) { throw new IllegalStateException(sm.getString("coyoteResponse.sendRedirect.ise")); } // Ignore any call from an included servlet if (included) { return; } // Clear any data content that has been buffered resetBuffer(true); // Generate a temporary redirect to the specified location try { String locationUri; // Relative redirects require HTTP/1.1 if (getRequest().getCoyoteRequest().getSupportsRelativeRedirects() && getContext().getUseRelativeRedirects()) { locationUri = location; } else { locationUri = toAbsolute(location); } setStatus(status); setHeader("Location", locationUri); if (getContext().getSendRedirectBody()) { PrintWriter writer = getWriter(); writer.print(sm.getString("coyoteResponse.sendRedirect.note", RequestUtil.filter(locationUri))); flushBuffer(); } } catch (IllegalArgumentException e) { log.warn(sm.getString("response.sendRedirectFail", location), e); setStatus(SC_NOT_FOUND); } // Cause the response to be finished (from the application perspective) setSuspended(true); }
方法很好理解,就是重新封装了一个请求,给出了请求行的状态为302(302为重定向,我们还是需要多熟悉状态码滴)并且使用了Location这个请求头,将给定的url放入这个请求头属性中,当浏览器接受到请求头信息中的 Location: xxxx 后,就会自动跳转到 xxxx 指向的URL地址,这个跳转只有浏览器知道,所以使用了这个跳转这后,浏览器地址栏的url是会改变的,变成了我指定的url,而使用forward是不会改变的,因为他只是把请求转发给另一个servlet来处理。
include
既然都说了forward方法和sendRedirect方法了,那就再说下include方法吧。
使用方式:
request.getRequestDispatcher("jsp2.jsp").include(request, response);
功能是将RequestDispatcher对象封装的资源内容作为当前响应内容的一部分包含进来,从而实现可编程的服务器端包含功能。
这个方法的实现依旧是在ApplicationDispatcher中
@Override public void include(ServletRequest request, ServletResponse response) throws ServletException, IOException { if (Globals.IS_SECURITY_ENABLED) { try { PrivilegedInclude dp = new PrivilegedInclude(request,response); AccessController.doPrivileged(dp); } catch (PrivilegedActionException pe) { Exception e = pe.getException(); if (e instanceof ServletException) throw (ServletException) e; throw (IOException) e; } } else { doInclude(request, response); } } private void doInclude(ServletRequest request, ServletResponse response) throws ServletException, IOException { // Set up to handle the specified request and response State state = new State(request, response, true); if (WRAP_SAME_OBJECT) { // Check SRV.8.2 / SRV.14.2.5.1 compliance checkSameObjects(request, response); } // Create a wrapped response to use for this request wrapResponse(state); // Handle an HTTP named dispatcher include if (name != null) { ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state); wrequest.setAttribute(Globals.NAMED_DISPATCHER_ATTR, name); if (servletPath != null) wrequest.setServletPath(servletPath); wrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.INCLUDE); wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath()); invoke(state.outerRequest, state.outerResponse, state); } // Handle an HTTP path based include else { ApplicationHttpRequest wrequest = (ApplicationHttpRequest) wrapRequest(state); String contextPath = context.getPath(); if (requestURI != null) wrequest.setAttribute(RequestDispatcher.INCLUDE_REQUEST_URI, requestURI); if (contextPath != null) wrequest.setAttribute(RequestDispatcher.INCLUDE_CONTEXT_PATH, contextPath); if (servletPath != null) wrequest.setAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH, servletPath); if (pathInfo != null) wrequest.setAttribute(RequestDispatcher.INCLUDE_PATH_INFO, pathInfo); if (queryString != null) { wrequest.setAttribute(RequestDispatcher.INCLUDE_QUERY_STRING, queryString); wrequest.setQueryParams(queryString); } wrequest.setAttribute(Globals.DISPATCHER_TYPE_ATTR, DispatcherType.INCLUDE); wrequest.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR, getCombinedPath()); invoke(state.outerRequest, state.outerResponse, state); } }
咋一看doInclude方法好像跟forward里的doForward方法没差呀,确实很多地方都相同,但是他相较而言多了一个 invoke(state.outerRequest, state.outerResponse, state);方法
这个方法就是include的核心了,这个方法完成了是将RequestDispatcher对象封装的资源内容作为当前响应内容的一部分包含进来的功能,有兴趣的可以看看源码具体如何实现。
到这里,对SpringMVC配置文件的讲解就先告一段落了,接下来就要讲解HandlerMethod到底是如何处理请求等一系列问题了,我们只是解决了SpringMVC的一小部分问题,后面的路还很长呀。
转载地址:http://ydmvb.baihongyu.com/