0%

SpringMVC 篇(一)DispatcherServlet 初始化

引言

SpringMVC 的出现大大方便了 Java Web 领域的开发,开发一个 Web 接口几个注解就完事了。而 SpringBoot 的出现又进一步提升了我们的开发效率。在这一层层背后,你可曾想过它们到底是如何实现的?我相信肯定是有的,但是面对它庞大的代码体系,让人望而却步。不过不要紧,我来带大家进入 SpringBoot 的源码世界,见证一下这个 “艺术品” 是如何跑起来的。

DispatcherServlet

通过名字我们可以看出它就是一个 Servlet,而恰恰这个 Servlet 就是 SpringMVC 的核心。我们从它开始入手,抽丝剥茧,看看它到底是怎么对请求进行加工处理的。

  • 继承链

  • 启动

DispatcherServlet

1
2
3
4
5
6
7
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}

这个方法是我们首先要关注的方法,因为它完成了 DispatcherServlet 相关资源的初始化,看它调用的方法 initStrategies(context); 也能看出端倪。我们先不着急往下看,我们想一想谁调用的 onRefresh() 方法呢?我们通过它的注解发现它是一个继承下来的方法,好,我们往它的上游看。

FrameworkServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Callback that receives refresh events from this servlet's WebApplicationContext.
* <p>The default implementation calls {@link #onRefresh},
* triggering a refresh of this servlet's context-dependent state.
* @param event the incoming ApplicationContext event
*/
public void onApplicationEvent(ContextRefreshedEvent event) {
this.refreshEventReceived = true;
onRefresh(event.getApplicationContext());
}
/**
* Template method which can be overridden to add servlet-specific refresh work.
* Called after successful context refresh.
* <p>This implementation is empty.
* @param context the current WebApplicationContext
* @see #refresh()
*/
protected void onRefresh(ApplicationContext context) {
// For subclasses: do nothing by default.
}

我们发现 onRefresh() 方法是个空方法,而它的调用者就在它的上边,即:onApplicationEvent(ContextRefreshedEvent event),通过名字我们可以看出它是一个事件方法,当上下文刷新的时候,该方法会被触发。但是 FrameworkServlet并没有实现任何相关的监听器,是如何完成事件监听的呢?不要紧,我们大概看看这个类的结构,看看能发现什么端倪不。我们发现它有一个内部类,而这个内部类实现了监听器,而实现的监听方法恰恰调用了**onApplicationEvent(ContextRefreshedEvent event)**。

1
2
3
4
5
6
7
8
9
10
11
/**
* ApplicationListener endpoint that receives events from this servlet's WebApplicationContext
* only, delegating to {@code onApplicationEvent} on the FrameworkServlet instance.
*/
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}

看到这里的时候,信心满满的,但是当你启动准备验证的时候,发现,不对。好像并不是通过事件触发 onRefresh() 的。为什么?如果没有事件产生,那么该方法肯定就无法触发了。所以它肯定还有别的调用入口,我们继续往下看。

FrameworkServlet

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
   /**
* Initialize and publish the WebApplicationContext for this servlet.
* <p>Delegates to {@link #createWebApplicationContext} for actual creation
* of the context. Can be overridden in subclasses.
* @return the WebApplicationContext instance
* @see #FrameworkServlet(WebApplicationContext)
* @see #setContextClass
* @see #setContextConfigLocation
*/
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;

if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}

if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}

if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}

return wac;
}

我们把焦点放到这行代码上:

1
2
3
4
5
6
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}

也就是说,如果事件没有成功发布或者发布较早,那么需要手动调用 onRefresh(wac) 方法(也间接说明了,事件并不靠谱)。大体的调用关系链可以按照如下的方式分析:

1
servlet.init() -> GenericServlet.init() -> HttpServletBean.init() -> HttpServletBean.initServletBean() -> FrameworkSerlvet.initServletBean() -> FrameworkSerlvet.initWebApplicationContext() -> FrameworkSerlvet.onRefresh(wac)

📚 总结

onRefresh() 方法的调用有两个入口,一个是通过上下文事件触发,一个是手动触发(当事件触发失败的时候)。而手动触发的顶端则是 servlet 的 init() 方法。

组件初始化

当 onRefresh() 方法被触发的时候,组件的初始化工作就展开了。在开始之前,我们先重点关注一个配置文件,那就是跟 DispatcherServlet 同一级的 DispatcherServlet.properties 文件。该文件存放的就是组件的默认实现。

DispatcherServlet.properties

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
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
org.springframework.web.servlet.function.support.HandlerFunctionAdapter


org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

