Spring 注解编程之模式注解
阅读本文大概需要 5.5 分钟。
Spring 框架中有很多可用的注解,其中有一类注解称模式注解(Stereotype Annotations),包括
@Component,
@Service,
@Controller,
@Repository等。只要在相应的类上标注这些注解,就能成为 Spring 中组件(Bean)。
需要配置开启自动扫描。如在 XML 中配置` 或使用注解 @ComponentScan。
从最终的效果上来看,
@Component,
@Service,
@Controller,
@Repository起到的作用完全一样,那为何还需要多个不同的注解?
从官方 wiki 我们可以看到原因。
A stereotype annotation is an annotation that is used to declare the role that a component plays within the application. For example, the
@Repositoryannotation in the Spring Framework is a marker for any class that fulfills the role or stereotype of a repository (also known as Data Access Object or DAO).
不同的模式注解虽然功能相同,但是代表含义却不同。
标注
@Controller注解,这类组件就可以表示为 WEB 控制层 ,处理各种 HTTP 交互。标注
@Service可以表示为内部服务层 ,处理内部服务各种逻辑。而
@Repository可以代表示为数据控制层,代表数据库增删改查动作。
这样一来不同模式注解带来了不同的含义,清晰将服务进行分层。
除了上面的作用,特定的模式注解,Spring 可能会在未来增加额外的功能语义。如现在
@Repository注解,可以增加异常的自动转换功能。
所以,对于分层服务最好使用各自特定语义的模式注解,如 WEB 层就使用
@Controller注解。
模式注解原理
在 Spring 中任何标注
@Component的组件都可以成为扫描的候选对象。另外任何使用
@Component标注的注解,如
@Service,当其标注组件时,也能被当做扫描的候选对象。。
@Componentis a generic stereotype for any Spring-managed component. Any component annotated with@Componentis a candidate for component scanning. Similarly, any component annotated with an annotation that is itself meta-annotated with@Componentis also a candidate for component scanning. For example,@Serviceis meta-annotated with@Component.
如果想使自定义的注解也能如
@Service注解功能一样,只要在自定义注解上标注
@Component就可以。
AnnotationMetadata
5b4从上面文档看出只要在类上存在
@Component注解,即使存在于注解的注解上,Spring 都将能其成为候选组件。
注解上的注解 Spring 将其定义为元注解(meta-annotation),如
@Component标注在@Service上,@Component就被称作为元注解。后面我们就将注解的注解称为元注解。A meta-annotation is an annotation that is declared on another annotation. An annotation is therefore meta-annotated if it is annotated with another annotation. For example, any annotation that is declared to be documented is meta-annotated with
@Documentedfrom thejava.lang.annotationpackage.
那么对于一个类是否可以成为 Spring 组件,需要判断这个类是否包含
@Component注解,或者类上元注解中是否包含
@Component。
在 Spring 中可以通过
MetadataReader获取
ClassMetadata以及
AnnotationMetadata,然后获取相应元数据。
ClassMetadata可以获取类的各种元数据,比如类名,接口等。
而
AnnotationMetadata可以获取当前类上注解的元数据,如注解名字,以及元注解信息等。
所以只要获取到 AnnotationMetadata,就可以判断是否存在
@Component。判断方式如下
获取 AnnotationMetadata
这里我们从 XML 配置开启扫描开始讲起。
<context:component-scanbase-package="xxx.xxx.xx"/>
首先在 META-INF 下查找 spring.handles 文件。
不明白小伙伴们可以查看上一篇文章 缘起 Dubbo ,讲讲 Spring XML Schema 扩展机制
context 标签在
ContextNamespaceHandler注册 XML 解析器。在
ContextNamespaceHandler中其使用了
ComponentScanBeanDefinitionParser真正解析 XML。
在
ComponentScanBeanDefinitionParser#parse方法中,首先获取 XML 中配置
base-package属性,获取扫描的范围,然后调用
ClassPathBeanDefinitionScanner#doScan获取
base-package所有
BeanDefinition。
在
doScan方法中最终会调用
ClassPathScanningCandidateComponentProvider#scanCandidateComponents获取扫描范围内所有
BeanDefinition
<img src="https://img.hacpai.com/file/2019/06/carbon44-3fe12346.png" alt=" doScan" />
在
scanCandidateComponents中首先获取扫描包范围内资源对象,然后迭代从可读取资源对象中MetadataReaderFactory#getMetadataReader(resource)
获取MetadataReader` 对象。
<img src="https://img.hacpai.com/file/2019/06/carbon45-ad7207f0.png" alt=" scanCandidateComponents" />
上文已经讲到
MetadataReader对象作用,这里查看如何使用
MetadataReader进行判断。
筛选组件
在
isCandidateComponent方法中将会传入
MetadataReader到
TypeFilter#match进行判断。
<img src="https://img.hacpai.com/file/2019/06/carbon46-3c58666c.png" alt=" isCandidateComponent" />
条件的判断主要使用
excludeFilters与
includeFilters两个字段决定。那两个字段从何处生成?
原来在
ComponentScanBeanDefinitionParser中调用
ClassPathBeanDefinitionScanner构造方法时,默认传入
useDefaultFilters=true。
在
registerDefaultFilters注册默认的过滤器,生成
excludeFilters与
includeFilters初始值。
<img src="https://img.hacpai.com/file/2019/06/carbon47-e68d111c.png" alt=" registerDefaultFilters" />
默认情况下,
excludeFilters将会是个空集,而
includeFilters集合中增加一个包含
@Component类型信息的
AnnotationTypeFilter实例,以及另外两个包含 Java EE 注解
AnnotationTypeFilter实例。
跳到
AnnotationTypeFilter#match方法中。AnnotationTypeFilter 类图如下。
AnnotationTypeFilter#match方法在抽象类
AbstractTypeHierarchyTraversingFilter中实现。
match方法首先调用了
matchSelf,而该方法最终由 AnnotationTypeFilter 重写。
<img src="https://img.hacpai.com/file/2019/06/carbon49-d6c9313a.png" alt=" matchSelf" />
可以看到这里最终使用
AnnotationMetadata方法判断是否存在指定注解。
源码分析就到此为止,下篇文章将会深入
AnnotationMetadata,查看其实如何获取元数据的。
References
[1]异常的自动转换功能: https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#orm-exception-translation
[2]缘起 Dubbo ,讲讲 Spring XML Schema 扩展机制: https://juejin.im/post/5d06018b518825276a286a3d
[3]Spring Annotation Programming Model: https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model
[4]beans-stereotype-annotations: https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-stereotype-annotations
- Spring 注解编程之模式注解
- 第37天(就业班) 静态代理与动态代理、oglib代理、手动实现aop编程、注解实现AOP编程、aop相关的几个注解、xml实现aop、切入点表达式、spring对aop模式
- Spring3.0 学习-AOP面向切面编程_Spring AOP的注解模式即Aspectj模式
- 170721、springboot编程之注解(annotation)列表
- 是时候考虑Spring非阻塞编程模式?R2DBC pk JDBC 和 WebFlux pk Web MVC 评测数据
- SpringBoot 通过自定义注解实现AOP切面编程实例
- spring AOP编程--AspectJ注解方式
- SpringBoot2.0 基础案例(13):基于Cache注解模式,管理Redis缓存
- 小白第七篇 Spring AOP编程 使用@AspectJ注解的方式
- Web框架梳理:第四章:Spring学习入门、Spring属性注入、AOP编程、注解开发
- Spring 面向切面(AOP)编程,注解
- Spring5系列完整学习笔记以及相关代码(含Spring基本配置,IoC,AOP思想,依赖注入,注解开发Spring,静态/动态代理模式,MyBatis整合以及相关代码案例)
- Spring 模式注解装配
- Spring面向切面编程之AOP代理模式
- Spring AOP 面向切面编程相关注解
- Spring 注解编程之 AnnotationMetadata
- 【Spring学习笔记】11 spring aop 编程(注解方式)
- 深度分析Spring注解编程的注解发展历史
- 通过Spring Aop编程思想,利用注解为某些对象添加新的功能
- 【Spring学习笔记】11 spring aop 编程(注解方式)