SpringBoot之@Controller和@RequestMapping的實(shí)現(xiàn)原理解讀
SpringBoot之@Controller和@RequestMapping的實(shí)現(xiàn)原理
干貨分享,SpringBoot中Web接口資源是如何被管理起來(lái)呢?
一個(gè)請(qǐng)求,通過(guò)在瀏覽器上輸入了一個(gè)url,是如何被SpringWeb匹配到對(duì)應(yīng)的方法的呢?
帶著這個(gè)疑問(wèn)我們來(lái)學(xué)習(xí)本篇。
瀏覽器的請(qǐng)求,是如何被映射到后端服務(wù)的方法上呢?
后端服務(wù)使用SpringBoot只使用了一個(gè)注解就提供了web服務(wù)的實(shí)現(xiàn)原理是什么呢?
@RestController public class TestController { @GetMapping("/name") public String name(HttpServletRequest request){ return request.toString(); } }
帶著上面的疑問(wèn),小編通過(guò)源碼的方式帶你一看究竟吧。 為了能讓各位童鞋更好的更容易的理解。第一趴我們先來(lái)補(bǔ)充點(diǎn)知識(shí)點(diǎn)。
一、注解派生概念
在java體系中,類是可以被繼承,接口可以被實(shí)現(xiàn)。但是注解沒(méi)有這些概念,而是有一個(gè)派生的概念。舉例,注解A。被標(biāo)記了在注解B頭上,那么我們可以說(shuō)注解B就是注解A的派生。
下面我們舉一個(gè)例子:
@RestController public class PostController { @ApiOperation(value = "查詢Bbs所有文章") @PostMapping(value = "/query/bbs/posts", produces = MediaType.APPLICATION_JSON_VALUE) public Result<PostAllResponse> queryBbsPostAll(@RequestBody PostAllSelectRequest postAllSelectRequest) { return postBiz.queryBbsPostAll(postAllSelectRequest); } public static void main(String[] args) { Method queryBbsPostAll = ClassUtils.getMethod(PostController.class, "queryBbsPostAll",PostAllSelectRequest.class); PostMapping annotation = AnnotationUtils.findAnnotation(queryBbsPostAll, PostMapping.class); ///query/bbs/posts System.out.println(StringUtils.arrayToCommaDelimitedString(annotation.value())); //application/json System.out.println(StringUtils.arrayToCommaDelimitedString(annotation.produces())); //是否包含RequestMapping: true System.out.println("是否包含RequestMapping: "+AnnotatedElementUtils.hasAnnotation(queryBbsPostAll,RequestMapping.class)); RequestMapping mergedAnnotation = AnnotatedElementUtils.getMergedAnnotation(queryBbsPostAll, RequestMapping.class); ///query/bbs/posts System.out.println(StringUtils.arrayToCommaDelimitedString(mergedAnnotation.value())); } }
- queryBbsPostAll是用了PostMapping類進(jìn)行標(biāo)記是一個(gè)POST資源
- 我們通過(guò)main方法里面可以知道。通過(guò)反射我們能拿到Method上的PostMapping注解信息。
- 但是看這一行AnnotatedElementUtils.hasAnnotation(queryBbsPostAll,RequestMapping.class)為什么也是true呢?
沒(méi)錯(cuò)因?yàn)镻ostMapping是RequestMapping的派生注解。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented @RequestMapping(method = RequestMethod.POST) public @interface PostMapping { ... }
請(qǐng)記住這個(gè)小的知識(shí)點(diǎn),后面的邏輯會(huì)用到。因?yàn)橄旅婧笥写罅康脑创a,為了方便標(biāo)注,小編使用截圖的形式,在截圖上會(huì)加上注釋信息。 ?
二、進(jìn)入正題,跟進(jìn)源碼解析請(qǐng)求Method
通過(guò)跟進(jìn)源碼,我們會(huì)發(fā)現(xiàn)這樣一個(gè)類。AbstractHandlerMethodMapping。其實(shí)現(xiàn)了實(shí)現(xiàn) InitializingBean接口。在當(dāng)前 Bean初始化時(shí)候會(huì)執(zhí)行afterPropertiesSet -> initHandlerMethods。
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean { @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()); } }
RequestMappingHandlerMapping解析Method上的RequestMapping信息
isHandler 方法判斷是否是web資源類。當(dāng)一個(gè)類被標(biāo)記了 @Controller 或者@RequestMapping。 注意 @RestController 是@Controller的派生類。所以這里只用判斷 @Controller 或者@RequestMapping就行了。
@Override protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); }
detectHandlerMethods方法就是真正開始解析Method的邏輯。通過(guò)解析Method上的 @RequestMapping或者其他派生的注解。生成請(qǐng)求信息。 注意這個(gè)請(qǐng)求信息里面也是有很多邏輯的不過(guò)不是本篇討論的重點(diǎn),就不說(shuō)了。稍微提一下。根據(jù)規(guī)則來(lái)匹配url邏輯就在這里面。
這里我們能看到源碼里拿到了Method并拿到了執(zhí)行這個(gè)Method的實(shí)例Bean。在這里封裝成了HandlerMethod并注冊(cè)到了MappingRegistry中。
在注冊(cè)的過(guò)程中把RequestMapping中的路徑信息同事也放到一個(gè)urlLookup中。key是url,value是Mapping信息。
到這里其實(shí)我們就把本篇的議題就說(shuō)明清楚了。下面我們?cè)诳聪耂pringWeb是如何將http請(qǐng)求信息路由到具體的HandlerMethod的吧。
三、最后串一下請(qǐng)求流程
看了前面的截圖,我們知道Spring是如何把這些Web資源信息給保存起來(lái)的了。然后就看是DispatcherServlet的邏輯了。 首先DispatcherServlet 是一個(gè)Servlet。Servlet相信大家都都知道就不重點(diǎn)說(shuō)原理。 我們直接看doService -> doDispatch 方法
根據(jù)請(qǐng)求路徑,找到從Mapping信息,然后根據(jù)Mapping信息匹配到具體的HandlerMethod。 ok本篇內(nèi)容就到這里。謝謝大家。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Maven繼承父工程時(shí)的relativePath標(biāo)簽解析用法小結(jié)
relativePath 的作用是為了找到父級(jí)工程的pom.xml,本文主要介紹了Maven繼承父工程時(shí)的relativePath標(biāo)簽解析用法小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03java利用udp實(shí)現(xiàn)發(fā)送數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了java利用udp實(shí)現(xiàn)發(fā)送數(shù)據(jù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07Java數(shù)據(jù)導(dǎo)入功能之讀取Excel文件實(shí)例
這篇文章主要介紹了Java數(shù)據(jù)導(dǎo)入功能之讀取Excel文件實(shí)例,本文給出了jar包的下載地址以及讀取Excel文件的代碼實(shí)例,需要的朋友可以參考下2015-06-06超細(xì)講解Java調(diào)用python文件的幾種方式
有時(shí)候我們?cè)趯慾ava的時(shí)候需要調(diào)用python文件,下面這篇文章主要給大家介紹了關(guān)于Java調(diào)用python文件的幾種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12mybatis3使用@Select等注解實(shí)現(xiàn)增刪改查操作
這篇文章主要介紹了mybatis3使用@Select等注解實(shí)現(xiàn)增刪改查操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11Java解析調(diào)用webservice服務(wù)的返回XML串詳解
這篇文章主要介紹了Java解析調(diào)用webservice服務(wù)的返回XML串詳解的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07java面向?qū)ο笤O(shè)計(jì)原則之迪米特法則分析詳解
這篇文章主要為大家介紹了java面向?qū)ο笤O(shè)計(jì)原則之迪米特法則的示例分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,學(xué)有所得2021-10-10