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 映射关系
@Overridepublic 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)) {// 处理 beanprocessCandidateBean(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 的方法,并注册到 mappingRegistrydetectHandlerMethods(beanName);}}@Overrideprotected 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 = RequestMappingInfoMap<Method, T> methods = MethodIntrospector.selectMethods(userType,(MethodIntrospector.MetadataLookup<T>) method -> {try {// ★★★ 返回有 @RequestMapping 的方法,并封装为 RequestMappingInforeturn 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 = RequestMappingInforegisterHandlerMethod(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) {// ★★★ 关键代码:加入 urlLookupthis.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 注解:RequestMappingHandlerMapping2、beanName 形式:BeanNameUrlHandlerMapping*/mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {// 返回 404noHandlerFound(processedRequest, response);return;}/*Determine handler adapter for the current request.★★★ 关键代码:确定当前请求的 handler 的 Adapter(3种方式)1、加上了 @Controller 注解:mappedHandler.getHandler() 返回一个:方法HandlerAdapter = RequestMappingHandlerAdapter2、继承 Controller 接口:mappedHandler.getHandler() 返回一个:beanHandlerAdapter = SimpleControllerHandlerAdapter3、继承 HttpRequestHandler 接口:mappedHandler.getHandler() 返回一个:beanHandlerAdapter = 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 的方式,就是 RequestMappingHandlerAdaptermv = 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 afterCompletionif (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 来处理,判断方式如下:看方法和返回值上是否有该注解
@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||returnType.hasMethodAnnotation(ResponseBody.class));}
如果符合条件的话,就会尝试在从 messageConverters 中寻找 JSON 的消息解析器,进行解析,返回到页面
2、String 类型的返回形式
先通过 selectHandler 查找一个 handler,如果返回 String 的话,并且方法返回时 Void,会找到 RequestResponseBodyMethodProcessor 来处理,判断方式如下:
@Overridepublic 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 happenthrow 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
@Nullableprivate ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {// 处理 ModelmodelFactory.updateModel(webRequest, mavContainer);// 关键代码:如果不需要返回视图,则直接返回 nullif (mavContainer.isRequestHandled()) {return null;}ModelMap model = mavContainer.getModel();ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());// 检查是不是一个 View 对象,其实就是看看是不是 Stringif (!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);
**
@Overrideprotected 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
@Overridepublic boolean supportsReturnType(MethodParameter returnType) {return ModelAndView.class.isAssignableFrom(returnType.getParameterType());}
接下来,开始处理 ModelAndView
@Overridepublic 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 是不是 Stringif (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);
