Spring的源碼非常復(fù)雜,想要一篇概覽幾乎不可能扛点,所以只能分而治之哥遮,本文摘寫自近期抽空翻閱SpringMVC框架的部分源碼,借此由淺入深地探索SpringMVC的核心體系結(jié)構(gòu)陵究。
在前后端分離大行其道的今日眠饮,由后端完成渲染的JSP/Freemarker時代可以說已經(jīng)逐漸被NodeJS搭建的大前端體系取代,所以本文也不去翻閱SpringMVC視圖渲染部分铜邮,僅認(rèn)為后端作為一個數(shù)據(jù)服務(wù)接口來看到仪召,簡而言之,僅分析SpringMVC與前端的JSON交互流程松蒜。
基于過往的研發(fā)經(jīng)驗(yàn)相信絕大多數(shù)都了解到扔茅,SpringMVC的核心組件為DispatcherServlet組件,而實(shí)現(xiàn)的規(guī)約則是J2EE關(guān)于Servlet的規(guī)范秸苗。如果對于Servlet規(guī)范不甚清楚召娜,請抽空自行補(bǔ)充。本篇僅討論HTTP請求的完整調(diào)用鏈路惊楼,不涉及其他協(xié)議規(guī)范說明玖瘸。
DispatcherServlet
作為一個全局Servlet秸讹,欲深究其意,需先了解DispatcherServlet容器初始化時做了什么處理雅倒?而DispatcherServlet對象初始化包括如下三部分:
- DispatcherServlet對象靜態(tài)初始化部分璃诀?
- 構(gòu)造函數(shù)?
- Servlet規(guī)范中容器初始化流程屯断?
在整個容器初始化的過程文虏,只有明白其中會涉及到得我們需要去核心關(guān)注的部分,才能很好的把握住SpringMVC的核心流程殖演,從中透析核心體系氧秘。
靜態(tài)初始化部分
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
}
靜態(tài)初始化實(shí)際是讀取了DispatcherServlet.properties文件的內(nèi)容,其中都有些什么呢趴久?摘4.3.18.RELESE版本的文件丸相,內(nèi)容如下:
// 省略...
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
// 省略....
找一個類進(jìn)去翻閱,可以猜測它可能是要初始化這些Bean彼棍,再查閱DispatcherServlet#getDefaultStrategies() 即可確認(rèn)灭忠,當(dāng)然,這部分并非是靜態(tài)初始化就完成座硕,而是延遲至Serlvet容器初始化時分批進(jìn)行初始化弛作。
構(gòu)造函數(shù)
翻閱代碼可以發(fā)現(xiàn)內(nèi)部有兩個構(gòu)造函數(shù),那么究竟在容器初始化的時候默認(rèn)調(diào)用的是哪個华匾?
easy映琳,打個斷點(diǎn)DEBUG一下就可以,基于我的環(huán)境上我發(fā)現(xiàn)是默認(rèn)無參構(gòu)造函數(shù)蜘拉。(基于springboot 1.5.6版本)
那么是誰來調(diào)用改無參構(gòu)造函數(shù)的呢萨西?
easy,在springboot環(huán)境運(yùn)行時可以看到有一個顯眼的注解SpringBootApplication旭旭,而該注解的組成部分還包括EnableAutoConfiguration谎脯,這個注解又是要解決什么的呢?
它將默認(rèn)加載spring-boot-autoconfig下的spring.factories文件內(nèi)部的類持寄,比如:
org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
///.....
那么查閱這些類源梭,它們做了什么?比如DispatcherServletAutoConfiguration稍味,查閱時發(fā)現(xiàn)它加了注解Configuration
查閱該注解你又將發(fā)現(xiàn)它會處理聲明@Bean的方法咸产,并將該Bean注入到Spring容器中,由容器托管其生命周期仲闽。
Servlet容器初始化
查閱javax.servlet.Servlet接口的定義,可以清晰的看到規(guī)范中定義四個方法僵朗,其中需要重點(diǎn)關(guān)注如 init, service
那么DispatcherServlet容器在初始化時做了什么赖欣?
HttpServlet#init() -> FrameworkServlet#initServletBean -> DispatchserServlet#initStrategies()
注:如果想要知道Serlevt如何工作屑彻,可以翻閱Tomcat源碼,以此看到工業(yè)級產(chǎn)品如何實(shí)現(xiàn)Servlet規(guī)范
翻閱DispatchserServlet#initStrategies()可以看到好幾個方法顶吮,方法名稱皆以init打頭社牲,那么它們就是在初始化什么?為什么在自己的程序中使用@Controller, @RequestMapping, @ResponseBody就可以跟前端完成常見的JSON交互了呢悴了?
為了完成非常復(fù)雜的功能交互搏恤,SpringMVC定義了非常多的頂級接口,而我們核心要關(guān)注的無非是HandlerMapping, HandlerAdpater
HandlerMapping
查閱DispatcherServlet#initHandlerMappings()方法時發(fā)現(xiàn)一個問題湃交,那就是它并非依據(jù)前文中提到的DispatcherServlet.properties中加載出來的對象熟空,而是從ApplicationContext中取
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
那么取出了什么Bean呢?
這些Bean從而何來搞莺?前文說過息罗,本文基于springboot 1.5.6版本,程序啟動加載spring-boot-autoconfig下的spring.factories文件才沧,其中比如org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration
由于我們實(shí)際編碼過程經(jīng)常使用@RequestMapping來完成URL路徑標(biāo)記迈喉,那么先從與之有些類似的RequestMappingHandlerMapping中翻閱代碼,看看初始化時核心放了什么温圆?
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
mapping.setInterceptors(getInterceptors());
mapping.setContentNegotiationManager(mvcContentNegotiationManager());
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer configurer = getPathMatchConfigurer();
if (configurer.isUseSuffixPatternMatch() != null) {
// 比如這個挨摸,很多開發(fā)者應(yīng)該都記得以前URL一般都有后綴,比如.action, .do, .ac等等
mapping.setUseSuffixPatternMatch(configurer.isUseSuffixPatternMatch());
}
if (configurer.isUseRegisteredSuffixPatternMatch() != null) {
mapping.setUseRegisteredSuffixPatternMatch(configurer.isUseRegisteredSuffixPatternMatch());
}
if (configurer.isUseTrailingSlashMatch() != null) {
mapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
}
UrlPathHelper pathHelper = configurer.getUrlPathHelper();
if (pathHelper != null) {
mapping.setUrlPathHelper(pathHelper);
}
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
mapping.setPathMatcher(pathMatcher);
}
return mapping;
}
單單查閱內(nèi)部方法岁歉,類的命名大致可以猜測出一些信息來得运,不過最終的使用還是要通過其他核心流程來分析。
HandlerAdpater
同理的方式可以看到核心的關(guān)注類
Servlet容器工作流程
DispatcherServlet#doDispatch()刨裆,可以認(rèn)為是Servlet規(guī)范中的service()語義澈圈。
截取核心代碼如下,并且將會按照入口核心源碼來分析
HandlerExecutionChain mappedHandler = getHandler(processedRequest);
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
ModelAndView mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
getHandler(processedRequest)
/**
* Return the HandlerExecutionChain for this request.
* <p>Tries all handler mappings in order.
* @param request current HTTP request
* @return the HandlerExecutionChain, or {@code null} if no handler could be found
*/
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;
}
方法語義其實(shí)很簡單帆啃,從注入的一堆HandlerMapping中查找合適的一個并返回瞬女。那么什么是合適的?查閱HandlerMapping中對于getHandler()方法的注解:
Return a handler and any interceptors for this request. The choice may be made
on request URL, session state, or any factor the implementing class chooses.
HandlerExecutionChain包含著真正的HandlerMapping努潘, 以及一系列的攔截器诽偷。通過DEBUG可以發(fā)現(xiàn)最終找到的對象為RequestMappingHandlerMapping,怎么匹配的呢疯坤?通過結(jié)果來反向推導(dǎo)原因报慕。Spring框架中大量使用了模板方法,所以看類實(shí)現(xiàn)過程要關(guān)注子類與父類之間的關(guān)系压怠。
RequestMappingHandlerMapping
類體系
先查閱整個類結(jié)構(gòu)體系眠冈,相當(dāng)復(fù)雜,不過不要緊,看關(guān)鍵點(diǎn)蜗顽。
HandlerMapping <-- AbstractHandlerMapping <-- AbstractHandlerMethodMapping <-- RequestMappingInfoHandlerMapping <--RequestMappingHandlerMapping
初始化流程
RequestMappingHandlerMapping作為一個由Spring托管的Bean布卡,在它初始化的生命周期中,有非常關(guān)鍵的一步雇盖,即afterPropertiesSet方法忿等,跟蹤上去,最終你會看到如下幾個核心的方法:
//初始化HandlerMethods
AbstractHandlerMethodMapping#initHandlerMethods
// 判斷這個類是否有@Controller 或者@RequestMapping
RequestMappingHandlerMapping#isHandler()
// 查找hanlder內(nèi)部方法
AbstractHandlerMethodMapping#detectHandlerMethods()
// 找到handler內(nèi)部帶有標(biāo)記的@RequestMapping的方法崔挖,包裝成RequestMappingInfo
RequestMappingHandlerMapping#getMappingForMethod()
// 找到一個可以被調(diào)用的目標(biāo)方法Method
AopUtils.selectInvocableMethod(entry.getKey(), userType)
// 將相信的handler贸街,method等信息注冊到MappingRegistry中,注冊前會將信息包裝成HandlerMethod
AbstractHandlerMethodMapping#registerHandlerMethod(handler, method, RequestMappingInfo)
// 注冊狸相,MappingRegistry有非常核心的Map薛匪,比如urlLookup
MappingRegistry#register(RequestMappingInfo, handler, Method)
最終的HandlerMethod包括了什么信息?
this.bean = bean;
this.beanType = ClassUtils.getUserClass(bean);
this.method = method;
this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
this.parameters = initMethodParameters();
注:這些信息最終可以通過反射來完成方法的調(diào)用卷哩。
在完成初始化之后蛋辈,當(dāng)進(jìn)來時,如何完成URL與Controller中的Method的對應(yīng)呢将谊?
映射
AbstractHandlerMethodMapping#getHandlerInternal()
AbstractHandlerMethodMapping#lookupHandlerMethod()
MappingRegistry#getMappingsByUrl()
當(dāng)完成初始化之后冷溶,通過請求的URL找到對應(yīng)的HandlerMethod其實(shí)非常簡單.
getHandlerAdapter()
通過RequestMappingHandlerMapping來找Adapter,先來看HandlerAdapter接口的定義:MVC framework SPI, allowing parameterization of the core MVC workflow.
它的核心在于尊浓,找到RequestMappingHandlerMapping來完成從請求到執(zhí)行到響應(yīng)的完整工作流程逞频。
通過DEBUG可以非常快速定外找栋齿,需要關(guān)注的核心實(shí)現(xiàn)為RequestMappingHandlerAdapter.通過其父類實(shí)現(xiàn)的support()其實(shí)也可以非趁缯停快速確定,因?yàn)樗С值?strong>Handler類型為HandlerMethod.
handler()
RequestMappingHandlerAdapter
可以非常明顯看到這個類內(nèi)部包含著一系列的HttpMessageConverter瓦堵,這些對象可以解決什么問題呢基协?
方法調(diào)用
RequestMappingHandlerAdapter#invokeHandlerMethod()
ServletInvocableHandlerMethod#invokeAndHandle()
InvocableHandlerMethod#invokeForRequest()
InvocableHandlerMethod#doInvoke()
// 最終方法執(zhí)行
Method#invoke()
也就是說,HTTP請求進(jìn)入SpringMVC應(yīng)用之后菇用,匹配到HandlerMethod澜驮,最終通過HandlerMethod中包含的Method對象,而Method通過Java反射即可完成方法調(diào)用惋鸥。
完成調(diào)用的過程分兩部分:
- 從HTTP中解析參數(shù)
- 將返回值進(jìn)行處理(比如聲明@ResponseBody)
參數(shù)解析
參數(shù)的解析以及映射是一件相當(dāng)復(fù)雜的工作杂穷,比如
@ResponseBody
@RequestMapping("data")
fun userData(@RequestParam TransferObject json) : UserData {
return UserData(...);
}
在一個完整的HTTP請求中,核心參數(shù)可能在URL上卦绣,也可以在Body上耐量,當(dāng)要完成參數(shù)解析時,需要從中取出參數(shù)滤港,并按照方法所需入?yún)⑦M(jìn)行封裝廊蜒,最后入?yún)⑼瓿煞椒ㄕ{(diào)用,欲深究請關(guān)注如下方法
InvocableHandlerMethod#getMethodArgumentValues()
返回值處理
返回值可能會有各式各樣的結(jié)果,一般情況下可能是一個JSON對象劲藐,通過DEBUG可以看到核心類RequestResponseBodyMethodProcessor
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
從HTTP協(xié)議來看八堡,在入?yún)r就已經(jīng)明確了響應(yīng)體的類型,核心參數(shù)為:accept聘芜,常見比如application/json, text/html缝龄, 等等汰现。SpringMVC如何處理這個問題呢?請聚焦于MediaType
AbstractMessageConverterMethodProcessor#writeWithMessageConverters()
// 從http請求中取出來后進(jìn)行解析
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
// 根據(jù)返回值判斷何時的MediaType
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
比如上請求叔壤,根據(jù)返回值判斷即為JSON瞎饲,故而結(jié)果如下圖:
MediaType將決定以何中HttpMessageConvert來完成對象的轉(zhuǎn)換處理,以及流輸出炼绘。
messageConverter.canWrite(declaredType, valueType, selectedMediaType)
messageConverter.write(outputValue, declaredType, selectedMediaType, outputMessage)
而判斷的核心在于實(shí)現(xiàn)類的supportedMediaTypes參數(shù)值嗅战,比如:
public MappingJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
}
鉤子函數(shù)
在將返回值寫入到輸出流中前,提供了鉤子函數(shù)RequestResponseBodyAdviceChain
processDispatchResult()
完成請求的后置處理俺亮。
比如mappedHandler.triggerAfterCompletion(request, response, null)
其實(shí)是將所有的后置攔截器完成調(diào)用驮捍。
思考
看完了完整的流程后,如果需要對SpringMVC做一些優(yōu)化脚曾,需要如何东且?
優(yōu)化的層面往往是往下往上兩個層面。
- 往下從使用層面去考慮本讥,選用更適合SpringMVC框架的用法珊泳,比如URL匹配規(guī)則簡單化
- 往上從優(yōu)化層面去考慮,比如減少不必要的Bean創(chuàng)建拷沸,簡化從HandlerMap, HandlerAdapter等的尋找鏈路色查,加快容器初始化,提高調(diào)用鏈路效率撞芍,等等