SpringMVC解析5-DispatcherServlet逻辑细节

时间:2023-03-09 06:26:38
SpringMVC解析5-DispatcherServlet逻辑细节

MultipartContent类型的request处理

对于请求的处理,spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换request为MultipartHttpServletRequest类型的request.

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, " +
"this typically results from an additional MultipartFilter in web.xml");
}
else {
return this.multipartResolver.resolveMultipart(request);
}
}
// If not returned before: return original request.
return request;
}

每当我们上传文件的时候就是用的这段代码的源码。

根据request信息寻找对应的handler

我们知道dispatcher获取request后会将request交给handlerMapping处理,在Spring加载的过程中,Spring会将类型为SimpleUrlHandlerMapping的实例加载到this.handlerMappings中,按照常理推断,根据request提取对应的Handler,无非就是提取当前实例中的userController,但是userController为继承自AbstractController类型实例,与HandlerExecutionChain并无任何关联,那么这一步是如何封装的呢?

protected HandlerExecutionChain getHandler(HttpServletRequest request, boolean cache) throws Exception {
return getHandler(request);
}
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}

在系统启动的时候,Spring会将所有的配置的或默认的handlerMapping类型的bean注册到this.handlerMappings变量中,所以这个函数的目的就是遍历所有的handlerMapping,并调用其getHandler方法进行封装处理。我们来看看其getHandler方法:

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根据request获取对应的handler
/*
* 函数中会首先使用getHandlerInternal方法根据request信息获取对应的handler,以我们平时的应用为例
* 此方法就是根据URL找到匹配的Controller并返回,如果没有找到对应的Controller处理器那么程序会尝试
* 去查找配置中默认的处理器。
*/
Object handler = getHandlerInternal(request);
if (handler == null) {
//如果没有request的handler则使用默认的handler
handler = getDefaultHandler();
}
//如果没有默认的handler则无法继续处理,返回null
if (handler == null) {
return null;
}
// Bean name or resolved handler?
/*
* 如果查找的handler为String类型的时候,那就意味着返回的是配置的bean名称,需要根据bean名称查找对应的bean
* 最后还要通过getHandlerExecutionChain方法对返回的Handler进行封装,以满足返回类型的匹配
*/
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
return getHandlerExecutionChain(handler, request);
}

根据request查找对应的Handler,逻辑如下:

protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
//截取用于匹配的url有效路径
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//根据路径寻找handler
Object handler = lookupHandler(lookupPath, request);
if (handler == null) {
// We need to care for the default handler directly, since we need to
// expose the PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE for it as well.
Object rawHandler = null;
if ("/".equals(lookupPath)) {
//如果请求的路径仅仅是“/”那么使用RootHandler进行处理
rawHandler = getRootHandler();
}
if (rawHandler == null) {
//无法找到handler则使用默认handler
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
//根据beanName寻找handler
String handlerName = (String) rawHandler;
rawHandler = getApplicationContext().getBean(handlerName);
}
//模板方法
validateHandler(rawHandler, request);
handler = buildPathExposingHandler(rawHandler, lookupPath, lookupPath, null);
}
}
if (handler != null && logger.isDebugEnabled()) {
logger.debug("Mapping [" + lookupPath + "] to " + handler);
}
else if (handler == null && logger.isTraceEnabled()) {
logger.trace("No handler mapping found for [" + lookupPath + "]");
}
return handler;
}
protected Object lookupHandler(String urlPath, HttpServletRequest request) throws Exception {
// Direct match?
//直接匹配情况的处理
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?
//通配符匹配的处理
List<String> matchingPatterns = new ArrayList<String>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
}
String bestPatternMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
Collections.sort(matchingPatterns, patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
}
bestPatternMatch = matchingPatterns.get(0);
}
if (bestPatternMatch != null) {
handler = this.handlerMap.get(bestPatternMatch);
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = getApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestPatternMatch, urlPath); // There might be multiple 'best patterns', let's make sure we have the correct URI template variables
// for all of them
Map<String, String> uriTemplateVariables = new LinkedHashMap<String, String>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestPatternMatch, matchingPattern) == 0) {
Map<String, String> vars = getPathMatcher().extractUriTemplateVariables(matchingPattern, urlPath);
Map<String, String> decodedVars = getUrlPathHelper().decodePathVariables(request, vars);
uriTemplateVariables.putAll(decodedVars);
}
}
if (logger.isDebugEnabled()) {
logger.debug("URI Template variables for request [" + urlPath + "] are " + uriTemplateVariables);
}
return buildPathExposingHandler(handler, bestPatternMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}

