Spring MVC學習

我有一口玄黄气發表於2024-03-24

Spring MVC學習


今天開始學spring mvc原始碼咯,先看一下和原生的servlet有啥聯絡吧。可以看到Spring實現了Servlet介面,讓自己的DispatcherServlet來響應web請求和返回資料。

順便讓我想看一下spring裡面Application的一個繼承圖,裡面有很多實現

順便再看一下儲存bean的BeanFactory繼承體系是咋樣的,在spring專案中預設使用的BeanFactory是DefaultListableBeanFactory

有點忘記java物件例項化出來的順序了,在oracle官方文件裡面找到,於是把這個記下來,可以看到,例項化是排在初始化之前的,這樣想到了spring框架裡面的BeanPostProcessor介面和其中一個子介面InstantiationAwreBeenPostProcessor

可以看到子介面多了兩個關於bean例項化的方法,當Spring容器啟動過程中,會有一些階段呼叫BeanPostProcessor的實現類,在spring構建例項bean的過程中,截圖中的postProcessBeforeInstantiation方法和postProcessAfterInstantiation方法會比postProcessBeforeInitialization和postAfterInitialization方法先執行,這四個方法都是用於spring要對某些bean做一下額外動作的場景。

Spring在啟動時候也需要選擇正確的ApplicationContext實現,這裡官網上有一個簡單的描述

言歸正傳,我們知道spring是透過DispatcherServlet類來響應請求的,檢視原始碼,裡面對servlet的初始化程式碼如下,看到方法名可以很輕易地推測出作用,比如初始化檔案上傳解析器、國際化解析器、檢視解析器、請求對映器、請求介面卡、請求異常解析器等等。先重點看一下initHandlerMappings方法,這個方法是為了尋找到request應該由哪個物件來處理的。Spring預設會初始化三個HandlerMapping的實現,一個是BeanNameUrlHandlerMapping,RequestMappingHandlerMapping,RouterFunctionMapping;我們主要介紹RequestMappingHandlerMapping.

	/**
	 * This implementation calls {@link #initStrategies}.
	 */
	@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	/**
	 * 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);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

所以這裡列一下RequestMappingHandlerMapping的類繼承圖

需要找到能夠處理請求的物件,大家在平常開發的時候想必已經很熟悉了,對於spring web應用,響應請求的物件上面都會加上@Controller註解,然後對應一些url的匹配方法上都要寫@RequestMapping註解;Spring框架的做法是找到擋牆容器中所有的bean,然後判斷某個bean是否標註有上述兩個註解;同樣的,這樣的邏輯也是透過上述繼承圖裡面的抽象類實現的,這個邏輯藏在圖中的AbstractHandlerMethodMapping類中,也可以看到,這個類繼承了InitializingBean。

public interface InitializingBean {

	/**
	 * Invoked by the containing {@code BeanFactory} after it has set all bean properties
	 * and satisfied {@link BeanFactoryAware}, {@code ApplicationContextAware} etc.
	 * <p>This method allows the bean instance to perform validation of its overall
	 * configuration and final initialization when all bean properties have been set.
	 * @throws Exception in the event of misconfiguration (such as failure to set an
	 * essential property) or if initialization fails for any other reason
	 */
	void afterPropertiesSet() throws Exception;

}

這個物件我們不會陌生,因為在Sping框架啟動時會在bean的生命週期某個階段呼叫這個介面的實現類,用於對bean做一些自定義操作,而AbstractHandlerMethodMapping自然也會被呼叫了,所以我們看看是如何實現的, getCandidateBeanNames就是把當前容器中所有的bean拿到,遍歷bean,一個個處理,如果判斷一個bean是被@Controller或者@RequestMapping註解標註,就進入尋找handlerMethod的邏輯;

@Override
	public void afterPropertiesSet() {
		initHandlerMethods();
	}

	/**
	 * Scan beans in the ApplicationContext, detect and register handler methods.
	 * @see #getCandidateBeanNames()
	 * @see #processCandidateBean
	 * @see #handlerMethodsInitialized
	 */
	protected void initHandlerMethods() {
		for (String beanName : getCandidateBeanNames()) {
			if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
				processCandidateBean(beanName);
			}
		}
		handlerMethodsInitialized(getHandlerMethods());
	}

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)) {
			detectHandlerMethods(beanName);
		}
	}




繼續看一看isHandler方法,這個方法的實現在RequestMappingHandlerMapping類裡面,簡單明瞭。

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

而detectHandlerMethods方法就比較複雜,大體邏輯是拿到bean物件,遍歷bean物件裡面的方法判斷是不是handler方法,如果是的話還要看類物件上面有沒有@RequestMapping註解,有的話要將類物件和方法上@RequestMapping註解裡面匹配的url拼接起來,拼接過後的完整url才是可以對映的請求地址。

	/**
	 * Look for handler methods in the specified handler bean.
	 * @param handler either a bean name or an actual handler instance
	 * @see #getMappingForMethod
	 */
	protected void detectHandlerMethods(Object handler) {
		Class<?> handlerType = (handler instanceof String ?
				obtainApplicationContext().getType((String) handler) : handler.getClass());

		if (handlerType != null) {
			Class<?> userType = ClassUtils.getUserClass(handlerType);
            // 這裡就是遍歷這個類裡面所有的方法,看一下是不是被@RequestMapping註解標註
			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);
			});
		}
	}

今天就先到這裡好了,我們現在已經知道什麼Spring框架使用RequestMappingHandlerMapping物件來處理將外部的請求找到對應的handler邏輯。後面還有把這種對映關係儲存起來、還有定義攔截器什麼的,我們後面會一點一點來說。

相關文章