Srping MVC 执行流程真的是老生常谈的话题了,最近同事小刚出去面试,前面面试官相继问了几个 Spring 相关的问题,但当面试官问他,你知道 Srping MVC 的执行流程吗?小刚娴熟的巴拉巴拉回答完后,面试官就让他回去等通知了…
Spring MVC 执行流程
Spring MVC 执行流程(图片版):
Spring MVC 执行流程(文字版):
- 用户发送请求到前端控制器 DispatcherServlet
- DispatcherServlet 控制器接收到请求,然后调用 HandlerMapping 处理器映射器。
- HandlerMapping 处理器映射器找到处理请求的 HandlerAdapter 处理器映射器。
- DispatcherServlet 调用 HandlerAdapter 处理器适配器找到具体的处理器 Controller。
- 执行 Controller 进行业务处理。
- Controller 执行完返回 ModelAndView 给 HandlerAdapter。
- HandlerAdapter 将 Controller 执行结果 ModelAndView 返回给 DispatcherServlet。
- DispatcherServlet 查询一个或多个 ViewResoler 视图解析器,找到 ModelAndView 指定的视图 View。
- ViewReslover 解析后返回具体 View。
- DispatcherServlet 根据View 进行渲染视图。
- DispatcherServlet 响应结果给用户。
整个流程这样回答下来应该没什么问题,因为无论是书上还是面试题上基本都这么标注的答案,但巧就巧在,同事小刚做的项目是前后端分离的项目(简历中写的),言外之意就是从第 6 步开始,下面的回答基本就不对了,因为在前后端分离项目中,最终返回给前端的数据是以 JSON 形式的,所以不存在什么视图解析器一说。
这个时候需要变一个答法,就是直接返回 JSON 数据回去,可以使用 @ResponseBody 注解。
到这基本也明白了为啥小刚答完这个问题,面试官就让他回去等消息了…
Spring MVC 工作原理
相信大家上方的执行流程都背的滚瓜烂熟了…
不知道大家是否有这种好奇,就是 @RequestMapping 注解的 Url 怎么就跟 Controller 关联起来了呢?
不管你好不好奇,接下来我们就假装带着这个好奇来看看怎么就关联上了。
从上边的流程我们知道请求的入口是 DispatcherServlet ,那么我们来看一下这个类:
@SuppressWarnings("serial")
public class DispatcherServlet extends FrameworkServlet {public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";public static final String HANDLER_MAPPING_BEAN_NAME = "handlerMapping";public static final String HANDLER_ADAPTER_BEAN_NAME = "handlerAdapter";public static final String HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver";public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";public static final String VIEW_RESOLVER_BEAN_NAME = "viewResolver";public static final String FLASH_MAP_MANAGER_BEAN_NAME = "flashMapManager";public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";public static final String INPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".INPUT_FLASH_MAP";public static final String OUTPUT_FLASH_MAP_ATTRIBUTE = DispatcherServlet.class.getName() + ".OUTPUT_FLASH_MAP";public static final String FLASH_MAP_MANAGER_ATTRIBUTE = DispatcherServlet.class.getName() + ".FLASH_MAP_MANAGER";public static final String EXCEPTION_ATTRIBUTE = DispatcherServlet.class.getName() + ".EXCEPTION";public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);private static final Properties defaultStrategies;static {try {ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);}catch (IOException ex) {throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + ex.getMessage());}}/** Detect all HandlerMappings or just expect "handlerMapping" bean? */private boolean detectAllHandlerMappings = true;/** Detect all HandlerAdapters or just expect "handlerAdapter" bean? */private boolean detectAllHandlerAdapters = true;/** Detect all HandlerExceptionResolvers or just expect "handlerExceptionResolver" bean? */private boolean detectAllHandlerExceptionResolvers = true;/** Detect all ViewResolvers or just expect "viewResolver" bean? */private boolean detectAllViewResolvers = true;/** Throw a NoHandlerFoundException if no Handler was found to process this request? **/private boolean throwExceptionIfNoHandlerFound = false;/** Perform cleanup of request attributes after include request? */private boolean cleanupAfterInclude = true;/** MultipartResolver used by this servlet */private MultipartResolver multipartResolver;/** LocaleResolver used by this servlet */private LocaleResolver localeResolver;/** ThemeResolver used by this servlet */private ThemeResolver themeResolver;/** List of HandlerMappings used by this servlet */private List<HandlerMapping> handlerMappings;/** List of HandlerAdapters used by this servlet */private List<HandlerAdapter> handlerAdapters;/** List of HandlerExceptionResolvers used by this servlet */private List<HandlerExceptionResolver> handlerExceptionResolvers;/** RequestToViewNameTranslator used by this servlet */private RequestToViewNameTranslator viewNameTranslator;private FlashMapManager flashMapManager;/** List of ViewResolvers used by this servlet */private List<ViewResolver> viewResolvers;public DispatcherServlet() {super();}public DispatcherServlet(WebApplicationContext webApplicationContext) {super(webApplicationContext);}@Overrideprotected void onRefresh(ApplicationContext context) {initStrategies(context);}protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context);}
}
这个类真的是太长了,实在是没耐心看下来,以至于不得不精简了一部分(上方为精简后的),在这里面我们可以看到一些熟悉的面孔:
- HandlerMapping:用于handlers映射请求和一系列的对于拦截器的前处理和后处理,大部分用@Controller注解。
- HandlerAdapter:帮助DispatcherServlet处理映射请求处理程序的适配器,而不用考虑实际调用的是 哪个处理程序
- ViewResolver:根据实际配置解析实际的View类型。
- ThemeResolver:解决Web应用程序可以使用的主题,例如提供个性化布局。
- MultipartResolver:解析多部分请求,以支持从HTML表单上传文件。
既然 HandlerMapping 是用来映射请求的,然后我们就继续朝着这个方向走,在上方 DispatcherServlet 中找到 HandlerMapping 相关的代码,然后我们就找到了这个集合:
private List<HandlerMapping> handlerMappings;
接着看看在哪给这个 handlerMappings 集合赋值的,然后就找到了如下方法:
private void initHandlerMappings(ApplicationContext context) {this.handlerMappings = null;if (this.detectAllHandlerMappings) {Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerMappings = new ArrayList(matchingBeans.values());AnnotationAwareOrderComparator.sort(this.handlerMappings);}} else {try {HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class);this.handlerMappings = Collections.singletonList(hm);} catch (NoSuchBeanDefinitionException var3) {}}if (this.handlerMappings == null) {this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class);if (this.logger.isTraceEnabled()) {this.logger.trace("No HandlerMappings declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties");}}}
简单分析一下 initHandlerMappings() 这段方法,首先我们需要先了解 HandlerMapping 其实是一个接口类,然后这个接口类就定义了一个方法 getHandler() ,这个方法也很简单,就是根据请求的 request,获取 HandlerExecutionChain 对象:
public interface HandlerMapping {String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";@NullableHandlerExecutionChain getHandler(HttpServletRequest var1) throws Exception;
}
Spring MVC 提供了许多 HandlerMapping 的实现,我们可以点进去看看这个 HandlerMapping 有多少实现:
大约有19个子类实现了 HandlerMapping 接口,默认使用的是 BeanNameUrlHandlerMapping(可以根据 Bean 的 name 属性映射到 URL 中)。我们再回到 initHandlerMappings() 方法,我们可以看到在赋值的时候有个 if (this.detectAllHandlerMappings)
属性的判断,这个属性是用来标记是否只期望 Srping MVC 只加载指定的 HandlerMappring 的,如果修改为 fasle ,Spring MVC 就只会查找名为 “handlerMapping” 的 bean,并作为当前系统的唯一的 HandlerMapping。
而正常情况为 true 时,就会加载所有 HandlerMapping 的实现类,加载之后还有个使用优先级的排序过程 AnnotationAwareOrderComparator.sort(this.handlerMappings);
,优先使用高优先级的 HandlerMapping。
看到这,可能就会觉得,既然 HandlerMapping 有这么多的实现类,但是具体的某个实现类又是怎么初始化的呢?毕竟 HandlerMapping 的作用可是用来映射请求的,还没看到具体实现过程呢…
所以到这个时候,很显然得进行下去嘛,所以不得不找一个实现类来看看是如何具体初始化的,但是具体找哪个分析呢?在决定分析哪个之前,我们先了解一下 HadlerMapping 接口的继承体系。
HandlerMapping接口继承体系
这个体系比较庞大,我们着重看我标注的红框跟蓝筐内的内容,通过1、2我们可以将 HadlerMapping 分为两个体系,一是继承自 AbstractUrlHandlerMapping,二是继承 AbstractHandlerMethodMapping,因为随着版本的问题(本文以Spring4.3.13为例),其中 AbstractUrlHandlerMapping 在目前大部分的项目已经很少使用到了,所以接下来我们就重点分析AbstractHandlerMethodMapping,他就是我们经常使用的@RequestMapping
注解会使用到的方式。
在分析 AbstractHandlerMethodMapping 之前,我们先分析下这个类的父类 AbstractHandlerMapping,不然有些方法就很懵逼。
1、AbstractHandlerMapping概述
已经不想贴这个类的完整代码了,感兴趣的小伙伴自己点进去看看吧,简单说一下定义,AbstractHandlerMapping 是 HandlerMapping 的抽象实现,采用模板模式设计了 HandlerMapping 的整体架构。其定义了getHandlerInternal() 方法,该方法就是一个模版方法,根据 request 来获取相应的 Handler,由它的两个子类来具体实现该方法。然后再根据 request 来获取相应的 interceptors,整合从子类获取的 Handler,组成 HandlerExecutionChain 对象返回。
@Nullable
protected abstract Object getHandlerInternal(HttpServletRequest var1) throws Exception;
2、AbstractHandlerMapping初始化
AbstractHandlerMapping 继承了 WebApplicationObjectSupport(获取Spring的ApplicationContext方式之一,可以看做java类获取Spring容器的Bean),初始化时会自动调用模板方法 initApplicationContext,具体如下:
@Override
protected void initApplicationContext() throws BeansException {// 模板方法,暂无子类实现extendInterceptors(this.interceptors);// 从容器中获取实现了MappedInterceptor接口的对象,添加到adaptedInterceptors列表中detectMappedInterceptors(this.adaptedInterceptors);// 将interceptors(由子类添加)中的对象封装后添加到adaptedInterceptors列表中initInterceptors();
}
3、AbstractHandlerMapping的使用
AbstractHandlerMapping 继承自 HandlerMapping ,实现了其 getHandler() 方法,我们上边也提到该方法就是根据请求的 request,获取 HandlerExecutionChain 对象,我们来看一下 AbstractHandlerMapping 中的实现:
@Override
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {// 模板模式方法,具体由子类实现Object handler = getHandlerInternal(request);if (handler == null) {handler = getDefaultHandler();}if (handler == null) {return null;}if (handler instanceof String) {String handlerName = (String) handler;handler = getApplicationContext().getBean(handlerName);}// 根据request从adaptedInterceptors中选择匹配的interceptors,与handler一起封装成HandlerExecutionChain对象HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);return executionChain;
}
到这就是交给子类去完成了,分别是 AbstractUrlHandlerMapping 和 AbstractHandlerMethodMapping,接下来我们只着重分析 AbstractHandlerMethodMapping 类。
AbstractHandlerMethodMapping
首先实现了父类 getHandlerInternal() 方法:
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {// 获得请求的路径String lookupPath = this.getUrlPathHelper().getLookupPathForRequest(request);request.setAttribute(LOOKUP_PATH, lookupPath);// 获得读锁this.mappingRegistry.acquireReadLock();HandlerMethod var4;try {// 获得 HandlerMethod 对象HandlerMethod handlerMethod = this.lookupHandlerMethod(lookupPath, request);var4 = handlerMethod != null ? handlerMethod.createWithResolvedBean() : null;} finally {// 释放锁this.mappingRegistry.releaseReadLock();}return var4;
}
重点在于 lookupHandlerMethod() 方法,如下为详细代码:
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {// Match数组,用于存储匹配上当前请求的结果List<AbstractHandlerMethodMapping<T>.Match> matches = new ArrayList();// 优先级,基于直接 URL 的 Mapprings 进行匹配List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);if (directPathMatches != null) {this.addMatchingMappings(directPathMatches, matches, request);}// 扫描注册表的 Mappings 进行匹配if (matches.isEmpty()) {this.addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);}// 如果匹配到,则获取最佳匹配的 Match 对象的 handlerMethod 属性if (!matches.isEmpty()) {// 创建 MathComparator 对象Comparator<AbstractHandlerMethodMapping<T>.Match> comparator = new AbstractHandlerMethodMapping.MatchComparator(this.getMappingComparator(request));// 排序 matches 结果matches.sort(comparator);// 获取首个 Match 对象AbstractHandlerMethodMapping<T>.Match bestMatch = (AbstractHandlerMethodMapping.Match)matches.get(0);// 处理存在多个 Match 对象的情况if (matches.size() > 1) {if (this.logger.isTraceEnabled()) {this.logger.trace(matches.size() + " matching mappings: " + matches);}if (CorsUtils.isPreFlightRequest(request)) {return PREFLIGHT_AMBIGUOUS_MATCH;}// 比较 bestMatch 和 secondBestMatch,如果相等则抛出 IllegalStateException 异常AbstractHandlerMethodMapping<T>.Match secondBestMatch = (AbstractHandlerMethodMapping.Match)matches.get(1);if (comparator.compare(bestMatch, secondBestMatch) == 0) {Method m1 = bestMatch.handlerMethod.getMethod();Method m2 = secondBestMatch.handlerMethod.getMethod();String uri = request.getRequestURI();throw new IllegalStateException("Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");}}request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);// 处理首个 Match 对象this.handleMatch(bestMatch.mapping, lookupPath, request);return bestMatch.handlerMethod;} else {// 如果匹配不到,则处理不匹配的情况return this.handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);}
}
在分析 lookupHandlerMethod() 方法的整体思路之前,我们还得知晓 AbstractHandlerMethodMapping 的内部类 MappingRegistry。MappingRegistry 类中定义了两个比较重要的变量,Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<T, HandlerMethod>()
和 MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<String, T>()
。其中 mappingLookup 变量保存了 RequestMappingInfo 与 HandlerMethod 的一一对应关系,而 urlLookup 变量则保存了 url 与 RequestMappingInfo 的对应关系,需要注意的是 MultiValueMap 类型的变量是可以一个 key 对应多个 value 的,也就是说 urlLookup 变量中,一个 url 可能对应多个 RequestMappingInfo。
有了这个概念后我们再来看 lookupHandlerMethod() 方法,整个过程结果就是,根据入参 lookupPath(可以看成是 url),从 request 中获取到一个最符合的 RequestMappingInfo 对象,然后根据该对象再去获取到 HandlerMethod 对象返回给父类 AbstractHandlerMapping。
其实在 lookupHandlerMethod() 方法之前,还有一个 mappingLookup、urlLookup 等参数初始化的过程,AbstractHandlerMethodMapping 实现了 InitializingBean 接口,当 Spring 容器启动时会自动调用其 afterPropertiesSet() 方法,来完成 handlerMethod 的注册操作,但是该方法最终又交给 initHandlerMethods() 方法完成具体的初始化:
protected void initHandlerMethods() {if (logger.isDebugEnabled()) {logger.debug("Looking for request mappings in application context: " + getApplicationContext());}// 从springMVC容器中获取所有的beanNamString[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :getApplicationContext().getBeanNamesForType(Object.class));// 注册从容器中获取的beanNamefor (String beanName : beanNames) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {Class<?> beanType = null;try {beanType = getApplicationContext().getType(beanName);}catch (Throwable ex) {// An unresolvable bean type, probably from a lazy bean - let's ignore it.if (logger.isDebugEnabled()) {logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);}}if (beanType != null && isHandler(beanType)) {detectHandlerMethods(beanName);}}}// 模板方法,暂无子类实现handlerMethodsInitialized(getHandlerMethods());
}
简单来说这个方法就是进行 HandlerMethod 的注册操作,首先从 Spring MVC 的容器中获取所有的 beanName 然后进行过滤处理,注册 URL 和实现方法 HandlerMethod 的对应关系。
在 initHandlerMethods() 方法中我们主要关注两个方法 isHandler(beanType) 与 detectHandlerMethods(beanName) ,其中 isHandler(beanType) 方法由子类 RequestMappingHandlerMapping 实现,用于对 bean 进行过滤,判断是否包含 Controller 或者 RequestMapping 注解。
@Override
protected boolean isHandler(Class<?> beanType) {return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
而 detectHandlerMethods(beanName) 方法,则根据筛选出的 bean,进行一系列的注册,最终实现是在 registerHandlerMethod() 方法:
protected void detectHandlerMethods(final Object handler) {Class<?> handlerType = (handler instanceof String ?getApplicationContext().getType((String) handler) : handler.getClass());// CGLib动态代理的特殊处理final Class<?> userType = ClassUtils.getUserClass(handlerType);// methods包含了bean中所有符合条件的method与相关的RequestMappingInfo键值对Map<Method, T> methods = MethodIntrospector.selectMethods(userType,new MethodIntrospector.MetadataLookup<T>() {@Overridepublic T inspect(Method method) {try {// 如果method有@RequestMapping注解,则返回由注解得到的封装好的RequestMappingInfo对象,否则返回nullreturn getMappingForMethod(method, userType);}catch (Throwable ex) {throw new IllegalStateException("Invalid mapping on handler class [" +userType.getName() + "]: " + method, ex);}}});if (logger.isDebugEnabled()) {logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);}for (Map.Entry<Method, T> entry : methods.entrySet()) {Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);T mapping = entry.getValue();// 注册 beanName,Method及创建的RequestMappingInfo之间的关系registerHandlerMethod(handler, invocableMethod, mapping);}
}
detectHandlerMethods() 方法中的 getMappingForMethod() 方法是在子类 RequestMappingHandlerMapping 中实现的,具体实现就是创建一个 RequestMappingInfo:
/*** Uses method and type-level @{@link RequestMapping} annotations to create* the RequestMappingInfo.* @return the created RequestMappingInfo, or {@code null} if the method* does not have a {@code @RequestMapping} annotation.* @see #getCustomMethodCondition(Method)* @see #getCustomTypeCondition(Class)*/
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {RequestMappingInfo info = createRequestMappingInfo(method);if (info != null) {RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);if (typeInfo != null) {info = typeInfo.combine(info);}}return info;
}/*** Delegates to {@link #createRequestMappingInfo(RequestMapping, RequestCondition)},* supplying the appropriate custom {@link RequestCondition} depending on whether* the supplied {@code annotatedElement} is a class or method.* @see #getCustomTypeCondition(Class)* @see #getCustomMethodCondition(Method)*/
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);RequestCondition<?> condition = (element instanceof Class ?getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}
detectHandlerMethods() 方法中的 registerHandlerMethod() 方法的操作是注册 beanName,Method 及创建的 RequestMappingInfo 之间的关系:
protected void registerHandlerMethod(Object handler, Method method, T mapping) {this.mappingRegistry.register(mapping, handler, method);
}
到这就简单实现了将 url 和 HandlerMethod 的对应关系注册到 mappingRegistry 中了。
private final AbstractHandlerMethodMapping<T>.MappingRegistry mappingRegistry = new AbstractHandlerMethodMapping.MappingRegistry();
最后总结
Spring MVC 执行流程
在描述 Spring MVC 执行流程的时候需要注意,只有在以前使用 jsp、themlef 等模板引擎的时候,我们会把前端界面放在后端的工程里,然后在 controller 执行完业务逻辑之后会返回一个界面模版名称,也就是ModelAndView,然后 DispatherServlet 将 ModelAndView 传递给 ViewReslover 视图解析器,视图解析器解析后返回具体的 View,然后对 html 界面做一个渲染。
如果返回的是一个json串的话,也就是前后端分离的项目,那么只需要返回json数据即可。
Spring MVC 工作原理
Spring MVC 使用 HandlerMappring 来找到并保存 URL 请求和处理函数间的 mapping 关系。
以 AbstractHandlerMethodMapping 为例来具体看 HandlerMapping 的作用,首先拿到容器里所有的 bean,然后根据一定的规则筛选出 Handler,然后保存在 map 中,具体的筛选工作在子类中进行:筛选的逻辑就是检查类前是否存在 @Controller 或者 @RequestMapping 注解 ,然后在 detectHandlerMethods() 方法中负责将 Handler 保存在 map 中。
1、用户发送请求时会先从 DispathcherServler 的 doService 方法开始,在该方法中会将 ApplicationContext、localeResolver、themeResolver 等对象添加到 request 中,紧接着就是调用 doDispatch 方法。
2、进入 doDispatch 方法后首先会检查该请求是否是文件上传的请求,(校验的规则是是否是post并且contenttType是否为multipart/为前缀)即调用的是 checkMultipart 方法,如果是的话将 request 包装成 MultipartHttpServletRequest。
3、然后调用 getHandler 方法来匹配每个 HandlerMapping 对象,如果匹配成功会返回这个 Handle 的处理链 HandlerExecutionChain 对象,在获取该对象的内部其实也获取我们自定定义的拦截器,并执行了其中的方法。
4、执行拦截器的 preHandle 方法,如果返回 false 执行 afterCompletion 方法并理解返回
5、通过上述获取到了 HandlerExecutionChain 对象,通过该对象的 getHandler() 方法获得一个 object 通过 HandlerAdapter 进行封装得到 HandlerAdapter 对象。
6、该对象调用 handle 方法来执行 Controller 中的方法,然后根据类型返回不同的结果(如JSON、ModelAndView),该对象如果返回一个 ModelAndView 给 DispatcherServlet。
7、DispatcherServlet 借助 ViewResolver 完成逻辑试图名到真实视图对象的解析,得到 View 后 DispatcherServlet 使用这个 View 对 ModelAndView 中的模型数据进行视图渲染。
工作原理写着写着发现挺乱的,整体描述的不是特别清楚,也很枯燥,之后通过阅读 Spring MVC 整体源码后再补充。
本文首发于博客园:https://niceyoo.cnblogs.com/