引言
SpringMVC 的出現(xiàn)大大方便了 Java Web 領(lǐng)域的開發(fā)扮碧,開發(fā)一個 Web 接口幾個注解就完事了螺句。而 SpringBoot 的出現(xiàn)又進一步提升了我們的開發(fā)效率涎劈。在這一層層背后缤削,你可曾想過它們到底是如何實現(xiàn)的瘦棋?我相信肯定是有的,但是面對它龐大的代碼體系鲜棠,讓人望而卻步肌厨。不過不要緊,我來帶大家進入 SpringBoot 的源碼世界豁陆,見證一下這個 “藝術(shù)品” 是如何跑起來的柑爸。
DispatcherServlet
通過名字我們可以看出它就是一個 Servlet,而恰恰這個 Servlet 就是 SpringMVC 的核心盒音。我們從它開始入手表鳍,抽絲剝繭,看看它到底是怎么對請求進行加工處理的祥诽。
-
繼承鏈
-
啟動
DispatcherServlet
/**
* This implementation calls {@link #initStrategies}.
*/
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
這個方法是我們首先要關(guān)注的方法譬圣,因為它完成了 DispatcherServlet 相關(guān)資源的初始化,看它調(diào)用的方法 initStrategies(context); 也能看出端倪雄坪。我們先不著急往下看胁镐,我們想一想誰調(diào)用的 onRefresh() 方法呢蹭睡?我們通過它的注解發(fā)現(xiàn)它是一個繼承下來的方法电谣,好,我們往它的上游看去件。
FrameworkServlet
/**
* 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.
}
我們發(fā)現(xiàn) onRefresh() 方法是個空方法笨农,而它的調(diào)用者就在它的上邊就缆,即:onApplicationEvent(ContextRefreshedEvent event),通過名字我們可以看出它是一個事件方法,當(dāng)上下文刷新的時候谒亦,該方法會被觸發(fā)竭宰。但是 FrameworkServlet并沒有實現(xiàn)任何相關(guān)的監(jiān)聽器,是如何完成事件監(jiān)聽的呢份招?不要緊切揭,我們大概看看這個類的結(jié)構(gòu),看看能發(fā)現(xiàn)什么端倪不锁摔。我們發(fā)現(xiàn)它有一個內(nèi)部類廓旬,而這個內(nèi)部類實現(xiàn)了監(jiān)聽器,而實現(xiàn)的監(jiān)聽方法恰恰調(diào)用了onApplicationEvent(ContextRefreshedEvent event)谐腰。
/**
* 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);
}
}
看到這里的時候孕豹,信心滿滿的,但是當(dāng)你啟動準(zhǔn)備驗證的時候十气,發(fā)現(xiàn)励背,不對。好像并不是通過事件觸發(fā) onRefresh() 的砸西。為什么叶眉?如果沒有事件產(chǎn)生址儒,那么該方法肯定就無法觸發(fā)了。所以它肯定還有別的調(diào)用入口衅疙,我們繼續(xù)往下看莲趣。
FrameworkServlet
/**
* 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;
}
我們把焦點放到這行代碼上:
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);
}
也就是說,如果事件沒有成功發(fā)布或者發(fā)布較早炼蛤,那么需要手動調(diào)用 onRefresh(wac) 方法(也間接說明了妖爷,事件并不靠譜)蝶涩。大體的調(diào)用關(guān)系鏈可以按照如下的方式分析:
servlet.init() -> GenericServlet.init() -> HttpServletBean.init() -> HttpServletBean.initServletBean() -> FrameworkSerlvet.initServletBean() -> FrameworkSerlvet.initWebApplicationContext() -> FrameworkSerlvet.onRefresh(wac)
總結(jié)
onRefresh() 方法的調(diào)用有兩個入口理朋,一個是通過上下文事件觸發(fā),一個是手動觸發(fā)(當(dāng)事件觸發(fā)失敗的時候)绿聘。而手動觸發(fā)的頂端則是 servlet 的 init() 方法嗽上。
組件初始化
當(dāng) onRefresh() 方法被觸發(fā)的時候,組件的初始化工作就展開了熄攘。在開始之前兽愤,我們先重點關(guān)注一個配置文件,那就是跟 DispatcherServlet 同一級的 DispatcherServlet.properties 文件。該文件存放的就是組件的默認(rèn)實現(xiàn)挪圾。
DispatcherServlet.properties
# 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
/**
* 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);
// 處理器適配器(實際執(zhí)行 Controller 方法)
initHandlerAdapters(context);
// 處理器異常解析器
initHandlerExceptionResolvers(context);
// RequestToViewName 解析器
initRequestToViewNameTranslator(context);
// 視圖解析器(視圖的匹配和渲染)
initViewResolvers(context);
// FlashMap 管理器
initFlashMapManager(context);
}
由于各個組件的加載完全一樣浅萧,所以這里我只選擇 initHandlerMappings(context) 進行詳細(xì)的介紹,該方法加載 HandlerMapping.class 的實例哲思,也就是處理 url 與 controller 的映射洼畅。
/**
* 實例化該類所使用的 HandlerMappings 映射器,如果該命名空間下(父子容器中的子容器)的 bean 容器中沒有該 bean 的定義棚赔,那么默認(rèn)會分配一個 BeanNameUrlHandlerMapping 映射器帝簇。
*/
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// 從當(dāng)前容器及它的父容器中獲取對應(yīng)的 bean 對象。
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// 根據(jù) @Order 進行排序靠益。
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
// 根據(jù)名字及類型取出對應(yīng)的 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.
}
}
// 如果沒有映射器被發(fā)現(xiàn),那么會從配置文件中獲取出一個默認(rèn)的映射器胧后。
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 類型的對象已經(jīng)加載完畢了芋浮,為了進行進一步說明,我們以 RequestMappingHandlerMapping 的加載為例進行詳細(xì)的說明壳快。為什么講它呢途样?它就是處理 url 及 controller 映射的處理器。廢話不多說濒憋,我們開始吧(細(xì)心的話何暇,你會發(fā)現(xiàn)在進行檢索的時候,有一個父容器的存在凛驮,這就是 Web 中的父子容器裆站,至于什么時候加載的父容器,大家可以回想一下先前的文章)!
RequestMappingHandlerMapping 通過它的父類間接實現(xiàn)了 InitializingBean 接口宏胯,該接口在之前介紹容器的時候已經(jīng)知道它的具體作用羽嫡,所以這里我們不多說,直接進入接口方法看看:
RequestMappingHandlerMapping
@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<T>
// Handler method detection
/**
* 初始化時檢測 handler
* @see #initHandlerMethods
*/
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* 從容器中檢索出已經(jīng)注冊好的 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 的名稱放到數(shù)組里。
* @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 篩選出來,篩選出來之后做什么呢艰管?我們繼續(xù)往下看:
AbstractHandlerMethodMapping<T>
// 這里的 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 目標(biāo)方法對象撩笆,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 信息與方法對應(yīng)起來
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
// 這里將 url 跟 mapping 信息對應(yīng)起來
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 及對應(yīng)的 Controller 方法,這里分了兩個集合缸浦,一個是存放 URL 對應(yīng)的 mapping 信息(包括了 HTTP 方法及 URL 信息 )夕冲,一個是存放 mapping 信息及對應(yīng)的 Controller 方法。這里也用到了讀寫鎖的寫鎖裂逐,大家可以分析一下為什么要上鎖歹鱼。
尾聲
這篇文章主要講解了 Dispatcher 的初始化過程,然后以 HandlerMapping 為例講解了 Controller 方法的映射絮姆。接下來的文章【SpringMVC 篇(二)DispatcherServlet 請求】將會介紹請求發(fā)生的時候醉冤,Dispatcher 是如何處理請求的,以及各個消息轉(zhuǎn)換器篙悯、異步處理器蚁阳、攔截器之間的協(xié)作方式。