Srping MVC 执行流程真的是老生常谈的话题了,最近同事小刚出去面试,前面面试官相继问了几个 Spring 相关的问题,但当面试官问他,你知道 Srping MVC 的执行流程吗?小刚娴熟的巴拉巴拉回答完后,面试官就让他回去等通知了...

Spring MVC 执行流程

Spring MVC 执行流程(图片版):

Spring MVC 执行流程(文字版):

  1. 用户发送请求到前端控制器 DispatcherServlet
  2. DispatcherServlet 控制器接收到请求,然后调用 HandlerMapping 处理器映射器。
  3. HandlerMapping 处理器映射器找到处理请求的 HandlerAdapter 处理器映射器。
  4. DispatcherServlet 调用 HandlerAdapter 处理器适配器找到具体的处理器 Controller。
  5. 执行 Controller 进行业务处理。
  6. Controller 执行完返回 ModelAndView 给 HandlerAdapter。
  7. HandlerAdapter 将 Controller 执行结果 ModelAndView 返回给 DispatcherServlet。
  8. DispatcherServlet 查询一个或多个 ViewResoler 视图解析器,找到 ModelAndView 指定的视图 View。
  9. ViewReslover 解析后返回具体 View。
  10. DispatcherServlet 根据View 进行渲染视图。
  11. 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);
}
@Override
protected 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"; @Nullable
HandlerExecutionChain 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容器中获取所有的beanNam
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
// 注册从容器中获取的beanName
for (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>() {
@Override
public T inspect(Method method) {
try {
// 如果method有@RequestMapping注解,则返回由注解得到的封装好的RequestMappingInfo对象,否则返回null
return 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://www.cnblogs.com/niceyoo/p/13663133.html

口述完SpringMVC执行流程,面试官就让同事回家等消息了的更多相关文章

  1. 面试高频SpringMVC执行流程最优解(源码分析)

    文章已托管到GitHub,大家可以去GitHub查看阅读,欢迎老板们前来Star! 搜索关注微信公众号 码出Offer 领取各种学习资料! SpringMVC执行流程 SpringMVC概述 Spri ...

  2. springMVC执行流程及原理

    spring的MVC执行原理 1.spring mvc将所有的请求都提交给DispatcherServlet,它会委托应用系统的其他模块负责对请求 进行真正的处理工作. 2.DispatcherSer ...

  3. 【阅读SpringMVC源码】手把手带你debug验证SpringMVC执行流程

    ✿ 阅读源码思路: 先跳过非重点,深入每个方法,进入的时候可以把整个可以理一下方法的执行步骤理一下,也可以,理到某一步,继续深入,回来后,接着理清除下面的步骤. ✿ 阅读本文的准备工作,预习一下Spr ...

  4. springmvc执行流程详细介绍

    1.什么是MVC MVC是Model View Controller的缩写,它是一个设计模式 2.springmvc执行流程详细介绍 第一步:发起请求到前端控制器(DispatcherServlet) ...

  5. 2.SpringMVC执行流程

    SpringMVC 执行流程: 执行流程简单分析: 1.浏览器提交请求到中央调度器 2.中央调度器直接将请求转给处理器映射器 3.处理器映射器会根据请求,找到处理该请求的处理器,并将其封装为处理器执行 ...

  6. SpringMvc执行流程及底层代码流程

    SpringMVC执行流程   01.客户端发送请求被我们在web.xml中配置DispatcherServlet(核心控制器)拦截: 默认执行DispatcherServlet中的 protecte ...

  7. SpringMVC执行流程总结

    SpringMVC 执行流程: 用户发送请求至前端控制器 DispatcherServlet DispatcherServlet 收到请求调用处理映射器 HandlerMapping 处理映射器根据请 ...

  8. SpringMVC执行流程及源码分析

    SpringMVC流程及源码分析 前言 ​ 学了一遍SpringMVC以后,想着做一个总结,复习一下.复习写下面的总结的时候才发现,其实自己学的并不彻底.牢固.也没有学全,视频跟书本是要结合起来一起, ...

  9. SpringMVC--从理解SpringMVC执行流程到SSM框架整合

    前言 SpringMVC框架是SSM框架中继Spring另一个重要的框架,那么什么是SpringMVC,如何用SpringMVC来整合SSM框架呢?下面让我们详细的了解一下. 注:在学习SpringM ...

随机推荐

  1. Django项目登录注册系统

    Django项目之个人网站 关注公众号"轻松学编程"了解更多. Github地址:https://github.com/liangdongchang/MyWeb.git 感兴趣的可 ...

  2. 面向初学者的Python爬虫程序教程之动态网页抓取

    目的是对所有注释进行爬网. 下面列出了已爬网链接.如果您使用AJAX加载动态网页,则有两种方式对其进行爬网. 分别介绍了两种方法:(如果对代码有任何疑问,请提出改进建议)解析真实地址爬网示例是参考链接 ...

  3. 记一次ns3的安装过程

    官方安装教程:https://www.nsnam.org/wiki/Installation 推荐使用Ubuntu18.04,Ubuntu20.04有些依赖无法下载. 准备工作 # 如果下载速度很慢, ...

  4. 在windows下安装node-sass失败,提示\node-sass: Command failed,解决方案

    执行命令 yarn add node-sass@4.7.2 --dev --registry=https://registry.npm.taobao.org :报错 出现这个问题的原因一般是网络问题, ...

  5. 第05组 Alpha冲刺(4/6)

    .th1 { font-family: 黑体; font-size: 25px; color: rgba(0, 0, 255, 1) } #ka { margin-top: 50px } .aaa11 ...

  6. Vue2.0源码分析

    Vue其实是用Function写的一个class: 1.通过一系列的函数给Vue.prototype上动态挂载方法和属性 2.通过initGlobalAPI(Vue)给Vue本身扩展全局API 数据驱 ...

  7. 极客mysql13

    1:为啥删除了表的一半数8据,表文文件大小没变化?因为delete 命令其实只是把记录的位置,或者数据页标记为了"可复用",但磁盘文件的大小是不会变的.也可以认为是一种逻辑删除,所 ...

  8. 自定义圆角背景的textview,抛弃shape

    自定义一个圆角背景的TextView,解放双手,不用再写shape了. 1.values目录新建attrs.xml. <?xml version="1.0" encoding ...

  9. 从头学起Verilog(一):组合逻辑基础与回顾

    引言 该部分主要回顾了本科时数字电路中组合逻辑电路部分,内容相对简单和基础. 内容主要包括:布尔代数相关知识,卡诺图,最大项与最小项,竞争和冒险以及一些常见模块 数字电路中的逻辑 组合逻辑:输出可以表 ...

  10. HECTF-5bit编码(Baudot Code)

    河北师大全国赛道比赛,第一次遇到这样的题,刚开始发现应该是一道摩斯密码的题,空格给去掉了,我在网上搜了半天, 列举全部的可能结果的工具,结果结果,啥也不是,感觉还是自己的能力不行吧,不过wp出来,感觉 ...