SpingMVC之<mvc:annotation-driven/>标签

对于SpringMVC的探索已经接近尾声,本篇笔记主要记录下SpringMVC为我们提供的一个神奇标签 <mvc:annotation-driven/>,这个标签会帮我们注入很多关键而实用的bean,但是用它也得小心跟自己手动注入的bean重复,会造成不必要的麻烦。所以今天来了解下这个标签。
本篇笔记主要分析SpringMVC 5.1.1 这个版本。

为了弄清楚这些问题,我们先找到它的解析类,所有的自定义命名空间(像mvc,context等)下的标签解析都是由BeanDefinitionParser 接口的实现类来完成的。我们今天研究的是<mvc:annotation-driven/>标签,所以我们找到对应的实现类是org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser

1 简单了解下功能

AnnotationDrivenBeanDefinitionParser,为 <annotation-driven /> MVC名称空间元素提供配置。

1.1 注册以下HandlerMappings (映射器们):

  • RequestMappingHandlerMapping 的排序为0,用于将请求映射到带@RequestMapping注释的控制器方法。
  • BeanNameUrlHandlerMapping 在排序为2,以将URL路径映射到控制器bean名称。

1.2 注册以下HandlerAdapters (适配器们):

  • RequestMappingHandlerAdapter 用于使用带@RequestMapping注解的控制器方法处理请求。
  • HttpRequestHandlerAdapter 用于使用HttpRequestHandlers处理请求。
  • SimpleControllerHandlerAdapter 用于使用基于接口的控制器处理请求。

1.3 注册以下HandlerExceptionResolvers (异常处理解析器们):

  • ExceptionHandlerExceptionResolver,用于通过 org.springframework.web.bind.annotation.ExceptionHandler 方法处理异常。
  • ResponseStatusExceptionResolver 用于使用 org.springframework.web.bind.annotation.ResponseStatus 注释的异常。
  • DefaultHandlerExceptionResolver 用于解析已知的Spring异常类型

1.4 其他

注册 org.springframework.util.AntPathMatcherorg.springframework.web.util.UrlPathHelper 以供 RequestMappingHandlerMappingViewControllersHandlerMappingHandlerMapping 服务资源是使用。

对于JSR-303实现,会检测 javax.validation.Validator 路径是否有效,有效则会帮我们创建对应的实现类并注入。

最后帮我们检测一些列 HttpMessageConverter 的实现类们,这些主要是用作直接对请求体里面解析出来的数据进行转换。俗称 http 消息转换器,与参数转换器不一样。
在 SpringMVC 5.1.1 中有以下几个检测:

检测路径 注入消息转换器 对应请求类型
com.rometools.rome.feed.WireFeed RssChannelHttpMessageConverter application/atom+xml
javax.xml.bind.Binder Jaxb2RootElementHttpMessageConverter application/xml
com.fasterxml.jackson.databind.ObjectMapper & com.fasterxml.jackson.core.JsonGenerator MappingJackson2HttpMessageConverter application/json
com.fasterxml.jackson.dataformat.xml.XmlMapper MappingJackson2XmlHttpMessageConverter application/xml
com.fasterxml.jackson.dataformat.smile.SmileFactory MappingJackson2SmileHttpMessageConverter application/x-jackson-smile
com.fasterxml.jackson.dataformat.cbor.CBORFactory MappingJackson2CborHttpMessageConverter application/cbor
com.google.gson.Gson GsonHttpMessageConverter application/json

除了会帮我们注入以上检测有效的 http 消息转换器外,还会帮我们注入SpringMVC自带的几个 http 消息转换器,上面检测的转换器是由上到下顺序加入的,也就是说解析的时候回根据 ContentType 从上到下找合适的。

2 源码简介

该标签的解释是在 org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser 类的 parse(..) 方法中

2.1 HandlerMappings 注册

2.1.1 RequestMappingHandlerMapping 映射器的注册
//生成RequestMappingHandlerMapping组件对象
RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
handlerMappingDef.setSource(source);
handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
//优先级设置为最高
handlerMappingDef.getPropertyValues().add("order", 0);
//添加contentNegotiationManager属性,处理media type
handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);

if (element.hasAttribute("enable-matrix-variables")) {
    Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
    handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
}

//配置路径匹配解析器等属性
configurePathMatchingProperties(handlerMappingDef, element, context);
readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);

//将RequestMappingHandlerMapping注册为bean对象放置bean工厂中
RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);
2.1.1 BeanNameUrlHandlerMapping 映射器注册就比较随意
// Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
MvcNamespaceUtils.registerDefaultComponents(context, source);

