Spring MVC 的核心流程是什么?其实就是一个请求访问某个资源时,会解析 Request Params,最后落到具体的 Controller,再由 Controller 中的逻辑进行相关的业务处理,最后将结果返回给客户端的过程。
我们直到 Spring MVC 核心就是 DispatcherServlet,既然时一个 Servlet,那么就一定由 doGet 和 doPost 方法,我们就从这里开始研究
我们先缕一缕 Spring MVC 加载 handler 的流程
- 初始化(查找)所有 @Controller 或 @RequestMapping 的注解(这里会牵扯到父子容器的概念)
- 扫描所有 @RequestMappting 的方法
- 将 @RequestMappting 的路径和方法绑定在一起,并加入到一个 Map 中去
初始化 @Controller 注解
Spring MVC 通过实现 InitializingBean,重写 afterPropertiesSet() 方法,来完成方法或类的 URI 映射关系
@Override
public void afterPropertiesSet() {
// ★★★ 关键代码:初始化 @Controller 的方法,并生成映射关系
initHandlerMethods();
}
(1)找到符合要求的 bean
获取所有的 bean,并处理符合要求的 bean,符合要求是指类名上含有 @Controller 或 @RequestMapping
protected void initHandlerMethods() {
// 遍历 bean 的名称
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 处理 bean
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
// 获取 bean 的类对象
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
// 判断是否是一个 handler
// 有没有 @Controller 或者 @RequestMapping 注解
if (beanType != null && isHandler(beanType)) {
// 推断 加了 @RequestMapping 的方法,并注册到 mappingRegistry
detectHandlerMethods(beanName);
}
}
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
(2)推断符合要求的方法
当找到了符合要求的 bean 之后,就开始推断符合要求的方法或类,并将其进行注册
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 获取所有方法 key = method, value = RequestMappingInfo
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
// ★★★ 返回有 @RequestMapping 的方法,并封装为 RequestMappingInfo
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
// 注册到 mappingRegistry 中
// 也就是注册到 urlLookup Map 中去,key = uri, value = RequestMappingInfo
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
(3)注册到 urlLookup 中
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
// ★★★ 关键代码:加入 urlLookup
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
客户端调用
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
// 异步编程(webflux)
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// ★★★ 检查请求是否有上传文件操作
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
/*
Determine handler for the current request.
★★★ 推断当前请求(Controller)的处理程序
关键代码:返回一个 MappedHandler 对象,里面包括要执行的方法以及对应的方法参数
处理 handler 由两大类型
1、加上了 @Controller 注解:RequestMappingHandlerMapping
2、beanName 形式:BeanNameUrlHandlerMapping
*/
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
// 返回 404
noHandlerFound(processedRequest, response);
return;
}
/*
Determine handler adapter for the current request.
★★★ 关键代码:确定当前请求的 handler 的 Adapter(3种方式)
1、加上了 @Controller 注解:
mappedHandler.getHandler() 返回一个:方法
HandlerAdapter = RequestMappingHandlerAdapter
2、继承 Controller 接口:
mappedHandler.getHandler() 返回一个:bean
HandlerAdapter = SimpleControllerHandlerAdapter
3、继承 HttpRequestHandler 接口:
mappedHandler.getHandler() 返回一个:bean
HandlerAdapter = HttpRequestHandlerAdapter
*/
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// 获取请求类型
String method = request.getMethod();
// 是否是 GET 请求方式
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
// 检查最后修改
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
// 从浏览器缓存读取
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 前置拦截器处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
// 关键代码:开始执行方法,返回一个 ModelAndView 对象
// 1、如果是 beanName 的方式就比较简单,直接调用接口的实现类的方法即可
// 2、如果是 @Controller 的方式,就是 RequestMappingHandlerAdapter
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 尝试解析一个默认视图
applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 关键代码:尝试开始解析视图
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
如何判断是走视图跳转还是JSON
1、@ResponseBody 的形式
先通过 selectHandler 查找一个 handler,如果是 @ResponseBody 注解的话,会找到 RequestResponseBodyMethodProcessor 来处理,判断方式如下:看方法和返回值上是否有该注解
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
如果符合条件的话,就会尝试在从 messageConverters 中寻找 JSON 的消息解析器,进行解析,返回到页面
2、String 类型的返回形式
先通过 selectHandler 查找一个 handler,如果返回 String 的话,并且方法返回时 Void,会找到 RequestResponseBodyMethodProcessor 来处理,判断方式如下:
@Override
public boolean supportsReturnType(MethodParameter returnType) {
Class<?> paramType = returnType.getParameterType();
return (void.class == paramType || CharSequence.class.isAssignableFrom(paramType));
}
如果符合条件的话,将返回值作为视图的名称,设置到 ModelAndViewContainer 中,在判断是否是重定向视图,即 redirect: 开头的字符串,如果是的话:ModelAndViewContainer 设置为重定向
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue instanceof CharSequence) {
String viewName = returnValue.toString();
// 设置视图名称
mavContainer.setViewName(viewName);
// 是否包含 redirect: 前缀
if (isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else if (returnValue != null) {
// should not happen
throw new UnsupportedOperationException("Unexpected return type: " +
returnType.getParameterType().getName() + " in method: " + returnType.getMethod());
}
}
protected boolean isRedirectViewName(String viewName) {
return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName)
|| viewName.startsWith("redirect:"));
}
当封装好 ModelAndViewContainer 后(里面已经包含视图名称和是否重定向)开始创建一个 ModelAndView 对象,其实就是 new 了一个 ModelAndView,分别开始处理 Model 和 View
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
// 处理 Model
modelFactory.updateModel(webRequest, mavContainer);
// 关键代码:如果不需要返回视图,则直接返回 null
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
// 检查是不是一个 View 对象,其实就是看看是不是 String
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
// 判断是不是 重定向 的参数
// 即方法中含有 RedirectAttributes 参数,该参数可以传递重定向的参数值
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
此时 ModelAndView 对象就已经初始化完成了,下一步开始渲染视图
先通过视图解析器,根据视图名称,查找所需要的视图,然后返回给浏览器,其实核心就是调用转发来实现视图跳转:
- 如果手动关闭输出流:RequestDispatcher.include(request, response);
- 如果没有关闭输出流:RequestDispatcher.forward(request, response);
**
@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.
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() + "]");
}
// 如果已经手动关闭了输出流 response.getWriter().close()
// 就使用 RequestDispatcher.include(request, response);
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() + "]");
}
// ★★★ 关键代码
// 如果没有关闭输出流 response.getWriter().close()
// 其实就是 RequestDispatcher.forward(request, response);
rd.forward(request, response);
}
}
3、ModelAndView 返回形式
ModelAndView 的形式和 String 的形式类似,只是处理返回值的 handler 是 ModelAndViewMethodReturnValueHandler
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}
接下来,开始处理 ModelAndView
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
ModelAndView mav = (ModelAndView) returnValue;
// 判断 view 是不是 String
if (mav.isReference()) {
String viewName = mav.getViewName();
mavContainer.setViewName(viewName);
if (viewName != null && isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else {
View view = mav.getView();
mavContainer.setView(view);
if (view instanceof SmartView && ((SmartView) view).isRedirectView()) {
mavContainer.setRedirectModelScenario(true);
}
}
mavContainer.setStatus(mav.getStatus());
// 设置传递需要的参数
mavContainer.addAllAttributes(mav.getModel());
}
此时 ModelAndView 对象就已经初始化完成了,下一步开始渲染视图
先通过视图解析器,根据视图名称,查找所需要的视图,然后返回给浏览器,其实核心就是调用转发来实现视图跳转:
- 如果手动关闭输出流:RequestDispatcher.include(request, response);
- 如果没有关闭输出流:RequestDispatcher.forward(request, response);