根据URL获取对应Handler的匹配规则代码实现起来虽然很长,但是并不难理解,考虑了直接匹配与通配符两种情况。其中主要提及的是buildPathExposingHandler函数,它将Handler封装成了HandlerExecutionChain类型。

protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, Map<String, String> uriTemplateVariables) {
HandlerExecutionChain chain = new HandlerExecutionChain(rawHandler);
chain.addInterceptor(new PathExposingHandlerInterceptor(bestMatchingPattern, pathWithinMapping));
if (!CollectionUtils.isEmpty(uriTemplateVariables)) {
chain.addInterceptor(new UriTemplateVariablesHandlerInterceptor(uriTemplateVariables));
}
return chain;
}

在函数中看到了通过将Handler以参数形式传入,并构建HandlerExecutionChain类型实例,加入了两个拦截器。链处理机制,是Spring中非常常用的处理方式,是AOP中的重要组成部分,可以方便地对目标对象进行扩展及拦截,这是非常优秀的设计。

加入拦截器到执行链

getHandlerExecutionChain函数最主要的目的是将配置中的对应拦截器加入到执行链中,以保证这些拦截器可以有效地作用于目标对象。

protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
chain.addInterceptors(getAdaptedInterceptors());
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (MappedInterceptor mappedInterceptor : this.mappedInterceptors) {
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
return chain;
}

没找到对应的Handler的错误处理

每一个请求都应该对应着一个Handler,因为每个请求都会在后台有相应的逻辑处理,而逻辑实现就是在handler中,所以一旦遇到没有找到handler的情况(正常情况下如果没有URL匹配的Handler,开发人员可以设置默认的Handler来处理请求,但是如果默认请求也未设置就会出现Handler为空的情况),就只能通过response向用户返回错误信息。

protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping found for HTTP request with URI [" + getRequestUri(request) +
"] in DispatcherServlet with name '" + getServletName() + "'");
}
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}

根据当前Handler寻找对应的HandlerAdapter

在WebApplicationContext初始化过程中讨论了HandlerAdapter的初始化,了解了默认情况下普通的Web请求会交给SimpleControllerHandlerAdapter处理,下面我们以SimpleControllerHandlerAdapter为例来分析获取适配器的逻辑。

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
for (HandlerAdapter ha : this.handlerAdapters) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler adapter [" + ha + "]");
}
if (ha.supports(handler)) {
return ha;
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}

通过上面的函数,对于获取适配器的逻辑无非就是遍历所有适配器来选择合适的适配器并返回他,而某个适配器是否适用于当前的Handler逻辑被封装在具体的适配器中。进一步查看SimpleControllerHandlerAdapter中的supports方法。

public boolean supports(Object handler) {
return (handler instanceof Controller);
}

SimpleControllerHandlerAdapter就是用于处理普通的Web请求的,而且对于SpringMVC来说,我们会把逻辑封装至Controller的子类中。例如我们扫描注解@Controller的时候,就是扫描了Controller的子类。

last-modified头处理

我们先了解一个概念:Last-Modified缓存机制。

(1)在客户端第一次输入URL时,服务器端会返回内容和状态码200,表示请求成功,同时会添加一个“Last-Modefied”的响应头,表示此文件在服务器上的最后更新时间,例如:“Last-Modified:Wed 14 Mar 2012 10:22:42 GMT”表示最后更新时间为(2012-03-14 10:22)。

(2)客户端第二次请此URL时,客户端会向雾浮起发送请求头“If-Modified-Since”,询问服务器该事件只有当前请求内容是否改变过,如果服务端内容没有变化,则自动返回HTTP304状态码(只要响应头,内容为空,这样就节省了网络宽带)。

Spring提供的对Last-Modified机制的支持,只需要实现LastModified接口如下实例:

public class HelloWorldLastModifiedCacheController extends AbstractController implements LastModified{
private long lastModified;
protected ModelAndView handleRequestInternal(HttpServletRequest req ,HttpServletResponse resp)throws Exception{
//点击后再次请求当前页面
resp.getWriter().write("<a href=''>this</a>");
return null;
}
public long getLastModified(HttpServletRequest request){
if(lastModified == 0L){
//第一次或逻辑有变化的时候,应该重新返回内容最新修改的时间戳
lastModified = System.currentTimeMillis();
}
return lastModified;
}
}

