上篇文章中我們看到了Spring官方對DispatcherServlet的幾個特殊Bean的介紹撤防,其中第二個就是HandlerAdapter棒口,它是DispatcherServlet調(diào)用handler的關(guān)鍵,因為它會對DispatcherServlet屏蔽細(xì)節(jié)實現(xiàn)剥懒,讓DispatcherServlet只關(guān)心調(diào)用結(jié)果而不需要去關(guān)心調(diào)用細(xì)節(jié)(例如參數(shù)類型轉(zhuǎn)換及封裝等)初橘。下面我們來分析HandlerAdapter的初始化過程
我們已經(jīng)知道DispatcherServlet有一個默認(rèn)配置文件充岛,里面配置了各種策略。對于HandlerAdapter來說存在3個默認(rèn)配置夜只,他們分別是
- HttpRequestHandlerAdapter 這個處理器是用來處理靜態(tài)資源的
- SimpleControllerHandlerAdapter 這個處理器是用來處理在實現(xiàn)了Controller接口或者AbstractController等Spring的默認(rèn)實現(xiàn)類的子類
- RequestMappingHandlerAdapter 這個處理器用來處理@RequestMapping注解的處理器蒜魄,也是最常用,用的最多的一個旅挤,我們今天會著重分析這個
DispatcherServlet在初始化幾個策略時遇到使用默認(rèn)策略均會調(diào)用createDefaultStrategy方法粘茄,最終通過IOC容器創(chuàng)建對象并執(zhí)行回調(diào)秕脓。通過RequestMappingHandlerAdapter的類關(guān)系圖我們可以看出RequestMappingHandlerAdapter同樣實現(xiàn)了InitializingBean接口,因此我們關(guān)注的重點一個是在實例化階段芙贫,一個是在回調(diào)InitializingBean接口方法階段诵肛。
首先我們來看實例化階段
// 祖先類中會設(shè)置allowHeader為除了Trace外所有的HttpMethod但并未設(shè)置supportedMethods
public WebContentGenerator(boolean restrictDefaultSupportedMethods) {
if (restrictDefaultSupportedMethods) {
this.supportedMethods = new LinkedHashSet<>(4);
this.supportedMethods.add(METHOD_GET);
this.supportedMethods.add(METHOD_HEAD);
this.supportedMethods.add(METHOD_POST);
}
initAllowHeader();
}
private void initAllowHeader() {
Collection<String> allowedMethods;
// 這里配置allowedMethods
if (this.supportedMethods == null) {
allowedMethods = new ArrayList<>(HttpMethod.values().length - 1);
for (HttpMethod method : HttpMethod.values()) {
if (method != HttpMethod.TRACE) {
allowedMethods.add(method.name());
}
}
}
else if (this.supportedMethods.contains(HttpMethod.OPTIONS.name())) {
allowedMethods = this.supportedMethods;
}
else {
allowedMethods = new ArrayList<>(this.supportedMethods);
allowedMethods.add(HttpMethod.OPTIONS.name());
}
this.allowHeader = StringUtils.collectionToCommaDelimitedString(allowedMethods);
}
// 在自身的構(gòu)造器中添加了StringHttpMessageConverter、ByteArrayHttpMessageConverter蓄诽、
// SourceHttpMessageConverter媒吗、AllEncompassingFormHttpMessageConverter
public RequestMappingHandlerAdapter() {
StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
stringHttpMessageConverter.setWriteAcceptCharset(false); // see SPR-7316
this.messageConverters = new ArrayList<>(4);
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(stringHttpMessageConverter);
try {
this.messageConverters.add(new SourceHttpMessageConverter<>());
}
catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
}
上述代碼中初始化了4種消息轉(zhuǎn)換器闸英,針對不同的content-type,設(shè)置不同的消息轉(zhuǎn)換器
- StringHttpMessageConverter 支持text/plain或所有/的請求類型
- ByteArrayHttpMessageConverter 支持application/octet-stream或所有/的請求類型
- SourceHttpMessageConverter 支持text/xml出吹、application/xml捶牢、application/*-xml這三種類型的請求
- AllEncompassingFormHttpMessageConverter 支持application/x-www-form-urlencoded的讀寫(file upload)和multipart/form-data格式的寫操作(RestTemplate或WebClient中發(fā)送數(shù)據(jù))巍耗。同時會根據(jù)classpath是否包含class文件來決定是否添加對xml和json的處理。
/**
* jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
* jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
* jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
* jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
* gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
* jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
*/
public AllEncompassingFormHttpMessageConverter() {
try {
addPartConverter(new SourceHttpMessageConverter<>());
}
catch (Error err) {
// Ignore when no TransformerFactory implementation is available
}
if (jaxb2Present && !jackson2XmlPresent) {
addPartConverter(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
addPartConverter(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
addPartConverter(new GsonHttpMessageConverter());
}
else if (jsonbPresent) {
addPartConverter(new JsonbHttpMessageConverter());
}
if (jackson2XmlPresent) {
addPartConverter(new MappingJackson2XmlHttpMessageConverter());
}
if (jackson2SmilePresent) {
addPartConverter(new MappingJackson2SmileHttpMessageConverter());
}
}
實例化階段主要就做了這些操作,后面在分析DispatcherServlet處理過程時詳細(xì)分析各轉(zhuǎn)換器的作用亲族。
下面我們再來關(guān)注初始化后的回調(diào),開始之前我們先來看兩張圖
以上兩張圖來自Spring官方文檔票腰,介紹了SpringMVC的Controller中支持的請求參數(shù)類型和返回值類型。其中我們眼熟的@RequestParam测柠、@PathVariable、@RequestBody谒主、Map赃阀、Model擎颖、ServletRequest搂捧、ServletResponse懂缕、@ResponseBody、ModelAndView等等聋丝。下面就會針對上述的類型注冊相關(guān)解析器或處理器工碾。
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
// 在這個方法內(nèi)會獲取當(dāng)前IOC容器中所有注有@ControllerAdvice的Bean,將他們包裝成ControllerAdviceBean况木。
// 并將他們分條件緩存端圈。條件包括注有@InitBinder的方法,注有@ModelAttribute但沒有@RequestMapping的方法矗晃。
// 還會判斷ControllerAdviceBean上是否注有RequestBodyAdvice或ResponseBodyAdvice宴倍,若存在也會緩存
initControllerAdviceCache();
// 這里會初始化參數(shù)解析器(包括內(nèi)建解析器和通過setCustomArgumentResolvers方法自定義的解析器)
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
// 這里會初始化一系列解析@InitBinder的解析器
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
// 這里會初始化返回值處理器()包括內(nèi)建處理器和通過setReturnValueHandlers自定義的處理器)
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
至此初始化過程基本結(jié)束俗他,后面的異常處理、視圖解析兆衅、本地化嗜浮、樣式、文件上傳和請求轉(zhuǎn)發(fā)處理我們以后在分析畏铆,下一篇我們會開始分析DispatcherServlet的處理過程吉殃。
在查看官方文檔的時候發(fā)現(xiàn)Spring現(xiàn)在推薦使用MVC Config作為最佳入口楷怒,因為它申明了必須的Special Bean同時提供要給高等級的自定義配置回調(diào)API鸠删。
只有無法找到MVC Config時才會使用DispatcherServlet.properties進行默認(rèn)配置冶共。后面有機會我們會重新分析MVC Config方式下的操作流程每界。