DispatcherServlet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 文件上传解析器
initMultipartResolver(context);
// 本地化解析器
initLocaleResolver(context);
// 主题解析器
initThemeResolver(context);
// 处理器映射器(url 和 Controller 方法的映射)
initHandlerMappings(context);
// 处理器适配器(实际执行 Controller 方法)
initHandlerAdapters(context);
// 处理器异常解析器
initHandlerExceptionResolvers(context);
// RequestToViewName 解析器
initRequestToViewNameTranslator(context);
// 视图解析器(视图的匹配和渲染)
initViewResolvers(context);
// FlashMap 管理器
initFlashMapManager(context);
}

由于各个组件的加载完全一样,所以这里我只选择 initHandlerMappings(context) 进行详细的介绍,该方法加载 HandlerMapping.class 的实例,也就是处理 urlcontroller 的映射。

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
   /**
* 实例化该类所使用的 HandlerMappings 映射器,如果该命名空间下(父子容器中的子容器)的 bean 容器中没有该 bean 的定义,那么默认会分配一个 BeanNameUrlHandlerMapping 映射器。
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;

if (this.detectAllHandlerMappings) {
// 从当前容器及它的父容器中获取对应的 bean 对象。
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// 根据 @Order 进行排序。
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 根据名字及类型取出对应的 bean 对象。
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}

// 如果没有映射器被发现,那么会从配置文件中获取出一个默认的映射器。
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}

到这里 HandlerMapping 类型的对象已经加载完毕了,为了进行进一步说明,我们以 RequestMappingHandlerMapping 的加载为例进行详细的说明。为什么讲它呢?它就是处理 urlcontroller 映射的处理器。废话不多说,我们开始吧(细心的话,你会发现在进行检索的时候,有一个父容器的存在,这就是 Web 中的父子容器,至于什么时候加载的父容器,大家可以回想一下先前的文章)!
RequestMappingHandlerMapping 通过它的父类间接实现了 InitializingBean 接口,该接口在之前介绍容器的时候已经知道它的具体作用,所以这里我们不多说,直接进入接口方法看看:

RequestMappingHandlerMapping

1
2
3
4
5
6
7
8
9
10
11
12
13
   @Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(useSuffixPatternMatch());
this.config.setTrailingSlashMatch(useTrailingSlashMatch());
this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
this.config.setContentNegotiationManager(getContentNegotiationManager());

super.afterPropertiesSet();
}

这里我们直接进入它的父方法看看:

AbstractHandlerMethodMapping

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
   // Handler method detection

/**
* 初始化时检测 handler
* @see #initHandlerMethods
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}

/**
* 从容器中检索出已经注册好的 handler bean。
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
/**
* 检测 object 类型的 bean,并将 bean 的名称放到数组里。
* @since 5.1
* @see #setDetectHandlerMethodsInAncestorContexts
* @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors
*/
protected String[] getCandidateBeanNames() {
return (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
}

/**
* 这里只处理 @Controller@RequestMapping 修饰的 bean
*/
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
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);
}
}
// 这里就是用来筛选 @Controller 或 @RequestMapping 修饰的类
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}

@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

通过以上代码,我们可以看出,把 @Controller 或者 @RequestMapping 修饰的 bean 筛选出来,筛选出来之后做什么呢?我们继续往下看:

AbstractHandlerMethodMapping

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
   // 这里的 handler 就是每个 controller 类的名字(并非全限定名)
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());

if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
// 这里 Method 就是具体的方法全限定名,而 T 就是该方法映射信息(HTTP 方法 + URL)
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
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);
// 进行注册,handler 是 controller 类的简称,invocableMethod 目标方法对象,mapping 是接口映射信息(HTTP 方法 + URL)
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}

protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
// 注册 URL 与 Controller 方法
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
// 将 mapping 信息与方法对应起来
this.mappingLookup.put(mapping, handlerMethod);

List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
// 这里将 url 跟 mapping 信息对应起来
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();
}
}

以上代码就是保存 URL 及对应的 Controller 方法,这里分了两个集合,一个是存放 URL 对应的 mapping 信息(包括了 HTTP 方法及 URL 信息 ),一个是存放 mapping 信息及对应的 Controller 方法。这里也用到了读写锁的写锁,大家可以分析一下为什么要上锁。


尾声

这篇文章主要讲解了 Dispatcher 的初始化过程,然后以 HandlerMapping 为例讲解了 Controller 方法的映射。接下来的文章【SpringMVC 篇(二)DispatcherServlet 请求】将会介绍请求发生的时候,Dispatcher 是如何处理请求的,以及各个消息转换器、异步处理器、拦截器之间的协作方式。