DispatcherServlet的逻辑处理
根据之前可以知道HttpServlet类中分别提供了相应的服务方法。他们是doDelete,doGet(),doPost(),doOptions(),doPut(),doTrace()。其根据请求的不同形式将程序引导至对应的函数进行处理。
在FrameworkServlet中
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
其实现是统一引导至processRequest中
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//记录时间,用于计算web请求的处理时间
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
try {
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
if (logger.isDebugEnabled()) {
if (failureCause != null) {
this.logger.debug("Could not complete request", failureCause);
}
else {
if (asyncManager.isConcurrentHandlingStarted()) {
logger.debug("Leaving response open for concurrent processing");
}
else {
this.logger.debug("Successfully completed request");
}
}
}
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
函数对请求的处理,把细节转移到了doService函数中实现。但是可以看到处理请求前后所做的准备与处理工作。
- 为了保证当前request线程的LocaleContext以及RequestAttributes可以在当前请求后还能恢复,提取当前线程的两个属性。
- 根据当前request创建对应的LocaleContext和RequestAttributes,并绑定到当前线程。
- 委托给doService方法进一步处理
- 请求处理结束后恢复线程到初始状态
- 请求处理结束后无论成功与否发布时间通知。(publishRequestHandledEvent)
doService
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (logger.isDebugEnabled()) {
String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
" processing " + request.getMethod() + " request for [" + getRequestUri(request) + "]");
}
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try {
doDispatch(request, response);
}
finally {
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Restore the original attribute snapshot, in case of an include.
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
doService中没有包括诸如寻找Handler并跳转页面之类的处理逻辑。但是相反的是准备工作,这些准备工作必不可少。
SPring将已经初始化的功能复制工具变量,如localeResolver,themeResolver等设置在request属性中,而这些属性会在接下来的处理中牌上用场。
doDispatch函数中,可以看到完整的处理请求处理过程。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//如果是MultipartContent类型的request则转换request为MultipartHttpServletRequest类型的request。
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
//根据request信息寻找对应的Handler
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
//没有找到对应的handler则通过response反馈错误信息
noHandlerFound(processedRequest, response);
return;
}
//根据当前handler寻找对应的HandlerAdapter
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
//真正的激活handler并返回视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//视图名称转换应用于需要添加前后缀的情况
applyDefaultViewName(processedRequest, mv);
//应用所有拦截器的postHandler方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//在这个函数中。有一个处理if(mv!=null)下,如果在Handler的实例中返回了view,那么需要做页面逻辑的处理,render(mv,processedRequest,response)他是处理页面跳转的
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
//完成处理激活触发器
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
doDispatch函数中展示了Spring请求处理所设计的主要逻辑,而我们之前设置在request中的各种辅助属性也都有派上用场。回顾一下逻辑处理的全过程。
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 if (hasMultipartException(request)) {
logger.debug("Multipart resolution previously failed for current request - " +
"skipping re-resolution for undisturbed error rendering");
}
else {
try {
return this.multipartResolver.resolveMultipart(request);
}
catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
}
else {
throw ex;
}
}
}
}
// If not returned before: return original request.
return request;
}
根据request信息寻找对于handler
Spring中最简单因素处理器配置如下
<bean id="simpleUrlMapping"
class = "org.Springframework.web.servlet.handler.SimpleUrlHandlerMapping:>
<property name="mapping">
<props>
<prop key="/userlist.html">userController</prop>
<props>
</peroperty>
</bean>
Spring加载过程中,会将类型SimpleUrlHandlerMapping的实例加载到this.handlerMappings中。按照常理推断,根据request提取对应的Handler,无非就是提取当前实例中的userControler,但是user Controller为继承自AbstractController类型实例,与HandlerExecutionChain并无任何关联。那么封装是怎么处理的。
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
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会将所有的映射类型bean注册到this.handlerMappings变量中,所以此函数的目的就是遍历所有的HandlerMapping,并调用其getHandler方法进行封装处理,以SimpleUrlHandlerMapping为例查看其getHandler方法如下
AbstractHandlerMapping.class
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//根据request获取对应的handler
Object handler = getHandlerInternal(request);
if (handler == null) {
//如果没有对应request的handler则用默认handler
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
函数会使用getHandlerInternal方法根据request信息获取对应的Handler。
SimpleUrlHandlerMapping为例,其根据Url找到匹配的Controller并返回,如果没找到则尝试去寻找配置中的默认处理器。如果查找的controller为String类型,那么意味返回的配置的bean名称,需要根据bean名称查找对应bean。最后还要通过getHandlerExecutionChain方法对返回的Handler进行封装,以保证满足返回类型的匹配。
- 根据request查找对应的Handler
AbstractUrlHandlerMapping.class
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则使用默认对应的bean
rawHandler = getDefaultHandler();
}
if (rawHandler != null) {
// Bean name or resolved handler?
if (rawHandler instanceof String) {
String handlerName = (String) rawHandler;
rawHandler = obtainApplicationContext().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;
}
/**
* Look up a handler instance for the given URL path.
* <p>Supports direct matches, e.g. a registered "/test" matches "/test",
* and various Ant-style pattern matches, e.g. a registered "/t*" matches
* both "/test" and "/team". For details, see the AntPathMatcher class.
* <p>Looks for the most exact pattern, where most exact is defined as
* the longest path pattern.
* @param urlPath the URL the bean is mapped to
* @param request current HTTP request (to expose the path within the mapping to)
* @return the associated handler instance, or {@code null} if not found
* @see #exposePathWithinMapping
* @see org.springframework.util.AntPathMatcher
*/
@Nullable
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 = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
// Pattern match?通配符
List<String> matchingPatterns = new ArrayList<>();
for (String registeredPattern : this.handlerMap.keySet()) {
if (getPathMatcher().match(registeredPattern, urlPath)) {
matchingPatterns.add(registeredPattern);
}
else if (useTrailingSlashMatch()) {
if (!registeredPattern.endsWith("/") && getPathMatcher().match(registeredPattern + "/", urlPath)) {
matchingPatterns.add(registeredPattern + "/");
}
}
}
String bestMatch = null;
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(urlPath);
if (!matchingPatterns.isEmpty()) {
matchingPatterns.sort(patternComparator);
if (logger.isDebugEnabled()) {
logger.debug("Matching patterns for request [" + urlPath + "] are " + matchingPatterns);
}
bestMatch = matchingPatterns.get(0);
}
if (bestMatch != null) {
handler = this.handlerMap.get(bestMatch);
if (handler == null) {
if (bestMatch.endsWith("/")) {
handler = this.handlerMap.get(bestMatch.substring(0, bestMatch.length() - 1));
}
if (handler == null) {
throw new IllegalStateException(
"Could not find handler for best pattern match [" + bestMatch + "]");
}
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(bestMatch, 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<>();
for (String matchingPattern : matchingPatterns) {
if (patternComparator.compare(bestMatch, 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, bestMatch, pathWithinMapping, uriTemplateVariables);
}
// No handler found...
return null;
}
根据URL获取对应Handler的匹配规则代码实现虽然很长,考虑了直接匹配和通配符两种情况。这里的buildPathExposingHandler函数,它将Handler封装成了HandlerExecutionChain类型。
protected Object buildPathExposingHandler(Object rawHandler, String bestMatchingPattern,
String pathWithinMapping, @Nullable 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));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
没有找到对应Handler的错误处理
每个请求对应着一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() + "'");
}
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
}
else {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
//也就是在这里返回404 response
}
}
根据当前Handler寻找对应的HandlerAdapter
在WebApplicationContext的初始化过程中我们讨论了HandlerAdapters的初始化。在默认情况下普通的Web请求会交给SimpleControllerHandlerAdapter去处理。
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
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逻辑被封装在具体适配器中。
public boolean supports(Object handler) {
return (handler instanceof Controller);
}
到此,SimpleContollerHandlerAdapter就是用于处理普通的Web请求的。而对于SpringMVC而言,我们把逻辑封装至Controller的子类中,如UserController就是继承自AbstractController,而AbstractController实现Controller接口
缓存处理
Last-Modified缓存机制。
- 在客户端第一次输入URL时,服务端返回内容和状态码200,表示请求成功。同时添加Last-Modified的相应头,表示此文件咋i服务器上的最后更新时间
- 客户端第二次请求该URL时,客户端会向服务器发送请求头,If-Modifed-Since,询问服务器该事件后当前请求内容是否被修改过,如果没有变化,则304状态码。(只要响应头内容空,这样就节省了网络带宽)
Spring提供对Last-Modified机制的支持,只需要实现LastModified接口。
HandlerInterceptor的处理
ServletAPI定义的servlet过滤器可以在servlet处理每个Web请求的前后分别对他进行前置处理和后置处理。
如此,Spring允许你通过拦截Web请求,进行前置和后置处理。处理拦截是在Spring的Web应用程序上下文中配置的。因此可以利用各种容器特性,并引用容器声明的任何bean。
- 处理拦截是针对特殊的处理程序映射进行注册的。因此它只拦截通过这些处理程序映射的请求。
- 每个处理拦截器必须实现HandlerInterceptor接口。
- 其接口包含了3个需要你实现的回调方法,preHandle(),postHandle(),afterHandle()。
- 一个和一二分别是在处理程序处理请求之前和之后被调用的。第二个方法还允许你返回访问的ModelAndView对象,因此可以在它里面操作模型属性。最后一个方法实在所有请求处理完成之后被调用的(如视图呈现后)。
逻辑处理
对于逻辑处理其实是通过适配器中转调用Handler并返回视图的。
mv = ha.handle(processedRequest,response,mappedHandler.getHandler());
对于普通Web请求,Spring默认SimpleControllerHandlerAdapter类进行处理。
SimpleControllerHandlerAdapter.class
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return ((Controller) handler).handleRequest(request, response);
}
AbstractController.class
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
if (HttpMethod.OPTIONS.matches(request.getMethod())) {
response.setHeader("Allow", getAllowHeader());
return null;
}
// Delegate to WebContentGenerator for checking and preparing.
checkRequest(request);
prepareResponse(response);
// Execute handleRequestInternal in synchronized block if required.
//如果需要在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);
}
异常视图的处理
系统允许过程出现异常,而不希望中断该服务,告知错误和原因。Spring异常中断机制帮我们完成了这个工作。
其实这里Spring的主要工作就是将逻辑引导至HandlerExceptionResolver类的resolveException方法。而HandlerExceptionResolver的使用,在WebApplicationContext初始化便说过了
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
@Nullable Object handler, Exception ex) throws Exception {
// Success and error responses may use different content types
request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
// Check registered HandlerExceptionResolvers...
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
exMv = resolver.resolveException(request, response, handler, ex);
if (exMv != null) {
break;
}
}
}
if (exMv != null) {
if (exMv.isEmpty()) {
request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
return null;
}
// We might still need view name translation for a plain error model...
if (!exMv.hasView()) {
String defaultViewName = getDefaultViewName(request);
if (defaultViewName != null) {
exMv.setViewName(defaultViewName);
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using resolved error view: " + exMv, ex);
}
if (logger.isDebugEnabled()) {
logger.debug("Using resolved error view: " + exMv);
}
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 != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
String viewName = mv.getViewName();
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, 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 {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
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, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
return null;
}
我们以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 {
// If this resolver is not supposed to handle the given view,
// return null to pass on to the next resolver in the chain.
//如果当前解析器不支持当前解析器如viewName为空等情况
if (!canHandle(viewName, locale)) {
return null;
}
//处理redirect:xxx的情况
// Check for special "redirect:" prefix.
if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
RedirectView view = new RedirectView(redirectUrl,
isRedirectContextRelative(), isRedirectHttp10Compatible());
String[] hosts = getRedirectHosts();
if (hosts != null) {
view.setHosts(hosts);
}
return applyLifecycleMethods(REDIRECT_URL_PREFIX, view);
}
//forward:xx
// Check for special "forward:" prefix.
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 {
Class<?> viewClass = getViewClass();
Assert.state(viewClass != null, "No view class");
AbstractUrlBasedView view = (AbstractUrlBasedView) BeanUtils.instantiateClass(viewClass);
view.setUrl(getPrefix() + viewName + getSuffix());
String contentType = getContentType();
if (contentType != null) {
view.setContentType(contentType);
}
view.setRequestContextAttribute(getRequestContextAttribute());
view.setAttributesMap(getAttributesMap());
Boolean exposePathVariables = getExposePathVariables();
if (exposePathVariables != null) {
view.setExposePathVariables(exposePathVariables);
}
Boolean exposeContextBeansAsAttributes = getExposeContextBeansAsAttributes();
if (exposeContextBeansAsAttributes != null) {
view.setExposeContextBeansAsAttributes(exposeContextBeansAsAttributes);
}
String[] exposedContextBeanNames = getExposedContextBeanNames();
if (exposedContextBeanNames != null) {
view.setExposedContextBeanNames(exposedContextBeanNames);
}
return view;
}
通过以上代码,可以发现对于InternalResourceViewResolver所提供的解析功能主要考虑到了
- 基于效率的考虑,提供了缓存的支持
- 提供了对redirectLxx和forward:xx 前缀的支持
- 添加了前缀及后缀,并向View中加入了必须的属性设置
页面跳转
当通过了viewName解析到对应的View后,就可以进一步处理跳转逻辑
public void render(@Nullable 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, getRequestToExpose(request), response);
}
对于ModelView的使用,可以将一些属性直接放入其中然后在页面直接通过JSTL语法or
原始的request获取,这是一个很方便也是很神奇的功能,无非就是把我们要用到的属性放入request中,以便在其他地方可以直接调用,而解析这些属性的工作就是在createMergedOutputModel函数中完成的。
AbstractView.class
protected Map<String, Object> createMergedOutputModel(@Nullable 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<>(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;
}
//页面处理跳转
InternalResourceView.class
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request);
// Expose helpers as request attributes, if any.
exposeHelpers(request);
// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);
// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, 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(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
}
rd.forward(request, response);
}
}
整体小结
-
web.xml先配置ContextLoaderListener-----获取param----->ServletContextListener
-
ServletContextListener 初始化WebApplicationContext实例并存至ServletContext中
-
ContextLoaderListener为辅助功能,用于创建WebApplicationContext类型实例,真正逻辑实现在DispatcherServlet
-
ContextLoader 静态代码块,读取属性文件,提取将要实现的WebApplicationContext接口实现类
-
接下来
-
DispatcherServlet ->FrameworkServlet ->HttpServletBean,ApplicationContextAware
HttpServletBean -> Httpservlet ->重写其init方法 -
inti方法->servletBean初始化->FrameworkServlet->覆盖initServletBean(对WebApplicationContext进一步初始化)—关键初始化–>initWebApplicationContext()
-
initWebApplicationContext()-----寻找or创建对应的WebApplicationContext实例 ------调用onRefresh()进行配置文件加载
-
onRefresh刷新Spring在Web功能实现的全局变量,如果没有手动配置bean--------------------- DispatcherServlet静态代码块初始化本身对应的各种init**属性
-
这一章节
-
FrameworkServlet----重写doGet() doPost() 方法------>processRequest函数-------->其中的doService初始化------->其中的doDispatch根据request寻找handler并处理页面跳转,并加入拦截器到执行链
-
细化doDispatch(根据找Handler)----->getHandler----->getHandlerInternal----->lookupHandler----->buildPathExposingHandler ----------/doDispath-----getHandler----------(配置中的对应拦截器加入到执行链)------>getHandlerExectuinChain
-
根据当前Handler寻找对应HandlerAdapter/没找到对应的Handler错误处理/HandlerInterceptor前后置处理器/逻辑处理/视图异常的处理
-
根据视图跳转页面:render—解析视图名称–>resolveViewName选择合适的视图渲染—>resolveViewName–>createView
render—页面跳转—>同过viewName解析到对于的View后进行逻辑跳转—>render------->解析属性/存放属性Map --createMergedOutputModel---->放置属性/页面跳转renderMergedOutputModel