1. 程式人生 > 其它 >Spring原始碼解析 -- SpringWeb請求對映Map初始化

Spring原始碼解析 -- SpringWeb請求對映Map初始化

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

簡介

在上篇文章中,大致解析了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

參考連結