springMVC源码分析

本篇将涉及,通过本篇可以对整个springmvc模块有一个清晰的视角,在日常开发中可以快速定位问题和源码

  1. springMVC启动初始化过程分析
  2. 处理请求过程分析
  3. 参数解析、返回值处理

springMVC应用启动初始化过程分析

初始化过程应该分为三步:

  • 第一步,创建两个spring的ApplicationContext,配置DispatcherServlet、filter、ContextLoaderListener到servlet容器中
  • 第二步,servlet容器启动后,调用ContextLoaderListener#contextInitialized初始化必要的bean(如DAO、service相关的bean)
  • 第三步,调用DispatcherServlet#init方法进一步初始化必要的bean(如HandlerMappings、HandlerMappingAdapter等)

第一步


主要完成以下工作:

  1. 通过配置初始化两个容器,分别为spring容器、springMVC容器。(关于为什么需要两个容器,个人认为是为了在两个DispatcherServlet中共享同一个spring容器)
  2. 注册DispatcherServlet到servlet容器中,对外提供服务,处理客户端请求
  3. 注册Filter到servlet容器中

第二步


主要完成以下工作:

  1. 初始化父容器
  2. 绑定到servletContext,在应用中全局共享

第三步

在这一步中,分为两个阶段:

  1. 初始化属于springmvc的ApplicationContext、并添加SourceFilteringListener作为spring容器时间监听器
  2. 在完成初始化属于springmvc的ApplicationContext的工作后,SourceFilteringListener监听到容器时间,调用onApplicationEvent方法,完成剩余的初始化工作(包括初始化HandlerMappings、HandlerMappingAdapter等属于MVC领域的bean)

SourceFilteringListener的职责

先来看一下其类关系图

SourceFilteringListener作为EventListener的实现类,在servlet的ApplicationContext初始化后,将收到事件回调完成剩余的初始化工作

1
2
3
4
5
6
7
8
9
10
11
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}

在这些初始化方法中,注册到spring容器中的bean由注解@EnableWebMVC导入。
导入工作委托给了DelegatingWebMvcConfiguration类

以上就是springMVC的初始化过程的整体脉络

springMVC处理请求过程分析

整体流程


整个过程相对比较复杂,整个逻辑在DispatcherServlet#doDispatcher方法中实现,该方法内部将具体工作委托给了RequestMappingHandlerMapping、HandlerExcutionChain、RequestMappingHandlerAdapter、ViewResolver、View等几个核心类来完成任务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

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.
// 获取HandlerExcutionChain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获取HandlerAdapter
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用拦截器前置处理
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 真正调用HandlerMethod(即Controller方法),内部涉及了参数解析(处理@RequestParams、@PathVariables注解原理)、参数类型转换、HandlerMethod执行结果处理(@ResponseBody注解原理)这几个步骤。后面内容我们详细剖析这个函数的内部机制
// Actually invoke the handler.
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);
}
}
}
}

根据请求寻找对应的HandlerMethod


具体寻找过程的代码细节如下:

  1. 首先mappingRegistry(类MappingRegistry的实例)实例变量存放了所有的HandlerMethod(在RequestMappingHandlerMapping#afterPropertiesSet()方法中初始化完成,以RequestMappingInfo实例保存在mappingRegistry.urlLookup实例变量中),之后在请求到来时通过请求URI从mappingRegistry获取
  2. 如果在使用路径参数@PathVariables的时候,需要遍历所有Controller方法(因为初始化的时候是以/user/{name}作为key保存在urlLookup变量中的,而实际的请求路径是/user/tom。无法通过this.mappingRegistry.getMappingsByUrl(lookupPath)直接获取到)。这里会涉及到Ant风格解析。O(n)的时间复杂度。效率极低
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
// 如果没有找到,则遍历所有Controller方法。这里会涉及到Ant风格解析。O(n)的时间复杂度。效率极低
// 在使用路径参数@PathVariables的时候,会进入到这段逻辑
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
// 设置属性到ServletHttpRequest实例中,在解析@PathVariables时使用
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
super.handleMatch(info, lookupPath, request);

String bestPattern;
Map<String, String> uriVariables;
Map<String, String> decodedUriVariables;

Set<String> patterns = info.getPatternsCondition().getPatterns();
if (patterns.isEmpty()) {
bestPattern = lookupPath;
uriVariables = Collections.emptyMap();
decodedUriVariables = Collections.emptyMap();
}
else {
bestPattern = patterns.iterator().next();
// 使用Ant风格解析路径参数
uriVariables = getPathMatcher().extractUriTemplateVariables(bestPattern, lookupPath);
decodedUriVariables = getUrlPathHelper().decodePathVariables(request, uriVariables);
}

request.setAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE, bestPattern);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, decodedUriVariables);

if (isMatrixVariableContentAvailable()) {
Map<String, MultiValueMap<String, String>> matrixVars = extractMatrixVariables(request, uriVariables);
request.setAttribute(HandlerMapping.MATRIX_VARIABLES_ATTRIBUTE, matrixVars);
}

if (!info.getProducesCondition().getProducibleMediaTypes().isEmpty()) {
Set<MediaType> mediaTypes = info.getProducesCondition().getProducibleMediaTypes();
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}

HandlerMethod执行细节

参数解析以及类型转换

主要是通过HandlerMethodArgumentResolverComposite这个特殊的HandlerMethodArgumentResolver聚合了多个HandlerMethodArgumentResolver来解析参数、其内部将调用RequestParamMethodArgumentResolver、PathVariableMapMethodArgumentResolver等来处理参数解析逻辑

返回结果处理

主要是通过HandlerMethodReturnValueHandlerComposite这个特殊的HandlerMethodReturnValueHandler聚合了多个HandlerMethodReturnValueHandler来处理返回结果,其内部将调用RequestResponseBodyMethodProcessor处理@ResponseBody、@RestController标记的方法和类来完成RESTful接口的结果返回

总结

以上就是整个springmvc的工作流程,包括了初始化、请求处理两个步骤。在请求处理的过程中又涉及到了参数解析、参数类型转换、返回结果处理这些流程。由于整个代码比较繁多,这里就不列举所有的流程(视图渲染、异常处理这些细节)通过本篇的主体流程图可以快速定位到具体的代码。后续有需要查看更多细节可参考本篇快速定位