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

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. 总结

所以SpringMVC对的读取可以归纳如下: (2018/1/24新增)

  1. <context:component-scan/> 默认是将所有@Component注解的类扫描进容器; @Controller自身就是被@Component修饰的
  2. <mvc:annotation-driven /> 的解析工作是由 AnnotationDrivenBeanDefinitionParser 类全权负责的。
  3. AnnotationDrivenBeanDefinitionParser会向SpringMVC容器中注册了 RequestMappingHandlerMapping (间接实现了HandlerMapping接口), order为0
  4. 查看RequestMappingHandlerMapping 类的继承链就会发现, 其祖先类 AbstractHandlerMethodMapping<T> 实现了 InitializingBean
  5. 而对InitializingBean的实现中, 会回调RequestMappingHandlerMapping.isHandler的实现,在那里你就可以看到对@RequestMapping注解的扫描了.

5. 引用

依然是CSDN推送的文章

  1. mvc:resources拦截资源显示问题
  2. mvc:annotation-driven中的HandlerMethodReturnValueHandler
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值