SpringMVC中<mvc:annotation-driven/>标签原理与实践详解

简介: SpringMVC中<mvc:annotation-driven/>标签原理与实践详解

本文研究背景:项目是传统SSM项目,非SpringBoot项目。项目使用xml方式配置SpringMVC并且没有手动往IOC容器中注入<mvc:annotation-driven/>注解注入的bean。

【1】BeanDefinitionParser


该接口被DefaultBeanDefinitionDocumentReader使用以处理自定义和顶级(直属<beans/>)标签。


实现类可以自由地将标签里面的元数据转化成许多需要的BeanDefinition。


解析器从标签隶属的namespace对应的NamespaceHandler找到合适的BeanDefinitonParser。该解析器的实现类有:

我们看到有多个AnnotationDrivenBeanDefinitionParser

他们是用来处理不同命名空间下的标签的,我们今天研究<annotation-driven/>的是<mvc:annotation-driven/>标签,所以我们找到对应的实现类org.springframework.web.servlet.config.AnnotationDrivenBeanDefinitionParser。

【2】AnnotationDrivenBeanDefinitionParser

通常如果我们希望通过注解的方式来进行Spring MVC开发,我们都会在***-servlet.xml中加入<mvc:annotation-driven/>标签来告诉Spring我们的目的。


但是我们为什么这么做呢?这个标签是什么意思呢?它做了什么呢?


同样为了弄清楚这些问题, 像<context:component-scan/>标签一样,我们先找到它的解析类。前面说过了,所有的自定义命名空间(像mvc,context等)下的标签解析都是由BeanDefinitionParser接口的子类来完成的。

AnnotationDrivenBeanDefinitionParser的文档说明该解析器想容器中注入了如下类:


HandlerMapping处理器映射器


RequestMappingHandlerMapping,ordered at 0 for mapping requests to annotated controller methods.

BeanNameUrlHandlerMapping,ordered at 2 to map URL paths to controller bean names.

也可以通过<mvc:view-controller>或<mvc:resources>注册其他的HandlerMappings

HandlerAdapter处理器适配器


RequestMappingHandlerAdapter,for processing requests with annotated controller methods.

HttpRequestHandlerAdapter,使用HttpRequestHandler处理请求

SimpleControllerHandlerAdapter,for processing requests with interface-based {@link Controller Controllers}.

HandlerExceptionResolver


ExceptionHandlerExceptionResolver,通过@ExceptionHandler注解声明的方法处理异常

ResponseStatusExceptionResolver,处理那些标注了@ResponseStatus注解

DefaultHandlerExceptionResolver,解析已知的Spring异常类型

上面几个Bean实例。这几个类都是用来做什么的呢?


前两个是HandlerMapping接口的实现类,用来处理请求映射的。


其中第一个是处理@RequestMapping注解的。.

第二个会将controller类的名字映射为请求url。


中间三个是用来处理请求的。具体点说就是确定调用哪个controller的哪个方法来处理当前请求。


第一个处理@Controller注解的处理器,支持自定义方法参数和返回值(很酷)。

第二个是处理继承HttpRequestHandler的处理器。

第三个处理继承自Controller接口的处理器。


后面三个是用来处理异常的解析器

【3】代码追溯

@Override
@Nullable
public BeanDefinition parse(Element element, ParserContext context) {
  Object source = context.extractSource(element);
  XmlReaderContext readerContext = context.getReaderContext();
  CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
  context.pushContainingComponent(compDefinition);
  RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, context);
// RequestMappingHandlerMapping
  RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
  handlerMappingDef.setSource(source);
  handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  handlerMappingDef.getPropertyValues().add("order", 0);
  handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
  if (element.hasAttribute("enable-matrix-variables")) {
    boolean enableMatrixVariables = Boolean.parseBoolean(element.getAttribute("enable-matrix-variables"));
    handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
  }
  configurePathMatchingProperties(handlerMappingDef, element, context);
  readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME, handlerMappingDef);
  // 跨域配置
  RuntimeBeanReference corsRef = MvcNamespaceUtils.registerCorsConfigurations(null, context, source);
  handlerMappingDef.getPropertyValues().add("corsConfigurations", corsRef);
//ConversionService
  RuntimeBeanReference conversionService = getConversionService(element, source, context);
  // Validator
  RuntimeBeanReference validator = getValidator(element, source, context);
  //MessageCodesResolver
  RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);
//ConfigurableWebBindingInitializer
  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);
  ManagedList<?> messageConverters = getMessageConverters(element, source, context);
  ManagedList<?> argumentResolvers = getArgumentResolvers(element, context);
  ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, context);
  String asyncTimeout = getAsyncTimeout(element);
  RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
  ManagedList<?> callableInterceptors = getInterceptors(element, source, context, "callable-interceptors");
  ManagedList<?> deferredResultInterceptors = getInterceptors(element, source, context, "deferred-result-interceptors");
//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);
//CompositeUriComponentsContributorFactoryBean
  RootBeanDefinition uriContributorDef =
      new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
  uriContributorDef.setSource(source);
  uriContributorDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
  uriContributorDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
  String uriContributorName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
  readerContext.getRegistry().registerBeanDefinition(uriContributorName, uriContributorDef);
// ConversionServiceExposingInterceptor
  RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
  csInterceptorDef.setSource(source);
  csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
  RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
  mappedInterceptorDef.setSource(source);
  mappedInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
  mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
  String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedInterceptorDef);
