SpringMVC源码研究之注解<mvc:annotation-driven />

来源:互联网 发布:淘宝买书靠谱吗 编辑:程序博客网 时间:2024/06/05 18:11

Spring-MVC注解

1. 前言

  1. 通过查看 spring-webmvc-xxx.jar 下的spring.handlers文件可以发现 mvc前缀的标签都是由 MvcNamespaceHandler 来进行解析的。
  2. 通过查看其内部唯一的方法 init() 的实现可以确定 <mvc:annotation-driven /> 的解析工作是由 AnnotationDrivenBeanDefinitionParser 类全权负责的。

2. AnnotationDrivenBeanDefinitionParser

其所实现的接口 BeanDefinitionParser 的作用和意义就不再赘述了.

观察该类对所继承接口的实现可以发现:
1. 向SpringMVC容器中注册了 ContentNegotiationManagerFactoryBean
2. 向SpringMVC容器中注册了 RequestMappingHandlerMapping (间接实现了HandlerMapping接口), order为0
3. 向SpringMVC容器中注册了 RequestMappingHandlerAdapter (间接实现了HandlerAdapter接口,直接实现了InitializingBean接口,关于这个接口的实现,参见本人的另外一篇博客)
4. 向SpringMVC容器中注册了 MappedInterceptor
5. 向SpringMVC容器中注册了 ExceptionHandlerExceptionResolver (间接实现了HandlerExceptionResolver接口), order为0
6. 向SpringMVC容器中注册了 ResponseStatusExceptionResolver (间接实现了HandlerExceptionResolver接口), order为1
7. 向SpringMVC容器中注册了 DefaultHandlerExceptionResolver (间接实现了HandlerExceptionResolver接口), order为2

2.1 日志分析

以下是从启动日志里提取出来的, 为了注释特意进行了换行, 原本只是一行.

信息: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@d672d2:  // SpringMVC容器defining beans [    mvcContentNegotiationManager,  // 在AnnotationDrivenBeanDefinitionParser类的getContentNegotiationManager()方法中进行注册    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0, //    org.springframework.format.support.FormattingConversionServiceFactoryBean#0, // 在AnnotationDrivenBeanDefinitionParser类的getConversionService()方法中进行注册    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#0, //    org.springframework.web.servlet.handler.MappedInterceptor#0, //    org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0, //    org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0, //    org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0, // 以上可以参见上面的解释    org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,     org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,  //    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter, // 以上三个由MvcNamespaceUtils.registerDefaultComponents()方法完成    org.springframework.aop.config.internalAutoProxyCreator, //    loginController, // 自定义    userController, // 自定义    org.springframework.context.annotation.internalConfigurationAnnotationProcessor,    org.springframework.context.annotation.internalAutowiredAnnotationProcessor,    org.springframework.context.annotation.internalRequiredAnnotationProcessor,    org.springframework.context.annotation.internalCommonAnnotationProcessor,    org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0, // 在spring-mvc.xml中的<mvc:resources />    viewResolver, // 在spring-mvc.xml中注册]; parent: org.springframework.beans.factory.support.DefaultListableBeanFactory@f80021

3. 绑定Handler与Method