HelloWorldLastModifiedCacheController只需要实现LastModified接口的getLastModified方法,保证当内容发证变化时返回最新的修改时间即可。

HandlerInterceptor的处理

Servlet API定义的servlet过滤器可以在servlet处理每个Web请求的前后分别对它进行前置和后置处理。

SpringMVC允许你通过处理拦截Web请求,进行前置处理和后置处理。处理拦截是在Spring的Web应用程序上下文中配置的,因此它们可以利用各种容器特性,并引用容器中声明的任何bean.处理拦截是针对特殊的处理程序映射进行注册的,因此它只拦截通过这些处理程序映射的请求。每个拦截器都必须实现HandlerInterceptor接口,它包含三个需要你实现的回调方法:preHandler(),postHandler()和afterCompletion()。第一个和第二个方法分别是处理程序请求之前和之后被调用。第二个方法还允许返回modelandview对象,因此可以在它里面操作模型属性。最后一个方法是处理完成之后被调用的。

逻辑处理

对于逻辑处理其实是通过适配器中转调用Handler并返回视图的,对应代码:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());  

同样,还是以引导示例为基础进行逻辑处理分析,之前分析过,对于普通的web请求,Spring默认使用SimpleControllerHandlerAdapter类进行处理,方法如下:

public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}

我们的逻辑是实现在handlerRequestInternal函数中,而不是handleRequest函数,所以我们进一步分析:

public abstract class AbstractController extends WebContentGenerator implements Controller {
private boolean synchronizeOnSession = false;
public final void setSynchronizeOnSession(boolean synchronizeOnSession) {
this.synchronizeOnSession = synchronizeOnSession;
}
public final boolean isSynchronizeOnSession() {
return this.synchronizeOnSession;
}
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
checkRequest(request);
prepareResponse(response);
     //如果需要session内的同步执行 
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
            //调用用户的逻辑
return handleRequestInternal(request, response);
}
}
}
     //调用用户的逻辑
return handleRequestInternal(request, response);
}
protected abstract ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response)
throws Exception; }

异常视图的处理

有时候系统运行过程中出现异常,我们并不希望就此中断用户的服务,而是至少告知客户当前系统在处理逻辑的过程中出现了异常,甚至告知他们什么原因导致的。Spring中的异常处理机制会帮我们完成这个工作。其实,这里Spring的主要逻辑就是将逻辑引导至HandlerExceptionResolver类的resolverException方法。

proteced ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
exMv.setViewName(getDefaultViewName(request));
}
if (logger.isDebugEnabled()) {
logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
}
WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
return exMv;
}
throw ex;
}

根据视图跳转页面

无论是一个系统还是一个站点,最重要的工作都是与用户进行交互,用户操作系统后无论下发的命令成功与否都需要给用户一个反馈,以便于用户进行下一步的谱判断。所以在逻辑处理的最后一定会涉及一个页面跳转的问题。

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine locale for request and apply it to the response.
Locale locale = this.localeResolver.resolveLocale(request);
response.setLocale(locale); View view;
if (mv.isReference()) {
// We need to resolve the view name.
view = resolveViewName(mv.getViewName(), mv.getModelInternal(), locale, request);
if (view == null) {
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
} // Delegate to the View object for rendering.
if (logger.isDebugEnabled()) {
logger.debug("Rendering view [" + view + "] in DispatcherServlet with name '" + getServletName() + "'");
}
try {
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "] in DispatcherServlet with name '" +
getServletName() + "'", ex);
}
throw ex;
}
}

解析视图名称

DispatcherServlet会根据ModelAndView选择合适的视图来进行渲染,而这以功能就是在resolveViewName函数中来完成的。

protected View resolveViewName(String viewName, Map<String, Object> model, Locale locale,
HttpServletRequest request) throws Exception {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
return null;
}

我们以默认的org.Springframework.web.servlet.view.InternalResourceViewResolver为例来分析ViewResolver逻辑的解析过程,其中resolveViewName函数的实现是在其父类AbstractCachingViewResolver中完成的。