// ExceptionHandlerExceptionResolver
  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);
// ResponseStatusExceptionResolver
  RootBeanDefinition statusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
  statusExceptionResolver.setSource(source);
  statusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  statusExceptionResolver.getPropertyValues().add("order", 1);
  String statusExResolverName = readerContext.registerWithGeneratedName(statusExceptionResolver);
// DefaultHandlerExceptionResolver
  RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
  defaultExceptionResolver.setSource(source);
  defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  defaultExceptionResolver.getPropertyValues().add("order", 2);
  String defaultExResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver);
  context.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
  context.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
  context.registerComponent(new BeanComponentDefinition(uriContributorDef, uriContributorName));
  context.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, mappedInterceptorName));
  context.registerComponent(new BeanComponentDefinition(methodExceptionResolver, methodExResolverName));
  context.registerComponent(new BeanComponentDefinition(statusExceptionResolver, statusExResolverName));
  context.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExResolverName));
// BeanNameUrlHandlerMapping  
//HttpRequestHandlerAdapter 
//SimpleControllerHandlerAdapter
  // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
  MvcNamespaceUtils.registerDefaultComponents(context, source);
  context.popAndRegisterContainingComponent();
  return null;
}



注册默认mapping和adapter方法如下:

public static void registerDefaultComponents(ParserContext parserContext, Object source) {
  registerBeanNameUrlHandlerMapping(parserContext, source);
  registerHttpRequestHandlerAdapter(parserContext, source);
  registerSimpleControllerHandlerAdapter(parserContext, source);
}


【4】总结


我们知道了它们自动为我们注册了这么多的Bean,那这些Bean是做什么的呢?主要说明里面的两个,RequestMappingHandlerMapping和RequestMappingHandlerAdapter。


第一个是HandlerMapping的实现类,它会处理@RequestMapping 注解,并将其注册到请求映射表中。

.

第二个是HandlerAdapter的实现类,它是处理请求的适配器,说白了,就是确定调用哪个类的哪个方法,并且构造方法参数,返回值。


那么它跟<context:component-scan/>有什么区别呢?


其实<context:component-scan/>标签是告诉Spring 来扫描指定包下的类,并注册被@Component,@Controller,@Service,@Repository等注解标记的组件(即,项目启动完成前这些被注解的组件就会被实例化bean放入容器上下文中)和注册AutowiredAnnotationBeanPostProcessor、 RequiredAnnotationBeanPostProcessor、 CommonAnnotationBeanPostProcessor以及 PersistenceAnnotationBeanPostProcessor。


而<mvc:annotation-driven/>是告知Spring,我们启用注解驱动,为WEB 应用服务(我们就可以使用该标签注册的几个bean的功能)。然后Spring会自动为我们注册上面说到的几个Bean到工厂中,来处理我们的请求。换句话说,<context:component-scan/>向容器中显式或隐式注册了一系列单个组件,但是项目要想将组件关联起来正常运转,则需要<mvc:annotation-driven/>注册的组件完成。


比如请求一个URL,我要知道这个URL匹配哪个controller中的哪个方法。哪个Controller就需要<context:component-scan/>注解,如何匹配,匹配哪个方法就需要<mvc:annotation-driven/>注解。


当然,你也可以不使用<context:component-scan/>而手动将所有需要的bean显示注册进applicationContext.xml中,这实在是太愚蠢!如果不想手动配置bean(xml),使用注解获取bean就必须使用<context:component-scan/>。


如果想使web项目正常运转,一般情况下,<mvc:annotation-driven/>和<context:component-scan/>是必需的!项目中二者相辅相成,缺一不可!


当然随着版本迭代、尤其是现在SpringBoot主流为我们省去了很多配置。但是想往高级走,我们还是需要了解其底层原理细节。

目录
相关文章
|
28天前
|
XML 前端开发 Java
拼多多1面:聊聊Spring MVC的工作原理!
本文详细剖析了Spring MVC的工作原理,涵盖其架构、工作流程及核心组件。Spring MVC采用MVC设计模式,通过DispatcherServlet、HandlerMapping、Controller和ViewResolver等组件高效处理Web请求。文章还探讨了DispatcherServlet的初始化和请求处理流程,以及HandlerMapping和Controller的角色。通过理解这些核心概念,开发者能更好地构建可维护、可扩展的Web应用。适合面试准备和技术深挖
40 0
|
5月前
|
设计模式 前端开发 Java
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
【Spring MVC】快速学习使用Spring MVC的注解及三层架构
72 1
|
6月前
|
前端开发 JavaScript Java
MVC框架:SpringMVC(三)
MVC框架:SpringMVC
59 0
|
6月前
|
JSON 前端开发 JavaScript
MVC框架:SpringMVC(二)
MVC框架:SpringMVC
64 0
|
6月前
|
前端开发 Java 应用服务中间件
MVC框架:SpringMVC(一)
MVC框架:SpringMVC
98 0
|
6月前
|
前端开发 Java 数据管理
Spring MVC 工作原理解析
Spring MVC 工作原理解析
105 0
|
6月前
|
前端开发 Java Spring
【Spring MVC】SpringMVC自动配置
【1月更文挑战第14天】【Spring MVC】SpringMVC自动配置
|
3月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
45 0
|
6月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
187 0
|
6月前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
77 0