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 小时前
缓存技术揭秘
java·运维·服务器·开发语言·spring·缓存
小蒜学长2 小时前
springboot多功能智能手机阅读APP设计与实现(代码+数据库+LW)
java·spring boot·后端·智能手机
追逐时光者4 小时前
精选 4 款开源免费、美观实用的 MAUI UI 组件库,助力轻松构建美观且功能丰富的应用程序!
后端·.net
你的人类朋友4 小时前
【Docker】说说卷挂载与绑定挂载
后端·docker·容器
间彧5 小时前
在高并发场景下,如何平衡QPS和TPS的监控资源消耗?
后端
间彧5 小时前
QPS和TPS的区别,在实际项目中,如何准确测量和监控QPS和TPS?
后端
zizisuo5 小时前
解决在使用Lombok时maven install 找不到符号的问题
java·数据库·maven
间彧5 小时前
消息队列(RocketMQ、RabbitMQ、Kafka、ActiveMQ)对比与选型指南
后端·消息队列
笨蛋少年派5 小时前
JAVA基础语法
java·开发语言
渡我白衣5 小时前
深入剖析:boost::intrusive_ptr 与 std::shared_ptr 的性能边界和实现哲学
开发语言·c++·spring