importjava,Spring源碼解析 -- SpringWeb請求映射Map初始化

 2023-10-12 阅读 32 评论 0

摘要:簡介 在上篇文章中,大致解析了Spring如何將請求路徑與處理方法進行映射,但映射相關的初始化對于我們來說還是一團迷霧 本篇文章就來探索下,請求路徑和處理方法的映射,是如何進行初始化的 概覽 基于上篇文章:Spring 源碼解析 – SpringWeb請

簡介

在上篇文章中,大致解析了Spring如何將請求路徑與處理方法進行映射,但映射相關的初始化對于我們來說還是一團迷霧

本篇文章就來探索下,請求路徑和處理方法的映射,是如何進行初始化的

概覽

基于上篇文章:Spring 源碼解析 – SpringWeb請求映射解析

本篇文章本來想早點寫完,但一直卡著,沒有找到想要的切入點,還好在周四左右定位到了相關的關鍵代碼,初步探索到相關初始化的代碼過程

importjava、接下來就展示這段時間的定位和解析過程,下面是自己這段時間探索歷程:

  • 想定位 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 的相關東西

import java.lang?這塊有很多的代碼,里面也有很多的東西,但在這里就不展示出來了,感興趣的老哥可自行探索

回顧請求映射查找匹配:幡然醒悟

探索 handlerMappings 無果,于是回到上面那段遍歷處理

經過調試 handlerMappings 基本是固定的,包含下面的類:

  • this.handlerMappings
    • RequestMappingHandlerMapping
    • BeanNameUrlHandlerMapping
    • RouterFunctionMapping
    • SimpleUrlHandlerMapping
    • WelcomePageHandlerMapping

而匹配的成功的是: RequestMappingHandlerMapping ,其返回了我們想要的處理方法 HelloWorld

調試中很疑惑為啥初期要初始化這幾個了,并且再套了一層請求匹配,目前掌握的知識還不足于破解,只能后面再探索了

java bean轉map?于是開始梳理 RequestMappingHandlerMapping 的請求匹配,在下面的一段關鍵代碼匹配成功了:

    # AbstractHandlerMethodMapping.java@Nullableprotected 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@Nullablepublic List<T> getMappingsByDirectPath(String urlPath) {return this.pathLookup.get(urlPath);}

于是關鍵點到了 this.mappingRegistry 的初始化,找到初始化的代碼,打上斷點

期間以為是在類:AbstractHandlerMethodMapping 中進行的初始的,在下面的函數打上了斷點:

    # AbstractHandlerMethodMapping.javapublic 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 中進行尋找,并成功定位到想要的關鍵代碼

請求映射關鍵代碼定位:柳暗花明

map轉bean對象。閱讀類: 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啟動相關,這些代碼相信大家嘗試閱讀源碼的時候讀過很多遍了

Springboot?跟蹤發現在: 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.classprotected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {......try {// 這里初始化了 RequestMappingHandlerMappingbeanInstance = 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.classprotected 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@Overridepublic void afterPropertiesSet() {// 初始化請求映射initHandlerMethods();}protected void initHandlerMethods() {// 遍歷所有的自定義的Controllers,后面自己又定義了一個Controllersfor (String beanName : getCandidateBeanNames()) {if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {// 在這里看到了我們定義的HelloWorldprocessCandidateBean(beanName);}}handlerMethodsInitialized(getHandlerMethods());}protected String[] getCandidateBeanNames() {return (this.detectHandlerMethodsInAncestorContexts ?BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) :obtainApplicationContext().getBeanNamesForType(Object.class));}

繼續跟蹤下去,看到了下面的獲取類中具體請求路徑相關的代碼,并且到了具體的初始化請求映射的代碼

    # AbstractHandlerMethodMapping.javaprotected 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 methodClass<?> 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 中請求路徑初始化

java map reduce、經過調試,我們還發現雖然 RequestMappingHandlerMapping 是一開始就初始化了,但加載到 handlerMappings 是第一次請求的時候才加載進去的

本篇雖然得到了大致的請求路徑初始化的代碼,但其中有很多細節是值得探索的,比如Bean中Method的處理

之前自己寫過一些DI和Web相關的Demo,停在了Servlet,卡在了請求映射初始化和匹配,這個給了我一些思路,后面詳細看看這塊代碼,完善下之前的Demo

參考鏈接

  • Spring 源碼解析系列

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/135554.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息