放在这里跟 HttpRequestHandlerAdapterSimpleControllerHandlerAdapterHandlerMappingIntrospector 一起注册的。

2.2 HandlerAdapters 注册

2.2.1 RequestMappingHandlerAdapter 适配器的注册
//从该标签的 "conversion-service" 属性中获取注入到容器里面的参数转换服务器, 没有则重新创建
RuntimeBeanReference conversionService = getConversionService(element, source, context);
//从该标签的 "validator" 属性中获取注入到容器里面的参数验证服务, 没有则创建
RuntimeBeanReference validator = getValidator(element, source, context);
//从该标签的 "message-codes-resolver" 属性中获取错误码解析器, 没有则不创建
RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);

//创建 `WebDataBinder` 初始化使用到的记录器, 并将上面的参数转换和验证相关绑定在其上面
RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
bindingDef.setSource(source);
bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
bindingDef.getPropertyValues().add("conversionService", conversionService);
bindingDef.getPropertyValues().add("validator", validator);
bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);

//从该标签的 `message-converters` 属性上获取记录请求体转换器的集合,没有则创建默认的
ManagedList<?> messageConverters = getMessageConverters(element, source, context);
//从该标签的 `argument-resolvers` 属性上获取记录自定义参数转换器的集合
ManagedList<?> argumentResolvers = getArgumentResolvers(element, context);
//从该标签的 `return-value-handlers` 属性上获取记录自定义返回值转换器的集合
ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, context);
//从该标签的 "async-support" 子节点解析,获取其中的 "default-timeout" 属性,作为异步处理超时时间,默认null
String asyncTimeout = getAsyncTimeout(element);
//从该标签的 "async-support" 子节点解析,获取其中的 "task-executor" 属性,异步任务线程池
RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
//从该标签的 "async-support" 子节点解析,获取其中的 "callable-interceptors"节点,异步处理callable类型拦截器
ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, context);
ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, context);

//生成RequestMappingHandlerAdapter组件对象,并将上面获取的相关绑定在该映射器上
RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
handlerAdapterDef.setSource(source);
handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
addRequestBodyAdvice(handlerAdapterDef);
addResponseBodyAdvice(handlerAdapterDef);

if (element.hasAttribute("ignore-default-model-on-redirect")) {
    Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
    handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
}
if (argumentResolvers != null) {
    handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
    handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
if (asyncTimeout != null) {
    handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
}
if (asyncExecutor != null) {
    handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
}

handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef);
2.2.2 HttpRequestHandlerAdapterSimpleControllerHandlerAdapter 适配器的注册就比较随意
registerHttpRequestHandlerAdapter(parserContext, source);
registerSimpleControllerHandlerAdapter(parserContext, source);

2.3 HandlerExceptionResolvers 组件注册

默认采用 ExceptionHandlerExceptionResolver(处理 @ExceptionHandler 方法注解)、ResponseStatusExceptionResolver(处理 @ResponseStatus 类型、方法注解)、DefaultHandlerExceptionResolver(处理普通的 Spring 异常) 作为异常处理类

RootBeanDefinition methodExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
methodExceptionResolver.setSource(source);
methodExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
methodExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
methodExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
methodExceptionResolver.getPropertyValues().add("order", 0);
addResponseBodyAdvice(methodExceptionResolver);
if (argumentResolvers != null) {
    methodExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
}
if (returnValueHandlers != null) {
    methodExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
}
String methodExResolverName = readerContext.registerWithGeneratedName(methodExceptionResolver);

RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
statusExceptionResolver.setSource(source);
statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
statusExceptionResolver.getPropertyValues().add("order", 1);
String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);

RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
defaultExceptionResolver.setSource(source);
defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
defaultExceptionResolver.getPropertyValues().add("order", 2);
String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);

2.4 其他的根据检测有效路径注册

static {
    ClassLoader classLoader = AnnotationDrivenBeanDefinitionParser.class.getClassLoader();
    javaxValidationPresent = ClassUtils.isPresent("javax.validation.Validator", classLoader);
    romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
    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);
    jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
    gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
}

后面会根据上面 Class 路径的检测结果注入相关的验证器和转换器。

其他相关文章

SpringMVC入门笔记
SpringMVC工作原理之处理映射[HandlerMapping]
SpringMVC工作原理之适配器[HandlerAdapter]
SpringMVC工作原理之参数解析
SpringMVC之自定义参数解析
SpringMVC工作原理之视图解析及自定义
SpingMVC之<mvc:annotation-driven/>标签

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容