信息: Mapped “{[//logout],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}” onto public void cn.springmvc.controller.LoginController.logout(javax.servlet.http.HttpServletResponse,javax.servlet.http.HttpSession)

以上这一句日志信息属于INFO级别; 位置位于 RequestMappingHandlerMapping 类的祖先类AbstractHandlerMethodMapping<T> 内的registerHandlerMethod方法中.

我们仔细查看RequestMappingHandlerMapping 类的继承链就会发现, 其祖先类 AbstractHandlerMethodMapping<T> 实现了 InitializingBean , 如此有名的接口我就不多介绍了. 现在就让我们直接查看AbstractHandlerMethodMapping<T> 对该接口的实现吧.

/** * Detects handler methods at initialization. */public void afterPropertiesSet() {    initHandlerMethods();}/** * Scan beans in the ApplicationContext, detect and register handler methods. * @see #isHandler(Class) * @see #getMappingForMethod(Method, Class) * @see #handlerMethodsInitialized(Map) */protected void initHandlerMethods() {    // this.detectHandlerMethodsInAncestorContexts默认为false    // getApplicationContext()获取到的是SpringMVC容器    // 注意我们在spring-mvc.xml中除了键入<mvc:annotation-driven />之外,    //  还会键入<context:component-scan base-package="xx.yyy.zzz" />;关于这个自定义标签, 请参见本人的上一篇博客    String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?            BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :            getApplicationContext().getBeanNamesForType(Object.class));    for (String beanName : beanNames) {        // isHandler在AbstractHandlerMethodMapping<T>类中抽象方法;         // RequestMappingHandlerMapping对其的实现请看下面        if (isHandler(getApplicationContext().getType(beanName))){            // 查看下面的讲解            detectHandlerMethods(beanName);        }    }    handlerMethodsInitialized(getHandlerMethods());}

3.1 isHandler

RequestMappingHandlerMapping类对祖先类AbstractHandlerMethodMapping<T>类的实现

// 判断该Bean是否属于Handler,//    关于Controller注解, 其被@Component修饰着, 更多的请参见本人的上一篇博客// 所以这里默认是筛选出所有的我们注册进SpringMVC容器中的标记了@Controller的Bean(例如使用<context:component-scan/>)protected boolean isHandler(Class<?> beanType) {    return ((AnnotationUtils.findAnnotation(beanType, Controller.class) != null) ||            (AnnotationUtils.findAnnotation(beanType, RequestMapping.class) != null));}   

3.2 detectHandlerMethods

具体实现位于AbstractHandlerMethodMapping<T> 类中, 详细代码就不贴了. 我们只看其中的重点内容

getMappingForMethod(method, userType)

其中:
1. userType为我们自定义的, 被@Controller注解所修饰的类.
2. method为 java.lang.reflect.Method 类型, 为我们所自定义的userType类型中的方法.
3. getMappingForMethod方法也是抽象的, 交由RequestMappingHandlerMapping类来实现.

registerHandlerMethod(handler, method, mappings.get(method));

其中:
1. handler为传入的bean name
2. method为 java.lang.reflect.Method 类型, 为我们所自定义的userType类型中的方法.
3. mappings.get(method)为我们使用getMappingForMethod 构造的RequestMappingInfo 实例

3.3 getMappingForMethod

RequestMappingHandlerMapping类进行实现

protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {    RequestMappingInfo info = null;    // 抽取该方法上注解的@RequestMapping    RequestMapping methodAnnotation = AnnotationUtils.findAnnotation(method, RequestMapping.class);    if (methodAnnotation != null) {        // getCustomMethodCondition默认返回null        RequestCondition<?> methodCondition = getCustomMethodCondition(method);        info = createRequestMappingInfo(methodAnnotation, methodCondition);        // 抽取handlerType(即我们自定义的Controller类)上注解的@RequestMapping        RequestMapping typeAnnotation = AnnotationUtils.findAnnotation(handlerType, RequestMapping.class);        // 如果该方法所在的类也有@RequestMapping信息, 则进行合并        if (typeAnnotation != null) {            RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);            // 合并两个RequestMapping的信息            info = createRequestMappingInfo(typeAnnotation, typeCondition).combine(info);        }    }    return info;}

3.4 createRequestMappingInfo

具体实现位于RequestMappingHandlerMapping

protected RequestMappingInfo createRequestMappingInfo(        RequestMapping requestMapping, RequestCondition<?> customCondition) {    return RequestMappingInfo            .paths(resolveEmbeddedValuesInPatterns(requestMapping.path())) /* 这里说明我们是可以在配置@RequestMapping时, 使用spel表达式的; 例如@RequestMapping("/yuyue/${url.mh}/login") */            .methods(requestMapping.method())            .params(requestMapping.params())            .headers(requestMapping.headers())            .consumes(requestMapping.consumes())            .produces(requestMapping.produces())            .mappingName(requestMapping.name())            .customCondition(customCondition)            .options(this.config)            .build();}

3.5 registerHandlerMethod

对上面抽取出来RequestMappingInfo 信息 注册到 LinkedHashMap<RequestMappingInfo,HandlerMethod>类型的全局字段handlerMethods和全局字段LinkedMultiValueMap<String, RequestMappingInfo>类型的全局字段urlMap中.

4. 引用

依然是CSDN推送的文章
1. mvc:resources拦截资源显示问题
2. mvc:annotation-driven中的HandlerMethodReturnValueHandler