Spring原始碼解析 -- SpringWeb請求對映Map初始化
簡介
在上篇文章中,大致解析了Spring如何將請求路徑與處理方法進行對映,但對映相關的初始化對於我們來說還是一團迷霧
本篇文章就來探索下,請求路徑和處理方法的對映,是如何進行初始化的
概覽
基於上篇文章:Spring 原始碼解析 -- SpringWeb請求對映解析
本篇文章本來想早點寫完,但一直卡著,沒有找到想要的切入點,還好在週四左右定位到了相關的關鍵程式碼,初步探索到相關初始化的程式碼過程
接下來就展示這段時間的定位和解析過程,下面是自己這段時間探索歷程:
- 想定位 handlerMappings 的初始化,但沒有定位請求URL和處理方法相關初始化的東西
- 回過來頭來看 handlerMappings ,看其有哪些東西,發現這個Map中並沒有自定義的HelloWorld
- 意識到關鍵的 RequestMappingHandlerMapping,跟蹤傳送只有在這個型別才成功匹配
- 回顧上篇的請求對映解析,在 RequestMappingHandlerMapping 細緻初始化相關的程式碼
- 成功找到相關的路徑和處理方法初始化的關鍵程式碼
接下來詳細看下:
原始碼解析
初步探索初始化:誤入歧途
在類: DispatcherServlet.java 中,我們定位到 mappedHandler 獲取的關鍵程式碼
@Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
它就是遍歷了: handlerMappings ,於是去跟蹤了 handlerMappings 初始化過程
結果失敗而歸,沒有找到自己想要的東西,並沒有發現自定義類: HelloWorld 的相關東西
這塊有很多的程式碼,裡面也有很多的東西,但在這裡就不展示出來了,感興趣的老哥可自行探索
回顧請求對映查詢匹配:幡然醒悟
探索 handlerMappings 無果,於是回到上面那段遍歷處理
經過除錯 handlerMappings 基本是固定的,包含下面的類:
- this.handlerMappings
- RequestMappingHandlerMapping
- BeanNameUrlHandlerMapping
- RouterFunctionMapping
- SimpleUrlHandlerMapping
- WelcomePageHandlerMapping
而匹配的成功的是: RequestMappingHandlerMapping ,其返回了我們想要的處理方法 HelloWorld
除錯中很疑惑為啥初期要初始化這幾個了,並且再套了一層請求匹配,目前掌握的知識還不足於破解,只能後面再探索了
於是開始梳理 RequestMappingHandlerMapping 的請求匹配,在下面的一段關鍵程式碼匹配成功了:
# AbstractHandlerMethodMapping.java
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
// 在這裡拿到了匹配結果
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
.......
}
在上面的程式碼中就匹配成功了,其中匹配的方法很簡單粗暴:
# AbstractHandlerMethodMapping.java -- MappingRegistry
@Nullable
public List<T> getMappingsByDirectPath(String urlPath) {
return this.pathLookup.get(urlPath);
}
於是關鍵點到了 this.mappingRegistry 的初始化,找到初始化的程式碼,打上斷點
期間以為是在類:AbstractHandlerMethodMapping 中進行的初始的,在下面的函式打上了斷點:
# AbstractHandlerMethodMapping.java
public void setPatternParser(PathPatternParser patternParser) {
Assert.state(this.mappingRegistry.getRegistrations().isEmpty(),
"PathPatternParser must be set before the initialization of " +
"request mappings through InitializingBean#afterPropertiesSet.");
super.setPatternParser(patternParser);
}
public void registerMapping(T mapping, Object handler, Method method) {
if (logger.isTraceEnabled()) {
logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
}
this.mappingRegistry.register(mapping, handler, method);
}
但一直進不去,於是直接在其定義的內部類中: MappingRegistry 中進行尋找,併成功定位到想要的關鍵程式碼
請求對映關鍵程式碼定位:柳暗花明
閱讀類: MappingRegistry 的相關程式碼,發現下面的方法和可以,我們直接打上斷點,重啟程式:
發現了前面的: this.pathLookup 的相關新增操作
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
// 這段程式碼是關鍵
this.pathLookup.add(path, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
應用重啟後,果然順利來到我們打上的斷點處,通過分析呼叫棧,我們確實找到了請求對映的關鍵程式碼
我們將呼叫棧從下網上分析檢視:
應用啟動相關
開始就是熟悉Spring啟動相關,這些程式碼相信大家嘗試閱讀原始碼的時候讀過很多遍了
跟蹤發現在: DefaultListableBeanFactory.class 的 preInstantiateSingletons 方法中個,一大段巢狀迴圈,心想這段程式碼目前能優化嗎?
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
public static void main(String[] args) throws Exception {
run(new Class[0], args);
}
public ConfigurableApplicationContext run(String... args) {
......
try {
......
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
// 從下面這個進入
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
......
} catch (Throwable var10) {
......
this.handleRunFailure(context, var10, listeners);
throw new IllegalStateException(var10);
}
......
}
public void refresh() throws BeansException, IllegalStateException {
synchronized(this.startupShutdownMonitor) {
.......
try {
this.postProcessBeanFactory(beanFactory);
StartupStep beanPostProcess = this.applicationStartup.start("spring.context.beans.post-process");
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
beanPostProcess.end();
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
// 從這裡進入
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var10) {
} finally {
......
}
......
}
}
RequestMappingHandlerMapping 相關初始化
繼續跟蹤下面的,看到了屬性的CreateBean和afterPropertiesSet
# AbstractAutowireCapableBeanFactory.class
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {
......
try {
// 這裡初始化了 RequestMappingHandlerMapping
beanInstance = this.doCreateBean(beanName, mbdToUse, args);
if (this.logger.isTraceEnabled()) {
this.logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
} catch (ImplicitlyAppearedSingletonException | BeanCreationException var7) {
throw var7;
} catch (Throwable var8) {
throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", var8);
}
}
# AbstractAutowireCapableBeanFactory.class
protected void invokeInitMethods(String beanName, Object bean, @Nullable RootBeanDefinition mbd) throws Throwable {
boolean isInitializingBean = bean instanceof InitializingBean;
if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
}
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(() -> {
((InitializingBean)bean).afterPropertiesSet();
return null;
}, this.getAccessControlContext());
} catch (PrivilegedActionException var6) {
throw var6.getException();
}
} else {
// 這裡進入請求對映的相關操作
((InitializingBean)bean).afterPropertiesSet();
}
}
......
}
請求對映初始化
繼續跟蹤下去,看看了迴圈遍歷Controllers相關的程式碼(還有很多細節沒搞清,後面再繼續了,先梳理主線)
# AbstractHandlerMethodMapping.java
@Override
public void afterPropertiesSet() {
// 初始化請求對映
initHandlerMethods();
}
protected void initHandlerMethods() {
// 遍歷所有的自定義的Controllers,後面自己又定義了一個Controllers
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 在這裡看到了我們定義的HelloWorld
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected String[] getCandidateBeanNames() {
return (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :
obtainApplicationContext().getBeanNamesForType(Object.class));
}
繼續跟蹤下去,看到了下面的獲取類中具體請求路徑相關的程式碼,並且到了具體的初始化請求對映的程式碼
# AbstractHandlerMethodMapping.java
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);
}
}
if (beanType != null && isHandler(beanType)) {
// 得到Controller Bean後的入口
detectHandlerMethods(beanName);
}
}
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
// 處理得到所有的Controllers method
Class<?> userType = ClassUtils.getUserClass(handlerType);
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));
}
else if (mappingsLogger.isDebugEnabled()) {
mappingsLogger.debug(formatMappings(userType, methods));
}
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
// 註冊
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
validateMethodMapping(handlerMethod, mapping);
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
for (String path : directPaths) {
// 對映新增
this.pathLookup.add(path, mapping);
}
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
總結
經過一段時間的探索的整理,我們終於得到了大致的請求路徑對映初始化的程式碼
- 1.應用啟動時,初始化:RequestMappingHandlerMapping
- 2.RequestMappingHandlerMapping 中請求路徑初始化
經過除錯,我們還發現雖然 RequestMappingHandlerMapping 是一開始就初始化了,但載入到 handlerMappings 是第一次請求的時候才載入進去的
本篇雖然得到了大致的請求路徑初始化的程式碼,但其中有很多細節是值得探索的,比如Bean中Method的處理
之前自己寫過一些DI和Web相關的Demo,停在了Servlet,卡在了請求對映初始化和匹配,這個給了我一些思路,後面詳細看看這塊程式碼,完善下之前的Demo