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邏輯。後面還有把這種對映關係儲存起來、還有定義攔截器什麼的,我們後面會一點一點來說。