一、上传文件功能的实现:
前端JSP代码:
form 表单提交,enctype为multipart/form-data,请求方式POST
html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
pageContext.setAttribute("ctx",request.getContextPath());
%>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${ctx}/fileupload" enctype="multipart/form-data" method="post">
描述:<input type="text" name="desc"><br><br>
文件:<input type="file" name="file"><br><br>
<input type="submit" value="上传">
</form>
</body>
</html>
FileUploadController 的 fileupload方法:
java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@Controller
public class FileUploadController {
@RequestMapping("fileupload")
public String upload(MultipartFile file, HttpServletRequest request,String desc) throws IOException {
System.out.println(desc);
if (file.isEmpty()){
return "false";
}
String path = request.getServletContext().getRealPath("/WEB-INF/file");
String fileName = file.getOriginalFilename();
File filePath = new File(path,fileName);
if (!filePath.getParentFile().exists()){
filePath.getParentFile().mkdir();
}
file.transferTo(filePath);
return "success";
}
}
二、上传文件使用的组件
MultipartResolver
MultipartResolver用于处理上传请求,处理方式是将普通的request包装成MultipartHttpServletRequest,可以直接调用getFile方法来获取File,如果上传多个文件,可以调用getFileMap来处理。
java
public interface MultipartResolver {
/**
* 判断是否是上传请求
*/
boolean isMultipart(HttpServletRequest request);
/**
* 将request请求包装成MultipartHttpServletRequest
*/
MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;
/**
* 处理完成之后清理上传过程中产生的临时资源
*/
void cleanupMultipart(MultipartHttpServletRequest request);
}
类继承关系,需要实现上面三个方法:
CommonsMultipartResolver
html
<a href="https://commons.apache.org/proper/commons-fileupload">Apache Commons FileUpload</a>
依赖的一个apache的组件jar包
初始化九大内置组件的时候,没有对上传组件设置默认值,所以要提前交给spring生成。
放在SpringMVC的配置文件里,等待加载完成。
bash
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="17367648787"></property>
<property name="defaultEncoding" value="UTF-8"></property>
</bean>
三、上传文件执行流程
1.检测请求是否为上传请求,如果是则通过multipartResolver将其封装成MultipartHttpServletRequest对象
确定请求是否包含多部分内容的实用程序方法。注意:在FileUpload 1.1发布之后,这个方法将被移动到ServletFileUpload类中。不幸的是,由于此方法是静态的,因此在删除此方法之前不可能提供其替代品。
MULTIPART和enctype="multipart/form-data" 对应,证明是上传文件的请求。
this.multipartResolver.isMultipart(request)返回true,
进入
// 将 HttpServletRequest 请求封装成 MultipartHttpServletRequest 对象,解析请求里面的参数以及文件
return this.multipartResolver.resolveMultipart(request);
CommonsMultipartResolver#resolveMultipart
java
// 处理懒加载,如果为true的话,会将解析请求的操作放到DefaultMultipartHttpServletRequest的initializeMultipart方法中,只有在实际调用的时候才会被调用
// 如果值为false的话,那么会先调用parseRequest方法来处理request请求,然后将处理的结果放到DefaultMultipartHttpServletRequest
private boolean resolveLazily = false;
public MultipartHttpServletRequest resolveMultipart(final HttpServletRequest request) throws MultipartException {
Assert.notNull(request, "Request must not be null");
if (this.resolveLazily) {
return new DefaultMultipartHttpServletRequest(request) {
@Override
protected void initializeMultipart() {//
MultipartParsingResult parsingResult = parseRequest(request);
setMultipartFiles(parsingResult.getMultipartFiles());
setMultipartParameters(parsingResult.getMultipartParameters());
setMultipartParameterContentTypes(parsingResult.getMultipartParameterContentTypes());
}
};
}
else {
MultipartParsingResult parsingResult = parseRequest(request);
return new DefaultMultipartHttpServletRequest(request, parsingResult.getMultipartFiles(),
parsingResult.getMultipartParameters(), parsingResult.getMultipartParameterContentTypes());
}
}
MultipartParsingResult parsingResult = parseRequest(request);
java
/**
* 对请求进行处理,转成MultipartParsingResult对象
*
* Parse the given servlet request, resolving its multipart elements.
* @param request the request to parse
* @return the parsing result
* @throws MultipartException if multipart resolution failed.
*/
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
// 从请求中读出当前请求的编码
String encoding = determineEncoding(request);
//按照请求的编码,获取一个FileUpload对象,装载到CommonsFileUploadSupport的property属性都会被装入这个对象中
//prepareFileUpload是继承自CommonsFileUploadSupport的函数,会比较请求的编码和XML中配置的编码,如果不一样,会拒绝处理
FileUpload fileUpload = prepareFileUpload(encoding);
try {
// 对请求中的multipart文件进行具体的处理
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
return parseFileItems(fileItems, encoding);
}
catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
}
catch (FileUploadBase.FileSizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
}
catch (FileUploadException ex) {
throw new MultipartException("Failed to parse multipart servlet request", ex);
}
}
// 对请求中的multipart文件进行具体的处理
List fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
java
public List<FileItem> parseRequest(RequestContext ctx)
throws FileUploadException {
List<FileItem> items = new ArrayList<FileItem>();
boolean successful = false;
try {
FileItemIterator iter = getItemIterator(ctx);
FileItemFactory fac = getFileItemFactory();
if (fac == null) {
throw new NullPointerException("No FileItemFactory has been set.");
}
while (iter.hasNext()) {
final FileItemStream item = iter.next();
// Don't use getName() here to prevent an InvalidFileNameException.
final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
item.isFormField(), fileName);
items.add(fileItem);
try {
Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new IOFileUploadException(format("Processing of %s request failed. %s",
MULTIPART_FORM_DATA, e.getMessage()), e);
}
final FileItemHeaders fih = item.getHeaders();
fileItem.setHeaders(fih);
}
successful = true;
return items;
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new FileUploadException(e.getMessage(), e);
} finally {
if (!successful) {
for (FileItem fileItem : items) {
try {
fileItem.delete();
} catch (Exception ignored) {
// ignored TODO perhaps add to tracker delete failure list somehow?
}
}
}
}
}
获取到传递得文件
java
/**
* 对请求进行处理,转成MultipartParsingResult对象
*
* Parse the given servlet request, resolving its multipart elements.
* @param request the request to parse
* @return the parsing result
* @throws MultipartException if multipart resolution failed.
*/
protected MultipartParsingResult parseRequest(HttpServletRequest request) throws MultipartException {
// 从请求中读出当前请求的编码
String encoding = determineEncoding(request);
//按照请求的编码,获取一个FileUpload对象,装载到CommonsFileUploadSupport的property属性都会被装入这个对象中
//prepareFileUpload是继承自CommonsFileUploadSupport的函数,会比较请求的编码和XML中配置的编码,如果不一样,会拒绝处理
FileUpload fileUpload = prepareFileUpload(encoding);
try {
// 对请求中的multipart文件进行具体的处理
List<FileItem> fileItems = ((ServletFileUpload) fileUpload).parseRequest(request);
return parseFileItems(fileItems, encoding);
}
catch (FileUploadBase.SizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getSizeMax(), ex);
}
catch (FileUploadBase.FileSizeLimitExceededException ex) {
throw new MaxUploadSizeExceededException(fileUpload.getFileSizeMax(), ex);
}
catch (FileUploadException ex) {
throw new MultipartException("Failed to parse multipart servlet request", ex);
}
}
parseFileItems
java
/**
* Parse the given List of Commons FileItems into a Spring MultipartParsingResult,
* containing Spring MultipartFile instances and a Map of multipart parameter.
* @param fileItems the Commons FileItems to parse
* @param encoding the encoding to use for form fields
* @return the Spring MultipartParsingResult
* @see CommonsMultipartFile#CommonsMultipartFile(org.apache.commons.fileupload.FileItem)
*/
protected MultipartParsingResult parseFileItems(List<FileItem> fileItems, String encoding) {
// 保存上传的文件
MultiValueMap<String, MultipartFile> multipartFiles = new LinkedMultiValueMap<>();
// 保存参数
Map<String, String[]> multipartParameters = new HashMap<>();
// 保存参数的contentType
Map<String, String> multipartParameterContentTypes = new HashMap<>();
// Extract multipart files and multipart parameters.
// 将fileItems分为文件和参数两类,并设置到对应的map
for (FileItem fileItem : fileItems) {
// 如果是参数类型
if (fileItem.isFormField()) {
String value;
String partEncoding = determineEncoding(fileItem.getContentType(), encoding);
try {
value = fileItem.getString(partEncoding);
}
catch (UnsupportedEncodingException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Could not decode multipart item '" + fileItem.getFieldName() +
"' with encoding '" + partEncoding + "': using platform default");
}
value = fileItem.getString();
}
String[] curParam = multipartParameters.get(fileItem.getFieldName());
if (curParam == null) {
// simple form field
// 单个参数
multipartParameters.put(fileItem.getFieldName(), new String[] {value});
}
else {
// array of simple form fields
// 数组参数
String[] newParam = StringUtils.addStringToArray(curParam, value);
multipartParameters.put(fileItem.getFieldName(), newParam);
}
// 保存参数的contentType
multipartParameterContentTypes.put(fileItem.getFieldName(), fileItem.getContentType());
}
else {
// multipart file field
// 如果是文件类型
CommonsMultipartFile file = createMultipartFile(fileItem);
multipartFiles.add(file.getName(), file);
LogFormatUtils.traceDebug(logger, traceOn ->
"Part '" + file.getName() + "', size " + file.getSize() +
" bytes, filename='" + file.getOriginalFilename() + "'" +
(traceOn ? ", storage=" + file.getStorageDescription() : "")
);
}
}
return new MultipartParsingResult(multipartFiles, multipartParameters, multipartParameterContentTypes);
}
parseFileItems得调用堆栈:
java
parseFileItems:301, CommonsFileUploadSupport (org.springframework.web.multipart.commons)
parseRequest:168, CommonsMultipartResolver (org.springframework.web.multipart.commons)
resolveMultipart:145, CommonsMultipartResolver (org.springframework.web.multipart.commons)
checkMultipart:1286, DispatcherServlet (org.springframework.web.servlet)
doDispatch:1088, DispatcherServlet (org.springframework.web.servlet)
doService:1006, DispatcherServlet (org.springframework.web.servlet)
processRequest:1085, FrameworkServlet (org.springframework.web.servlet)
doPost:971, FrameworkServlet (org.springframework.web.servlet)
service:681, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
回到CommonsMultipartResolver#resolveMultipart
回到DispatcherServlet得doDispatch方法,此时processedRequest = checkMultipart(request);执行结束,processedRequest变为:
设置上传请求的标志,此时只是请求处理,还没进行正式处理。
四、SpringMVC定义Controller得几种方式:
1、使用@Controller注解
java
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
@Controller
public class FileUploadController {
@RequestMapping("fileupload")
public String upload(MultipartFile file, HttpServletRequest request,String desc) throws IOException {
System.out.println(desc);
if (file.isEmpty()){
return "false";
}
String path = request.getServletContext().getRealPath("/WEB-INF/file");
String fileName = file.getOriginalFilename();
File filePath = new File(path,fileName);
if (!filePath.getParentFile().exists()){
filePath.getParentFile().mkdir();
}
file.transferTo(filePath);
return "success";
}
}
2、实现org.springframework.web.servlet.mvc.Controller接口
java
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Test01 implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
System.out.println("Controller接口 ..............");
return null;
}
}
3、实现org.springframework.web.HttpRequestHandler接口
java
import org.springframework.web.HttpRequestHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class Test02 implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
System.out.println("HttpRequestHandler.........");
}
}
总结: 使用2、3两种,要在配置文件写bean标签。/开头,表明这是个请求映射路径。1对应得是AbstractHandlerMethodMapping ,2、3对应得是AbstractUrlHandlerMapping
java
<bean name="/test01" class="com.mashibing.controller.testController.Test01"></bean>
<bean id="/test02" class="com.mashibing.controller.testController.Test02"></bean>
五、HandlerMapping 处理器映射器
bash
org.springframework.web.servlet.HandlerMapping=
org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping
获得请求对应的HandlerExecutionChain对象(HandlerMethod和HandlerInterceptor拦截器们)
mappedHandler = getHandler(processedRequest);//请求对应的是哪个controller
处理器映射器默认有三个先找AbstractUrlHandlerMapping的子类beanNameUrlHandlerMapping:
AbstractUrlHandlerMapping
这里的mapping映射器是beanNameUrlHandlerMapping,这个映射器是AbstractUrlHandlerMapping的子类,这里的handlerMap如下:
handlerMap是什么时候赋值的呢?
SpringMVC源码-AbstractUrlHandlerMapping处理器映射器的handlerMap如何实现Controller接口的方式定义的路径存储进去
需要的是FileUploadController在AbstractUrlHandlerMapping这里匹配不到合适的。
同时这步操作也完成了拦截器定义加载
AbstractHandlerMethodMapping类的mappingRegistry什么时候赋值的呢?
SpringMVC源码-AbstractHandlerMethodMapping处理器映射器将@Controller修饰类方法存储到处理器映射器
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);找到对应的controller
六、拦截器和HandlerAdapter处理器适配器
getHandlerInternal执行结束,开始执行HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
getHandlerExecutionChain 匹配合适的拦截器生成拦截器链
java
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// 创建 HandlerExecutionChain 对象
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// 获得请求路径
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request, LOOKUP_PATH);
// 遍历 adaptedInterceptors 数组,获得请求匹配的拦截器
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
// 需要匹配,若路径匹配,则添加到 chain 中
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
// 无需匹配,直接添加到 chain 中
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
获取HandlerAdapter
// 获得当前handler对应的HandlerAdapter对象 controller或者控制器有多种不同的实现方式
为了方便后续过程中调用 使用适配器模式来 解决 HandlerAdapter ha =
getHandlerAdapter(mappedHandler.getHandler());
SpringMVC源码-处理器适配器HandlerAdapter讲解
mappedHandler = getHandler(processedRequest);//请求对应的是哪个controller,以及需要被执行的拦截器。
这里配置的拦截器是对所有方法生效,执行拦截器是在下面:
进入方法:
进入HandlerMappingInterceptor拦截器执行preHandle方法
如果这里返回false:
进入triggerAfterCompletion
afterCompletion在整个请求执行完之后执行,也就是DispatcherServlet视图渲染之后执行,这个方法主要作用是用于清理资源,至此,拦截器的前置执行方法也被讲解完毕。
七、执行处理器适配器HandlerAdapter的handle方法
执行controller里的方法
java
handleInternal:825, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:90, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1127, DispatcherServlet (org.springframework.web.servlet)
doService:1006, DispatcherServlet (org.springframework.web.servlet)
processRequest:1085, FrameworkServlet (org.springframework.web.servlet)
doGet:960, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
RequestMappingHandlerAdapter的invokeHandlerMethod方法:
java
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
// 使用request和response创建ServletWebRequest对象
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 创建WebDataBinderFactory对象,此对象用来创建WebDataBinder对象,进行参数绑定,
// 实现参数跟String之间的类型转换,ArgumentResolver在进行参数解析的过程中会用到WebDataBinder
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);//返回的是@InitBinder注解修饰的方法的集合
// 创建ModelFactory对象,此对象主要用来处理model,主要是两个功能,1是在处理器具体处理之前对model进行初始化,2是在处理完请求后对model参数进行更新
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// 创建ServletInvocableHandlerMethod对象,并设置其相关属性,实际的请求处理就是通过此对象来完成的,参数绑定、处理请求以及返回值处理都在里边完成
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 设置参数处理器argumentResolvers 在该适配器初始化的时候调用afterPropertiesSet方法完成赋值
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// 设置返回值处理器
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 设置参数绑定工厂对象
invocableMethod.setDataBinderFactory(binderFactory);
// 设置参数名称发现器
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 创建ModelAndViewContainer对象,用于保存model和View对象
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
// 将flashmap中的数据设置到model中 FlashMap只适用于重定向跳转传参
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
// 使用modelFactory将sessionAttributes和注释了@ModelAttribute的方法的参数设置到model中
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
// 根据配置对ignoreDefaultModelOnRedirect进行设置
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// 创建AsyncWebRequest异步请求对象
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
// 创建WebAsyncManager异步请求管理器对象
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
// 如果当前异步请求已经处理并得到结果,则将返回的结果放到mavContainer对象中,然后将invocable对象进行包装转换,转成需要的执行对象然后开始执行
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
// 转换具体的invocable执行对象
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 执行调用
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 处理完请求后的后置处理,此处一共做了三件事,
// 1、调用ModelFactory的updateModel方法更新model,包括设置SessionAttribute和给Model设置BinderResult
// 2、根据mavContainer创建了ModelAndView
// 3、如果mavContainer里的model是RedirectAttributes类型,则将其设置到FlashMap
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
// 标记请求完成
webRequest.requestCompleted();
}
}
八、@ControllerAdvice和 @InitBinder注解源码层面讲解
getDataBinderFactory
创建WebDataBinderFactory对象,此对象用来创建WebDataBinder对象,进行参数绑定,实现参数跟String之间的类型转换,ArgumentResolver在进行参数解析的过程中会用到WebDataBinder
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);//返回的是@InitBinder注解修饰的方法的集合
java
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
Class<?> handlerType = handlerMethod.getBeanType();
// 检查当前Handler中的initBinder方法是否已经存在于缓存中
Set<Method> methods = this.initBinderCache.get(handlerType);
// 如果没有则查找并设置到缓冲中
if (methods == null) {
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);// 将当前Controller中所有被@InitBinder注解修饰的方法都获取到
this.initBinderCache.put(handlerType, methods);
}
// 定义保存InitBinder方法的临时变量 对当前controller有效
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();
// Global methods first @ControllerAdvice修饰的类里的@InitBinder注解修饰的方法
// 将所有符合条件的全局InitBinder方法添加到initBinderMethods
this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {
if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {
Object bean = controllerAdviceBean.resolveBean();
for (Method method : methodSet) {
initBinderMethods.add(createInitBinderMethod(bean, method));
}
}
});
// 将当前handler中的initBinder方法添加到initBinderMethods
for (Method method : methods) {//当前controller里@InitBinder注解修饰的方法和@ControllerAdvice修饰的类里的@InitBinder注解修饰的方法合并到一起 但是不会去重 因为这里的容器是list
// 创建当前方法对应的bean对象
Object bean = handlerMethod.getBean();
// 将method适配为可执行的invocableHandlerMethod
initBinderMethods.add(createInitBinderMethod(bean, method));
}
// 创建DataBinderFactory并返回
return createDataBinderFactory(initBinderMethods);
}
InitBinderController
java
/**
* InitBinder注解作用于Controller中的方法,表示为当前控制器注册一个属性编辑器
* 对webDataBinder进行初始化且只对当前的Controller有效
*
*
*/
@Controller
public class InitBinderController {
// 类型转换
@InitBinder
public void initBinder(WebDataBinder binder){
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
CustomDateEditor dateEditor = new CustomDateEditor(df,true);
binder.registerCustomEditor(Date.class,dateEditor);
}
// 属性编辑器
@RequestMapping("/param")
public String getFormatDate( Date data, Map<String,Object> map){
System.out.println(data);
map.put("name","zhangsan");
map.put("age",12);
map.put("date",data);
return "map";
}
// 参数绑定
@InitBinder("user")
public void init1(WebDataBinder binder){
binder.setFieldDefaultPrefix("u.");
}
@InitBinder("stu")
public void init2(WebDataBinder binder){
binder.setFieldDefaultPrefix("s.");
}
@RequestMapping("/getBean")
public ModelAndView getBean(User user, @ModelAttribute("stu") Student stu){
System.out.println(stu);
System.out.println(user);
String viewName = "success";
ModelAndView modelAndView = new ModelAndView(viewName);
modelAndView.addObject("user", user);
modelAndView.addObject("student", stu);
return modelAndView;
}
}
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);// 将当前Controller中所有被@InitBinder注解修饰的方法都获取到
ControllerAdviceController该类下面的 @InitBinder注解修饰的方法也被取到,SpringMVC源码-@ControllerAdvice和 @InitBinder注解源码讲解 @ControllerAdvice注解修饰类相关教程在这里。
查找当前处理类中所有包含@InitBinder注解修饰的方法查找@ControllerAdive注解修饰的类中有哪些@initBinder修饰的方法
全局的和当前controller的@InitBinder注解修饰的方法都获取到了,放在这里。
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
查找当前处理类中所有包含@ModelAttribute注解修饰的方法查找@ControlerAdive注解修饰的类中有哪些@ModelAttribute修饰的方法
// 使用modelFactory将sessionAttributes和注释了@ModelAttribute的方法的参数设置到model中
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
// 根据配置对ignoreDefaultModelOnRedirect进行设置
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
invocableMethod.invokeAndHandle(webRequest, mavContainer);开始执行方法调用
参数解析:
java
getParameterValues:427, RequestFacade (org.apache.catalina.connector)
getParameterValues:153, ServletWebRequest (org.springframework.web.context.request)
resolveName:183, RequestParamMethodArgumentResolver (org.springframework.web.method.annotation)
resolveArgument:117, AbstractNamedValueMethodArgumentResolver (org.springframework.web.method.annotation)
resolveArgument:123, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)
getMethodArgumentValues:177, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:138, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:109, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:929, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:825, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:90, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1127, DispatcherServlet (org.springframework.web.servlet)
doService:1006, DispatcherServlet (org.springframework.web.servlet)
processRequest:1085, FrameworkServlet (org.springframework.web.servlet)
doGet:960, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
当前controller以及全局的ControllerAdvice修饰的类中有@InitBinder注解修饰的方法,所以后续要进入该方法
对解析出来的参数进行处理:
方法调用堆栈路径是:
java
initBinder:30, InitBinderController (com.mashibing.controller.initBinder)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:202, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:143, InvocableHandlerMethod (org.springframework.web.method.support)
initBinder:73, InitBinderDataBinderFactory (org.springframework.web.method.annotation)
createBinder:62, DefaultDataBinderFactory (org.springframework.web.bind.support)
resolveArgument:136, AbstractNamedValueMethodArgumentResolver (org.springframework.web.method.annotation)
resolveArgument:123, HandlerMethodArgumentResolverComposite (org.springframework.web.method.support)
getMethodArgumentValues:177, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:138, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:109, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:929, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:825, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:90, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1127, DispatcherServlet (org.springframework.web.servlet)
doService:1006, DispatcherServlet (org.springframework.web.servlet)
processRequest:1085, FrameworkServlet (org.springframework.web.servlet)
doGet:960, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
所有 @InitBinder注解修饰的方法都进行遍历,除了当前controller的也包括全局的即ControllerAdvice修饰的类中。
开始执行http://localhost:8080/spring_mymvc/param?id=1&name=zhangsana&data=2024-10-11 19:40:25
controller这个方法,链路如下:
java
getFormatDate:39, InitBinderController (com.mashibing.controller.initBinder)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:202, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:143, InvocableHandlerMethod (org.springframework.web.method.support)
invokeAndHandle:109, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:929, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:825, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:90, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1127, DispatcherServlet (org.springframework.web.servlet)
doService:1006, DispatcherServlet (org.springframework.web.servlet)
processRequest:1085, FrameworkServlet (org.springframework.web.servlet)
doGet:960, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
返回map去找对应的视图处理,
controller方法到这里就执行结束了,也有返回值
java
setResponseStatus:149, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeAndHandle:111, ServletInvocableHandlerMethod (org.springframework.web.servlet.mvc.method.annotation)
invokeHandlerMethod:929, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:825, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:90, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1127, DispatcherServlet (org.springframework.web.servlet)
doService:1006, DispatcherServlet (org.springframework.web.servlet)
processRequest:1085, FrameworkServlet (org.springframework.web.servlet)
doGet:960, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
returnValue对应的是controller里的返回值,接下来开始状态码的赋值操作:
ServletInvocableHandlerMethod
java
/**
* Set the response status according to the {@link ResponseStatus} annotation.
*/
private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
// 获得状态码,和 @ResponseStatus 注解相关
HttpStatus status = getResponseStatus();
if (status == null) {
return;
}
// 设置响应的状态码
HttpServletResponse response = webRequest.getResponse();
if (response != null) {
String reason = getResponseStatusReason();
// 有 reason ,则设置 status + reason
if (StringUtils.hasText(reason)) {
response.sendError(status.value(), reason);
}
else {
// 无 reason ,则仅设置 status
response.setStatus(status.value());
}
}
// To be picked up by RedirectView
// 为了 RedirectView ,所以进行设置
webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
}
getResponseStatus方法是调用HandlerMethod的,
这是ResponseStatus的赋值流程。
从15个返回值处理器查找符合条件的进行返回,
开始执行:handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
重定向用flashMap保证数据不丢失
设置视图名,并且如果是重定向再进行额外的处理。
至此返回值处理器完成,开始获取ModelAndView对象
java
invokeHandlerMethod:937, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:825, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:90, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1127, DispatcherServlet (org.springframework.web.servlet)
doService:1006, DispatcherServlet (org.springframework.web.servlet)
processRequest:1085, FrameworkServlet (org.springframework.web.servlet)
doGet:960, FrameworkServlet (org.springframework.web.servlet)
service:655, HttpServlet (javax.servlet.http)
service:945, FrameworkServlet (org.springframework.web.servlet)
service:764, HttpServlet (javax.servlet.http)
internalDoFilter:227, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:189, ApplicationFilterChain (org.apache.catalina.core)
doFilter:162, ApplicationFilterChain (org.apache.catalina.core)
invoke:197, StandardWrapperValve (org.apache.catalina.core)
invoke:97, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:135, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:687, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:78, StandardEngineValve (org.apache.catalina.core)
service:360, CoyoteAdapter (org.apache.catalina.connector)
service:399, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:890, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1789, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1191, ThreadPoolExecutor (org.apache.tomcat.util.threads)
run:659, ThreadPoolExecutor$Worker (org.apache.tomcat.util.threads)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)
得到mav对象
流程图如下:
mav返回视图之后开始后续执行,
包括设置视图名称,执行拦截器后置处理,渲染视图等。
java
// 执行响应的interceptor的postHandler方法
mappedHandler.applyPostHandle(processedRequest, response, mv);
java
/**
* 从这里就可以看到 是倒序遍历拦截器数组 所以 postHandle的执行顺序刚好和定义的顺序相反,
* Apply postHandle methods of registered interceptors.
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
throws Exception {
// 获得拦截器数组
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
// 遍历拦截器数组,倒序执行
for (int i = interceptors.length - 1; i >= 0; i--) {
HandlerInterceptor interceptor = interceptors[i];
// 后置处理
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
处理返回结果,包括处理异常、渲染页面、触发Interceptor的afterCompletion
java
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
java
/**
* Handle the result of handler selection and handler invocation, which is
* either a ModelAndView or an Exception to be resolved to a ModelAndView.
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 标记是否为处理生成异常的ModelAndView对象
boolean errorView = false;
// 如果请求处理过程中有异常抛出则处理异常
if (exception != null) {
// 从ModelAndViewDefiningException中获得ModelAndView对象
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
}
// 处理异常,生成ModelAndView对象
else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// Did the handler return a view to render?
// 是否进行页面渲染
if (mv != null && !mv.wasCleared()) {
// 渲染页面
render(mv, request, response);
// 清理请求中的错误消息属性
// 因为上述的情况中,processHandlerException会通过WebUtils设置错误消息属性,所以需要进行清理
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No view rendering, null ModelAndView returned.");
}
}
// 如果启动了异步处理则返回
if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// Concurrent handling started during a forward
return;
}
// 发出请求处理完成通知,触发Interceptor的afterCompletion
if (mappedHandler != null) {
// Exception (if any) is already handled..
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
开始渲染页面
java
protected View createView(String viewName, Locale locale) throws Exception {
return loadView(viewName, locale);//UrlBasedViewResolver
}
java
@Override
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.
// 往请求中设置一些属性,Locale、TimeZone、LocalizationContext
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 [" + getUrl() + "]");
}
rd.include(request, response);
}
else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
// 最后进行转发
rd.forward(request, response);
}
}
页面渲染结果: