spring源碼閱讀4秩贰,請求如何匹配

前文提要

根據(jù)前文
我們知道所有的http請求都會到
DispatcherServlet#doDispatch內(nèi)霹俺,執(zhí)行下面兩個邏輯

  • 構(gòu)造HandlerExecutionChain,根據(jù)請求的路徑找到HandlerMethod(帶有Method反射屬性毒费,也就是對應Controller中的方法)丙唧,然后匹配路徑對應的攔截器,有了HandlerMethod和攔截器構(gòu)造個HandlerExecutionChain對象觅玻。HandlerExecutionChain對象的獲取是通過HandlerMapping接口提供的方法中得到想际。
  • 構(gòu)造HandlerAdapter,有了HandlerExecutionChain之后,通過HandlerAdapter對象進行處理得到ModelAndView對象溪厘,HandlerMethod內(nèi)部handle的時候沼琉,使用各種HandlerMethodArgumentResolver實現(xiàn)類處理HandlerMethod的參數(shù),使用各種HandlerMethodReturnValueHandler實現(xiàn)類處理返回值桩匪。 最終返回值被處理成ModelAndView對象打瘪,這期間發(fā)生的異常會被HandlerExceptionResolver接口實現(xiàn)類進行處理

但是如何根據(jù)我們在Controller中定義的RequestMapping()來找到具體的處理方法呢? 本文主要介紹這個問題傻昙。

相關接口和實現(xiàn)

首先是RequestMappingInfo闺骚,它保存了匹配的各種條件,如Header條件妆档,Pattern條件等根據(jù)名字其實也不難看出僻爽,它的繼承關系如下

image.png

org.springframework.web.servlet.mvc.condition.RequestCondition定義了一個接口,用于匹配請求

MappingRegistry用于保存所有的匹配信息贾惦,是AbstractHandlerMethodMapping的一個內(nèi)部類
這個類設計上比較關鍵胸梆,受限于蝙蝠,這里先不展開

HandlerMethod類须板,用于存儲方法的相關信息

image.png

初始化RequestMappingHandlerMapping

開啟<mvc:annotation-driven/>,將會注冊兩個bean碰镜。RequestMappingHandlerMappingRequetMappingHandlerAdapter
用于分配請求
RequestMappingHandlerMapping的繼承關系如下

image.png

繼承了InitializingBean說明創(chuàng)建后會初始化,實現(xiàn)HandlerMapping可以獲取HandlerExecutionChain习瑰,繼承了ApplicationObjectSupport可以方便的獲取上下文對象
先看RequestMappingHandlerMapping初始化绪颖,調(diào)用了org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#initHandlerMethods,方法如下甜奄。

獲取所有的bean 找到所有被RequesMapping修飾的方法和類柠横,調(diào)用detectHandlerMethods

protected void initHandlerMethods() {
if (logger.isDebugEnabled()) {
logger.debug("Looking for request mappings in application context: " + getApplicationContext());
}

//獲取所有的bean
String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
getApplicationContext().getBeanNamesForType(Object.class));

for (String beanName : beanNames) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
Class<?> beanType = null;
//獲取bean的類型
try {
beanType = getApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isDebugEnabled()) {
logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex);
}
}
//isHandler 判斷類上是否有Controller或者RequestMapping注解
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
}
handlerMethodsInitialized(getHandlerMethods());
}

detectHandlerMethods 對被修飾的方法創(chuàng)建一個map用于存儲方法和具體匹配關系的映射窃款,并且調(diào)用registerHandlerMethod把這些映射關系注冊到MappingRegistry的一個實例中,這個實例在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#mappingRegistry

protected void detectHandlerMethods(final Object handler) {
Class<?> handlerType = (handler instanceof String ?
getApplicationContext().getType((String) handler) : handler.getClass());
final Class<?> userType = ClassUtils.getUserClass(handlerType);

//key是Method牍氛,值是一個泛型晨继,在webmvc中是指org.springframework.web.servlet.mvc.method.RequestMappingInfo
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
new MethodIntrospector.MetadataLookup<T>() {
@Override
public T inspect(Method method) {
try {
//是抽象方法,具體由RequestMappingHandlerMapping實現(xiàn)
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
}
});

if (logger.isDebugEnabled()) {
logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods);
}
for (Map.Entry<Method, T> entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType);
T mapping = entry.getValue();
registerHandlerMethod(handler, invocableMethod, mapping);
}
}

