前言
我们接着讲解SpringMVC处理静态资源的方法,这一篇要讲解的是
<mvc:default-servlet-handler/>
配置处理静态资源的原理
mvc:default-servlet-handler的解析
不多说废话了,直接来到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));
Map<String, String> urlMap = new ManagedMap<String, String>();
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来处理请求。
- 首先我们来看看是如何得到RequestDispatcher实现类的。
我们这里的Web容器是Tomcat,对应的defaultServletName是default。至于这个defaullt的配置,在../lib/conf/web.xml
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
我们在这清楚的看到他是初始化时就要启动的一个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的一小部分问题,后面的路还很长呀。