1.問題的產生
日常開發中,大多數的API層中@Controller注解和@RequestMapping注解都會被使用在其中,但是為什么標注了@Controller和@RequestMapping注解之后,從外部訪問你設置的請求路徑,springmvc就能捕獲到請求並將請求交給指定的類和方法來執行呢?這其實就是RequestMappingHandlerMapping的核心功能了,注冊&發現。
2.RequestMappingHandlerMapping之注冊
@Controller和@RequestMapping是如何生效的呢,@Controller與其他的@Service,@Component又有什么區別呢,那么就來看下RequestMappingHandlerMapping是如何工作的。
RequestMappingHandlerMapping會在Spring容器初始化時實例化Bean注入到Spring容器中,RequestMappingHandlerMapping繼承於RequestMappingInfoHandlerMapping,
RequestMappingInfoHandlerMapping又繼承自AbstractHandlerMethodMapping,AbstractHandlerMethodMapping又實現了InitializingBean接口,InitializingBean接口我們都知道,
在Bean的生命周期中,在實例化Bean對象后,開始注入bean依賴的屬性,如果實現了BeanNameAware接口,那么緊接着會執行該接口的setBeanName(String name)進行beanName的回調,
之后如果實現了InitializingBean接口則會調用afterPropertiesSet()方法進行一些其他屬性的設置。
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping implements MatchableHandlerMapping, EmbeddedValueResolverAware {}
public abstract class RequestMappingInfoHandlerMapping extends AbstractHandlerMethodMapping<RequestMappingInfo> {
}
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {}
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
核心就在AbstractHandlerMethodMapping實現的afterPropertiesSet()中,可以看到afterPropertiesSet()中調用了initHandlerMethods()方法
@Override public void afterPropertiesSet() { initHandlerMethods(); }
protected void initHandlerMethods() {
//遍歷spring容器中所有beanName
for (String beanName : getCandidateBeanNames()) {
//如果beanName不是以scopedTarget.開頭則對該bean進行處理
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
//獲取該bean的類型
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);
}
}
//如果類型!=null&&類型屬於handler則進行檢測
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
//RequestMappingHandlerMapping實現的isHandler方法
@Override
protected boolean isHandler(Class<?> beanType) {
//判斷該類上是否有@Controller注解或者@RequestMapping注解
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
//檢測handler的方法
protected void detectHandlerMethods(Object handler) {
//獲取bean類型
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
//獲取該類的用戶自定義類,該ClassUtils是Spring提供的,通常情況下會返回該類本身的class,如果是cglib代理類則會去返回被代理類本身的class
Class<?> userType = ClassUtils.getUserClass(handlerType);
//將該類的每個方法和對應的url路徑(類上@RequestMapping的信息 + 方法上的@RequestMapping的信息進行拼裝)封裝到map中
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));
}
//遍歷這個map,將所有方法和對應的mapping信息進行注冊
methods.forEach((method, mapping) -> {
//通過AopUtils處理獲取到反射可以調用的方法
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
//將該方法以HandlerMethod的形式進行注冊,在SpringMvc的視角下,controller相當於handler,下屬方法相當於handlerMethod
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
getMappingForMethod(),通過方法獲取詳細的Mapping信息(類+方法)
@Override @Nullable protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//先獲取方法上@RequestMapping的路徑 RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) {
//獲取類上的@RequestMapping的路徑 RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
//如果類上存在@RequestMapping則將二者合並,所有路徑由類的在前,后面拼接方法上的 if (typeInfo != null) { info = typeInfo.combine(info); } String prefix = getPathPrefix(handlerType); if (prefix != null) { info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info); } } return info; }
selectInvocableMethod()通過反射獲取實際調用的方法
public static Method selectInvocableMethod(Method method, @Nullable Class<?> targetType) { if (targetType == null) { return method; } else {
//通過反射獲取到方法 Method methodToUse = MethodIntrospector.selectInvocableMethod(method, targetType);
//判斷是否為私有方法/非靜態方法/SpringProxy的子類,是則拋出異常,這里有個疑問,反射應該是不能調用靜態方法,這里不知道為什么不限制靜態方法? if (Modifier.isPrivate(methodToUse.getModifiers()) && !Modifier.isStatic(methodToUse.getModifiers()) && SpringProxy.class.isAssignableFrom(targetType)) { throw new IllegalStateException(String.format("Need to invoke method '%s' found on proxy for target class '%s' but cannot be delegated to target bean. Switch its visibility to package or protected.", method.getName(), method.getDeclaringClass().getSimpleName())); } else { return methodToUse; } } }
將方法以handlerMethod的形式進行注冊
protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); } public void register(T mapping, Object handler, Method method) {
public void register(T mapping, Object handler, Method method) {
//開啟寫鎖,防止多線程注冊多個相同的handleMehtod
this.readWriteLock.writeLock().lock();
try {
//構造handlerMethod
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//校驗該handlerMehtod是否可以注冊進來
validateMethodMapping(handlerMethod, mapping);
//獲取該方法的直接路徑
Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
//將該方法的所有直接路徑添加mappingRegister中的一個容器pathLookup,主要用來儲存直接路徑和mapping信息的關系
for (String path : directPaths) {
this.pathLookup.add(path, mapping);
}
String name = null;
if (getNamingStrategy() != null) {
//獲取該方法的名稱,如果@RequestMapping上的name有值則取該name,沒有則根據類的類型名轉換成大寫的后+'#'+方法的名稱 拼接而成
name = getNamingStrategy().getName(handlerMethod, mapping);
//將該handlerMehod添加進mappingRegister中的一個容器nameLookup,用來存存儲name和handleMethod的關系
addMappingName(name, handlerMethod);
}
//跨越相關配置
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
corsConfig.validateAllowCredentials();
this.corsLookup.put(handlerMethod, corsConfig);
}
//將handleMehod注冊進容器
this.registry.put(mapping,
new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
validateMethodMapping校驗handlerMethod是否合法
private void validateMethodMapping(HandlerMethod handlerMethod, T mapping) { // Assert that the supplied mapping is unique.
//根據mapping路徑獲取HandlerMethod HandlerMethod existingHandlerMethod = this.mappingLookup.get(mapping);
//如果存在並且跟當前的HandlerMethod並且不是同一個則拋錯 if (existingHandlerMethod != null && !existingHandlerMethod.equals(handlerMethod)) { throw new IllegalStateException( "Ambiguous mapping. Cannot map '" + handlerMethod.getBean() + "' method \n" + handlerMethod + "\nto " + mapping + ": There is already '" + existingHandlerMethod.getBean() + "' bean method\n" + existingHandlerMethod + " mapped."); } }
3.RequestMappingHandlerMapping之發現
通過上面部分的源碼我們大概知道了@Controller和@RequestMapping注解是如何生效的,RequestMappingHandlerMapping是如何去處理他們的,那我們說完了注冊,就該來說發現,當客戶端的請求發送到后台服務器時,SpringMvc是如何根據請求url給他找到指定的方法上呢?我們都知道SpringMVC處理請求的核心在於DisPatcherServlet,也就是所謂的前端控制器,DispatchServlet會拿到請求進行解析分發,那具體是如何操作的,來看下圖:
圖片來自百度,這張圖大家應該都很熟悉了,DispatcherServlet的工作流程,可以看到DispatcherServlet在接收到客戶端發送的request時會將請求委托給HandlerMapping去處理,HandlerMapping將根據url尋找一個合適的handler返回給DispatcherServlet,RequestMappingHandlerMapping實現自HandlerMapping接口,他是SpringMVC的眾多HandlerMapping中的一個,它們都在Spring容器初始化時一同創建並注入到容器中。而我們的RequestMappingHandlerMapping的發現功能就在此時派上了用場。
當請求進來時,dispatcherServlet的doDispatch()方法將對請求處理,部分代碼如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request.
// 可以看到在此時就要通過handlerMapping去獲取HandlerExecutionChain執行鏈,其中包括了handler和對應的攔截器Inteceptor等等。 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; }
getHandler方法如下:
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//如果handlerMapping集合不為null,則遍歷每個handlerMapping看是否可以找到對應的handler if (this.handlerMappings != null) { for (HandlerMapping mapping : this.handlerMappings) { HandlerExecutionChain handler = mapping.getHandler(request); if (handler != null) { return handler; } } } return null; }
這里我們主要來看RequestMappingHandlerMapping實現的getHandler()方法,查看源碼發現RequestMappingHandlerMapping並沒有重寫該方法,而是由其祖宗類AbstractHandlerMapping重寫了該方法
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//獲取handler又是通過getHandlerInternal()方法 Object handler = getHandlerInternal(request); if (handler == null) { handler = getDefaultHandler(); } if (handler == null) { return null; } // Bean name or resolved handler? if (handler instanceof String) { String handlerName = (String) handler; handler = obtainApplicationContext().getBean(handlerName); } // Ensure presence of cached lookupPath for interceptors and others if (!ServletRequestPathUtils.hasCachedPath(request)) { initLookupPath(request); }
//根據handler和request獲取執行鏈 HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); if (logger.isTraceEnabled()) { logger.trace("Mapped to " + handler); } else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) { logger.debug("Mapped to " + executionChain.getHandler()); } if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) { CorsConfiguration config = getCorsConfiguration(handler, request); if (getCorsConfigurationSource() != null) { CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request); config = (globalConfig != null ? globalConfig.combine(config) : config); } if (config != null) { config.validateAllowCredentials(); } executionChain = getCorsHandlerExecutionChain(request, executionChain, config); } //返回執行鏈 return executionChain; }
getHandlerInternal(request)方法又是由AbstractHandlerMapping的子類AbstractHandlerMethodMapping重寫的,
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { String lookupPath = initLookupPath(request);
//對mappingRegister加讀鎖,防止讀取的時候被篡改數據 this.mappingRegistry.acquireReadLock(); try {
//根據request獲取handlerMehtod,即找到了實際要執行的方法 HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null); } finally {
//釋放讀鎖 this.mappingRegistry.releaseReadLock(); } }
4. 其他的一些補充
在閱讀源碼中我們也是在不斷的在學習,其中的一些方法可能也是第一次見,這里也做解析讓自己學習一下
ClassUtils.getUserClass(Class<?> clazz)方法我們在源碼中看到有使用來根據calss獲取用戶自定義類,注意該ClassUtils為Spring包下的
public static Class<?> getUserClass(Class<?> clazz) {
//判斷類名稱是否包含cglib的分隔符 CGLIB_CLASS_SEPARATOR = "$$"; if (clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) {
//是則取獲取當前cglib類的父類,因為cglib代理類是增強類,並不是被代理類本身 Class<?> superclass = clazz.getSuperclass(); if (superclass != null && superclass != Object.class) { return superclass; } } return clazz; }
可以看到這里cglib代理類的的名稱是拼接而成,其中是帶着"$$"符號
5. 總結
關於RequestMappingHandlerMapping的源碼解析暫時先到這了,之后可能會隨時更改其中的內容,因為本人目前能力有限,文章中可能存在一些錯誤的地方,希望大家可以指出一起學習探討。