前言
是Java開發(fā)的小伙伴就一定會使用SpringMVC
,沒有SpringBoot
的年代或許我們還需要配置一些xml
文件疆导。但是到了SpringBoot
時代,Java程序員只需要使用@Controller
悠菜、@RequestMapping
等注解就OK了败富,一切都變得非常簡單兽叮,你只需要專注業(yè)務(wù)代碼就可以。但是專注業(yè)務(wù)代碼的同時账阻,我們還是需要了解這背后的@Controller
泽本,@RequestMapping
背后的故事。
SpringMVC啟動涉及解析Controller&RequestMapping的步驟
- 獲取
Spring
工廠中所有的Bean
蒲牧。 - 在所有的
Bean
中找出被@Controller
或@RequestMapping
注解的類,比如說找到A
類显熏。 - 獲取
A
類滿足以下條件的方法:public
方法晒屎、被@RequestMapping
注解標記的方法鼓鲁,比如找到方法b
。(父類的方法也會被找出來)橙弱。 - 獲取方法
b
的RequestMapping
信息燥狰,獲取A
類上的RequestMapping
(如果有的話)信息,將兩者信息進行合并新的RequestMapping
蛀缝。(比如組合路徑目代,類: /a, 方法:/b ->組合 /a/b) - 方法
b
和A
類的beanName
(或者A
類對象)構(gòu)建HandlerMethod
在讶,以合并的新RequestMapping
信息為key霜大,以HandlerMethod
為value
存入內(nèi)存中。
數(shù)據(jù)存儲如下
{"requestMapping合并信息":"handlerMethod"}
介紹完基本步驟后,接下來就是在源碼中把關(guān)鍵的代碼找出來就可以了湖笨。
入口
這里先介紹下SpringMVC
加載這些步驟的入口-RequestMappingHandlerMapping
慈省,這個類負責加載這些步驟,所以大家可以打開RequestMappingHandlerMapping
這個類一起跟看源碼袱衷。
詳解
入口方法是在RequestMappingHandlerMapping
父類AbstractHandlerMethodMapping
中笑窜,在文件中找到這個下面方法
protected void initHandlerMethods() {
// 這是第一步:獲取所有beanName
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));
for (String beanName : beanNames) {
// 這個是用來判斷bean是不是由ScopedProxyUtils這個工具類生成的排截,可以不用管,全當這個判斷不存在
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
}
}
// 這是第二步:找到只有包含Controller 和 RequestMapping的bean
if (beanType != null && isHandler(beanType)) {
//剩下步驟在這里處理
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}
剩下步驟具體代碼
protected void detectHandlerMethods(final Object handler) {
// 這個handler 就是被Controller或RequestMappering 標記的bean
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);
// 第四步:找出符合條件的方法脱吱,并合并方法和類的RequestMapping信息箱蝠。
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
}
}
});
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
// 第五步:放入內(nèi)存
registerHandlerMethod(handler, invocableMethod, mapping);
}
}
第四步驟:合并方法和類的RequestMapping信息
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
// 獲取方法中的RequestMapping的信息
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
// 獲取類的RequestMapping信息
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
// 合并RequestMapping信息
info = typeInfo.combine(info);
}
}
return info;
}
第五步驟:合并的信息存入內(nèi)存
public void register(T mapping, Object handler, Method method) {
this.readWriteLock.writeLock().lock();
try {
// HandlerMethod是不是有點熟悉宦搬,就是SpringMVC攔截器中的那個參數(shù)劫拗。
//HandlerMethod 包含bean對象以及對應(yīng)的方法
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
assertUniqueMethodMapping(handlerMethod, mapping);
// ReqeustMapping合并信息放入內(nèi)存
this.mappingLookup.put(mapping, handlerMethod);
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
//將相對路徑與RequestMapping對應(yīng)起來; /aa/dd
this.urlLookup.add(url, mapping);
}
String name = null;
if (getNamingStrategy() != null)
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
this.registry.put(mapping, new MappingRegistration<T>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
下面攔截器中的handler
參數(shù)就是HandlerMethod
其實在用戶請求的時候撇簿,SpringMVC
會根據(jù)用戶請求的相對路徑在urlLookup
中找出RquestMapping
信息四瘫,然后根據(jù)RequestMapping
找出HandlerMethod
欲逃,所以關(guān)系就對應(yīng)起來了。關(guān)系如下圖洗做。
SpringMVC的攔截器中的預(yù)處理和后處理就是在反射前后執(zhí)行彰居。關(guān)系就如下圖所示陈惰。