SpringMVC源码-接口请求执行流程,包含九大内置组件的实例化初始化,拦截器调用,页面渲染等源码讲解

一、上传文件功能的实现:

前端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);
		}
	}

页面渲染结果:

相关推荐
向前看-1 小时前
验证码机制
前端·后端
xlsw_1 小时前
java全栈day20--Web后端实战(Mybatis基础2)
java·开发语言·mybatis
神仙别闹2 小时前
基于java的改良版超级玛丽小游戏
java
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭2 小时前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
暮湫3 小时前
泛型(2)
java
超爱吃士力架3 小时前
邀请逻辑
java·linux·后端
南宫生3 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
转码的小石3 小时前
12/21java基础
java
李小白663 小时前
Spring MVC(上)
java·spring·mvc
GoodStudyAndDayDayUp3 小时前
IDEA能够从mapper跳转到xml的插件
xml·java·intellij-idea