HandlerMapping 詳解
1. 導言
萬丈高樓平地起,SpringMVC的輝煌離不開每個組件的相互協作,上一章詳細闡述了SpringMVC整個體系結構及實現原理,知道HandlerMapping在這個SpringMVC體系結構中有着舉足輕重的地位,充當着url和Controller之間映射關系配置的角色。主要有三部分組成:HandlerMapping映射注冊、根據url獲取對應的處理器、攔截器注冊。本文將立足於RequestMappingHandlerMapping詳細闡述HandlerMapping的整個體系。其結構如圖所示。
筆者可以以不同顏色表示三大主要過程,下面筆者將逐步分析RequestMappingHandlerMapping的整個體系。
2. 檢測方法,構造RequestHandlerInfo映射集合
- AbstractHandlerMethodMapping一個並不陌生的方法,afterPropertiesSet()
注意AbstractHandlerMethodMapping繼承自InitializingBean,會在Bean初始化完成后調用afterPropertiesSet()方法
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
initHandlerMethods的實現如下圖所示:
判斷beanType是否是滿足要求的handler和檢測並生存handlerMethod是最為關鍵的兩個過程。其中判斷是否滿足要求的handler,實現如下:
protected abstract boolean isHandler(Class<?> beanType);
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
注意到,isHandler方法是一個抽象方法,在父類不能確定如何實現,這邊將具體的實現交子類來進行,在 RequestMappingHandlerMapping中的實現為只要有@Controller注解或者@RequestMapping注解的均為滿足要求的handler。
檢測HandlerMethods是在detectHandlerMethods方法中實現的,其幾個關鍵的類、接口及方法實現如圖所示:
detectHandlerMethods的實現序列圖如圖所示
- what?selectMethods感覺好凌亂,這么復雜,是否有跟筆者一樣的想法?
selectMethods其實是兩個命令模式的變體的疊加。筆者看來每個設計模式都有多種變體,重要的是理解每個設計模式解決的問題。命令模式的主要目的是為了將觸發和命令的具體實現解耦,以實現觸發命令操作和具體的命令的實現相互隔離。當命令觸發時,命令對象就會執行操作,這是java事件的處理方式。java中典型的命令模式,就是多線程的start方法和Runnable的run方法,相信讀者並不會陌生。
Thread thread = new Thread(new Runnable(){
@Override
public void run(){
log.info("簡單的測試");
}
});
...
thread.start();
首先傳入一個命令對象,這個命令(run方法)並不會立馬執行,會在事件觸發后才會調用命令(start方法),但在什么時候觸發事件,在傳入命令對象的時候,我們並不關心,也沒辦法知道如何觸發事件。
簡單解釋了命令模式,解決的問題,現在回到主題,selectMethods是怎么實現的?
第一個命令模式:
public interface MetadataLookup<T> {
/**
* Perform a lookup on the given method and return associated metadata, if any.
* @param method the method to inspect
* @return non-null metadata to be associated with a method if there is a match,
* or {@code null} for no match
*/
T inspect(Method method);
}
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) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
}
});
傳入一個命令,MetadataLookup的實現,在selectMethods方法內部會調用對象的inspect方法。(實際上是在第二命令中調用的這個命令)。
第二個命令模式:
public interface MethodCallback {
/**
* Perform an operation using the given method.
* @param method the method to operate on
*/
void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
}
for (Class<?> currentHandlerType : handlerTypes) {
final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType);
ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
傳入一個命令,MethodCallback的實現,在doWithMethods方法內部會調用對象的dowith方法方法。
- 再談selectMethods實現
第一個命令模式,即selectMethods方法中,
(1)首先選擇所有HandlerType的所有繼承體系的所有class:
final Map<Method, T> methodMap = new LinkedHashMap<Method, T>();
Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>();
Class<?> specificHandlerType = null;
if (!Proxy.isProxyClass(targetType)) {
handlerTypes.add(targetType);
specificHandlerType = targetType;
}
handlerTypes.addAll(Arrays.asList(targetType.getInterfaces()));
(2)遍歷每一個handlerType
(3)選擇每一個滿足要求的方法,執行dowith方法
public static void doWithMethods(Class<?> clazz, MethodCallback mc, MethodFilter mf) {
// Keep backing up the inheritance hierarchy.
Method[] methods = getDeclaredMethods(clazz);
for (Method method : methods) {
if (mf != null && !mf.matches(method)) {
continue;
}
try {
mc.doWith(method);
}
catch (IllegalAccessException ex) {
throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex);
}
}
if (clazz.getSuperclass() != null) {
doWithMethods(clazz.getSuperclass(), mc, mf);
}
else if (clazz.isInterface()) {
for (Class<?> superIfc : clazz.getInterfaces()) {
doWithMethods(superIfc, mc, mf);
}
}
}
public static final MethodFilter USER_DECLARED_METHODS = new MethodFilter() {
//只選擇用戶定義的方法,Object方法和代理方法不滿則需求
@Override
public boolean matches(Method method) {
return (!method.isBridge() && method.getDeclaringClass() != Object.class);
}
};
(4)針對每一個method調用metadataLookup的dowith方法,以{method,result}的形式緩存:
public void doWith(Method method) {
Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass);
T result = metadataLookup.inspect(specificMethod);
if (result != null) {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod);
if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) {
methodMap.put(specificMethod, result);
}
}
}
(5)重頭戲,inspect方法
public T inspect(Method method) {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
}
其關鍵之關鍵為getMappingForMethod,首先會讀取方法上的@RequestMapping注解,然今讀取類上面的注解,最后進行聯合操作。
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
info = typeInfo.combine(info);
}
}
return info;
}
(6)注冊RequestMappingInfo
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
registerHandlerMethod(handler, invocableMethod, mapping);
registerHandlerMethod會調用MappingRegistry的registry方法,其實現流程如圖所示
這個過程主要針對HandlerMethod做了一些緩存,方便查詢,根據url,name,mapping均做了相應緩存,主要是為了優化查詢handlerMethod的性能。
3. getHandler方法,獲取執行器鏈。
- 獲取執行器鏈入口:
mappedHandler = getHandler(processedRequest);
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace("Testing handler map [" + hm + "] DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}
遍歷配置的handlerMappings,依次調用getHandler方法,只要找到滿足要求的handlerMapping,立馬返回。
- HandlerMapping的getHandler方法:
查找到匹配項后,handlerMethod做一些處理,RequestHandlerMethodMapping是會將相關內容緩存在request域中,當然,使用的時候也可以定制一些內容。筆者猜想,這些都是為了性能提升而努力的,畢竟性能提升在每一小步。
構造執行器鏈,執行器鏈中包含HandlerMethod和相關攔截器,同時包含有跨域的解決方案。
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
4. 再談攔截器
從上一節的代碼可以看出,攔截器至少包含兩種,實現MappedInterceptor和實現普通HandlerInterceptor接口的類。
普通handler接口,會直接加入到攔截器鏈中,而MappedInterceptor則只會加入matches方法返回true的攔截器。
至此HandlerMapping已分析完畢,SpringMVC的其它內容也將陸續推出。