public View resolveViewName(String viewName, Locale locale) throws Exception {
if (!isCache()) {
//不存在缓存的情况下直接创建视图
return createView(viewName, locale);
}
else {
//直接从缓存中抽取
Object cacheKey = getCacheKey(viewName, locale);
View view = this.viewAccessCache.get(cacheKey);
if (view == null) {
synchronized (this.viewCreationCache) {
view = this.viewCreationCache.get(cacheKey);
if (view == null) {
// Ask the subclass to create the View object.
view = createView(viewName, locale);
if (view == null && this.cacheUnresolved) {
view = UNRESOLVED_VIEW;
}
if (view != null) {
this.viewAccessCache.put(cacheKey, view);
this.viewCreationCache.put(cacheKey, view);
if (logger.isTraceEnabled()) {
logger.trace("Cached view [" + cacheKey + "]");
}
}
}
}
}
return (view != UNRESOLVED_VIEW ? view : null);
}
}

在父类UrlBasedViewResolver中重写了createView函数。

protected View createView(String viewName, Locale locale) throws Exception {  //如果当前解析器不支持当前解析器如viewName为空等情况
if (!canHandle(viewName, locale)) {
return null;
}
//处理前缀为redirect:xx的情况
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl, isRedirectContextRelative(), isRedirectHttp10Compatible());
return applyLifecycleMethods(viewName, view);
}
//处理前缀为"forward:"的情况
if (viewName.startsWith(FORWARD_URL_PREFIX)) {
String forwardUrl = viewName.substring(FORWARD_URL_PREFIX.length());
return new InternalResourceView(forwardUrl);
}
// Else fall back to superclass implementation: calling loadView.
return super.createView(viewName, locale);
}
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);
}
protected View loadView(String viewName, Locale locale) throws Exception {
AbstractUrlBasedView view = buildView(viewName);
View result = applyLifecycleMethods(viewName, view);
return (view.checkResource(locale) ? result : null);
}
protected AbstractUrlBasedView buildView(String viewName) throws Exception {
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(getViewClass());
//添加前缀及后缀
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
//contentType
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
return view;
}

通读以上代码,我们发现对于InternalResourceViewResolver所提供的解析功能主要考虑到了几个方面的处理。

  1. 基于效率的考虑,提供了缓存的支持
  2. 提供了对redirect:xx和forward:xx前缀的支持
  3. 添加了前缀及后缀,并向View中加入了必须的属性设置

页面跳转

当通过viewName解析到对应的View后,就可以进一步地处理跳转逻辑了。

public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isTraceEnabled()) {
logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
" and static attributes " + this.staticAttributes);
}
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
prepareResponse(request, response);
renderMergedOutputModel(mergedModel, request, response);
}

对于ModelView的使用,可以将一些属性直接放入到其中,然后再页面上直接通过JSTL语法或者原始的request获取。这个一个很方便也很神奇的功能,但是实现却不复杂,无非就是把我们将要用到的属性放入到request中,以便在其他地方可以直接调用,而解析这个属性的工作就是在createMergedOutputModel函数中完成的。

protected Map<String, Object> createMergedOutputModel(Map<String, ?> model, HttpServletRequest request,
HttpServletResponse response) {
@SuppressWarnings("unchecked")
Map<String, Object> pathVars = (this.exposePathVariables ?
(Map<String, Object>) request.getAttribute(View.PATH_VARIABLES) : null);
// Consolidate static and dynamic model attributes.
int size = this.staticAttributes.size();
size += (model != null ? model.size() : 0);
size += (pathVars != null ? pathVars.size() : 0);
Map<String, Object> mergedModel = new LinkedHashMap<String, Object>(size);
mergedModel.putAll(this.staticAttributes);
if (pathVars != null) {
mergedModel.putAll(pathVars);
}
if (model != null) {
mergedModel.putAll(model);
} // Expose RequestContext?
if (this.requestContextAttribute != null) {
mergedModel.put(this.requestContextAttribute, createRequestContext(request, response, mergedModel));
} return mergedModel;
}
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Determine which request handle to expose to the RequestDispatcher.
HttpServletRequest requestToExpose = getRequestToExpose(request);
//将model中的数据以属性的方式设置到request中
exposeModelAsRequestAttributes(model, requestToExpose);
// Expose helpers as request attributes, if any.
exposeHelpers(requestToExpose);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(requestToExpose, response);
//获取关于目标资源的requestDispatcher(通常是JSP页面)
RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}
// If already included or response already committed, perform include, else forward.
if (useInclude(requestToExpose, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(requestToExpose, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
exposeForwardRequestAttributes(requestToExpose);
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(requestToExpose, response);
}
}