getMappingForMethod中定義了如何根據(jù)一個RequestMapping方法得到一個存儲匹配信息的RequestMappingInfo搬俊,這里類的設計的比較巧妙紊扬,受限于篇幅,先按住不表悠抹。

從http請求到HandlerExecutionChain

回憶下我們說DispatcherServlet初始化的部分,DispatcherServlet最后的步驟是經(jīng)由onRefresh調(diào)用initStrategies扩淀,其中initHandlerMappings用于初始化HandleMapping

private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;

if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}

// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isDebugEnabled()) {
logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
}
}
}

如果是需要找到所有HandleMapping的子類開啟楔敌,則在所有bean定義中找到所有HandleMapping的實現(xiàn),并且作為元素放入org.springframework.web.servlet.DispatcherServlet#handlerMappings
否則將從bean中找到名字為handlerMapping作為唯一HandleMapping

一般情況下 需要找到所有HandleMapping的子類開啟

調(diào)用org.springframework.web.servlet.DispatcherServlet#getHandler獲取具體的handler

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
for (HandlerMapping hm : this.handlerMappings) {
if (logger.isTraceEnabled()) {
logger.trace(
"Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
return null;
}

也就是說通常情況下驻谆,getHandler最終會調(diào)用org.springframework.web.servlet.handler.RequestMappingHandlerMapping#getHandler
這個方法定義在其父類AbstractHandlerMapping中卵凑,這個方法分主要內(nèi)容是獲取通過getHandlerInternal獲取handler并且組成HandlerExecutionChain

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
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 = getApplicationContext().getBean(handlerName);
}

HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (CorsUtils.isCorsRequest(request)) {
CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}

getHandlerInternal是實際獲取的handler,定義在AbstractHandlerMethodMapping胜臊,獲取讀取鎖后調(diào)用lookupHandlerMethod查找handler后釋放鎖

@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
if (logger.isDebugEnabled()) {
logger.debug("Looking up handler method for path " + lookupPath);
}
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
if (logger.isDebugEnabled()) {
if (handlerMethod != null) {
logger.debug("Returning handler method [" + handlerMethod + "]");
}
else {
logger.debug("Did not find handler method for [" + lookupPath + "]");
}
}
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}

看看核心匹配方法lookupHandlerMethod

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<Match>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" +
lookupPath + "] : " + matches);
}
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}

返回最佳匹配勺卢,修飾的方法就找到了

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市象对,隨后出現(xiàn)的幾起案子黑忱,更是在濱河造成了極大的恐慌,老刑警劉巖勒魔,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件甫煞,死亡現(xiàn)場離奇詭異招刨,居然都是意外死亡杨名,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進店門斥黑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弟胀,“玉大人楷力,你說我怎么就攤上這事》趸В” “怎么了萧朝?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長夏哭。 經(jīng)常有香客問我剪勿,道長,這世上最難降的妖魔是什么方庭? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任厕吉,我火速辦了婚禮酱固,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘头朱。我一直安慰自己运悲,他們只是感情好,可當我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布项钮。 她就那樣靜靜地躺著班眯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪烁巫。 梳的紋絲不亂的頭發(fā)上署隘,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機與錄音亚隙,去河邊找鬼磁餐。 笑死,一個胖子當著我的面吹牛阿弃,可吹牛的內(nèi)容都是我干的诊霹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼渣淳,長吁一口氣:“原來是場噩夢啊……” “哼脾还!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起入愧,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鄙漏,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后棺蛛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體泥张,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年鞠值,在試婚紗的時候發(fā)現(xiàn)自己被綠了媚创。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡彤恶,死狀恐怖钞钙,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情声离,我是刑警寧澤芒炼,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站术徊,受9級特大地震影響本刽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一子寓、第九天 我趴在偏房一處隱蔽的房頂上張望暗挑。 院中可真熱鬧,春花似錦斜友、人聲如沸炸裆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽烹看。三九已至,卻和暖如春洛史,著一層夾襖步出監(jiān)牢的瞬間惯殊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工也殖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留土思,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓毕源,卻偏偏與公主長得像浪漠,于是被迫代替她去往敵國和親陕习。 傳聞我的和親對象是個殘疾皇子霎褐,可洞房花燭夜當晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容