夯实Spring系列|第二十三章:Spring 注解(Annotations)
文章目录
前言
通过本章不仅可以了解到 Spring 中所有注解编程模型,注解中各种的使用方式;还可以了解 @Enable 模式,条件注解等等和 Spring Boot ,Spring Cloud 紧密相关的技术。
1.项目环境
- jdk 1.8
- spring 5.2.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/thinking-in-spring
- 本章模块:annotation
2.Spring 注解驱动编程发展历程
Spring Framework 1.x
- @Transactional
- @ManageResouce
Spring Framework 2.x
- @Component
- @Repository
- @Controller
- @Service
Spring Framework 3.x
- @Configuration
- @ComponentScan
- @Lazy
- @Bean
Spring Framework 4.x
- @Conditional
Spring Framework 5.x
- @Indexed
- 性能优化,通过 APT(Annotation Processor Tools)在编译时生成元信息,帮助减少类的扫描
3.Spring 核心注解场景分类
模式注解
Spring 注解 | 场景说明 | 起始版本 |
---|---|---|
@Repository | 数据仓库模式注解 | 2.0 |
@Component | 通用组件模式注解 | 2.5 |
@Service | 服务模式注解 | 2.5 |
@Controller | Web 控制器模式注解 | 2.5 |
@Configuration | 配置类模式注解 | 3.0 |
装配注解
Spring 注解 | 场景说明 | 起始版本 |
---|---|---|
@ImportResource | 替换 XML 元素<import> | 2.5 |
@Import | 导入 Configuration 类 | 2.5 |
@ComponentScan | 扫描指定 package 下标注 Spring 模式注解的类 | 3.1 |
依赖注入注解
Spring 注解 | 场景说明 | 起始版本 |
---|---|---|
@Autowired | Bean 依赖注入,支持多种依赖查找方式 | 2.5 |
@Qualifier | 细粒度的 @Autowired 依赖查找 | 2.5 |
4.Spring 注解编程模型
官网地址:https://github.com/spring-projects/spring-framework/wiki/Spring-Annotation-Programming-Model
Spring 注解编程模型分为以下四类:
- 元注解(Meta-Annotations)
- Spring 模式注解(Stereotype Annotations)
- Spring 组合注解(Composed Annotations)
- Spring 注解属性别名和覆盖(Attribute Aliases and Overrides)
4.1 Spring 元注解(Meta-Annotations)
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.
标注在注解上面,用来描述另外一个注解的注解称为元注解。
举例说明:
- java.lang.annotation.Documented
- java.lang.annotation.Inherited
- java.lang.annotation.Repeatable
4.2 Spring 模式注解(Stereotype Annotations)
A stereotype annotation is an annotation that is used to declare the role that a component plays within the application.For example, the
@Repository
annotation 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).
@Component
is a generic stereotype for any Spring-managed component. Any component annotated with@Component
is a candidate for component scanning. Similarly, any component annotated with an annotation that is itself meta-annotated with@Component
is also a candidate for component scanning. For example,@Service
is meta-annotated with@Component
.Core Spring provides several stereotype annotations out of the box, including but not limited to:
@Component
,@Service
,@Repository
,@Controller
,@RestController
, and@Configuration
.@Repository
,@Service
, etc. are specializations of@Component
.
理解 @Component “派生性”
- 元标注了 @Component 的注解在 @ComponentScan 扫描中 “派生” 了 @Component 的特性
- Spring Framework 4.0 开始支持多层次"派生性"
举例说明:
- @Repository
- @Service
- @Controller
- @Configuration
- @SpringBootConfiguration
以上这些注解都元标注了 @Component 注解。
@Component “派生性” 原理
- 核心组件 - org.springframework.context.annotation.ClassPathBeanDefinitionScanner
- ClassPathScanningCandidateComponentProvider 上面类的父类
- 看命名可以理解为在 ClassPath 路径下通过扫描的方式来得到 Component 并转化为 BeanDefintion
- 资源处理 - org.springframework.core.io.support.ResourcePatternResolver
- 通配符资源类型搜索实现
- 资源-类元信息
- org.springframework.core.type.classreading.MetadataReaderFactory
- 类元信息 - org.springframework.core.type.ClassMetadata
- ASM 实现 - org.springframework.core.type.classreading.ClassMetadataReadingVisitor
- 反射实现 - org.springframework.core.type.StandardClassMetadata
- 注解元信息 - org.springframework.core.type.AnnotationMetadata
- ASM 实现 - org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor
- 反射实现 - org.springframework.core.type.StandardAnnotationMetadata
4.2.1 @ComponentScan 如何进行扫描?
调用链路
1.org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass
2.org.springframework.context.annotation.ComponentScanAnnotationParser#parse
3.org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
4.org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// 拿到 package 路径
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 拿到对应的资源
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {// 遍历循环
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setResource(resource);
sbd.setSource(resource);
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
...
核心关键 isCandidateComponent(metadataReader)
判断是否是一个 Component
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
for (TypeFilter tf : this.excludeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
for (TypeFilter tf : this.includeFilters) {
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return isConditionMatch(metadataReader);
}
}
return false;
}
includeFilters
如何获得,核心就下面这段代码
- org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#registerDefaultFilters
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
...
}
因为 includeFilters 增加了 Component.class 的注解类型,在 doScan 扫描的时候会被扫描到注册到 Spring IoC 容器中。
4.2.2 派生性示例
自定义 @MyComponent 注解
/**
* 自定义 {@link Component} 注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 元注解 实现 @Component “派生性”
public @interface MyComponent {
}
自定义 @MyComponent2注解,使用 @MyComponent 进行元标注
/**
* 自定义 {@link Component} 派生注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@MyComponent // 元注解 实现 @Component “派生性”
public @interface MyComponent2 {
}
测试类标注 @MyComponent
@MyComponent
public class TestClass {
}
测试类标注 @MyComponent2
@MyComponent2
public class TestClass2 {
}
测试是否两个类都会被 Spring 扫描到
@ComponentScan(basePackages = "com.huajie.thinking.in.spring.annotation")
public class ComponentScanDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册 Configuration Class
context.register(ComponentScanDemo.class);
// 启动 Spring 应用上下文
context.refresh();
System.out.println(context.getBean(TestClass.class));
System.out.println(context.getBean(TestClass2.class));
// 关闭 Spring 应用上下文
context.close();
}
}
执行结果
com.huajie.thinking.in.spring.annotation.TestClass@79079097
com.huajie.thinking.in.spring.annotation.TestClass2@4d1c00d0
可以看到两个 Bean 都已经存在 Spring IoC 容器中。
4.3 Spring 组合注解(Composed Annotations)
A composed annotation is an annotation that is meta-annotated with one or more annotations with the intent of combining the behavior associated with those meta-annotations into a single custom annotation.
Spring 组合注解是通过元标注的方式组合一个或者多个注解来合并成一个行为。
Spring Boot 中的 @SpringBootApplication 就是一个很好的例子,通过下面的源码可以看到,它通过组合的方式合并了 @SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan 三个注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
详细的分析可以看博主之前的文章:理解 @SpringBootApplication
4.4 Spring 注解属性别名(Attribute Aliases)
An attribute alias is an alias from one annotation attribute to another annotation attribute. Attributes within a set of aliases can be used interchangeably and are treated as equivalent. Attribute aliases can be categorized as follows.
- Explicit Aliases: if two attributes in one annotation are declared as aliases for each other via
@AliasFor
, they are explicit aliases.- Implicit Aliases: if two or more attributes in one annotation are declared as explicit overrides for the same attribute in a meta-annotation via
@AliasFor
, they are implicit aliases.- Transitive Implicit Aliases: given two or more attributes in one annotation that are declared as explicit overrides for attributes in meta-annotations via
@AliasFor
, if the attributes effectively override the same attribute in a meta-annotation following the law of transitivity, they are transitive implicit aliases.
显性别名(Explicit Aliases)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
...
可以看到 value 和 basePackages 两个属性在同一个注解内,通过 @AliasFor 方式互相描述,这种就是显性别名。换言之 @ComponentScan(basePackages = "com.xxx")
和 @ComponentScan(value= "com.xxx")
是等效的。
隐性别名(Implicit Aliases)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@ComponentScan
public @interface MyComponentScan {
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages") // 隐性别名
String[] scanBasePackages() default {};
}
@ComponentScan 是 @MyComponentScan 通过组合注解的方式引入的,这段代码的意思是通过 scanBasePackages 属性来别名化 @ComponentScan 的 basePackages 属性,通过这种方式来获取相关的属性功能。
同样的用这个 @MyComponentScan 代替上面 ComponentScanDemo 中的 @ComponentScan 也能达到同样的效果。
传递隐性别名(Transitive Implicit Aliases)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@ComponentScan
public @interface MyComponentScan {
@AliasFor(annotation = ComponentScan.class, attribute = "value") // 传递隐性别名
String[] scanBasePackages() default {};
}
MyComponentScan#scanBasePackages -> ComponentScan#value -> ComponentScan#basePackages
4.5 Spring 注解属性覆盖(Attribute Overrides)
An attribute override is an annotation attribute that overrides (or shadows) an annotation attribute in a meta-annotation. Attribute overrides can be categorized as follows.
- Implicit Overrides: given attribute
A
in annotation@One
and attributeA
in annotation@Two
, if@One
is meta-annotated with@Two
, then attributeA
in annotation@One
is an implicit override for attributeA
in annotation@Two
based solely on a naming convention (i.e., both attributes are namedA
).- Explicit Overrides: if attribute
A
is declared as an alias for attributeB
in a meta-annotation via@AliasFor
, thenA
is an explicit override forB
.- Transitive Explicit Overrides: if attribute
A
in annotation@One
is an explicit override for attributeB
in annotation@Two
andB
is an explicit override for attributeC
in annotation@Three
, thenA
is a transitive explicit override forC
following the law of transitivity.
隐性覆盖
当注解属性出现元注解相同的属性名称时,会覆盖元注解的属性内容。
如下代码中,MyComponentScan2 中的 scanBasePackages 会覆盖 MyComponentScan 中的同名属性
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@MyComponentScan
public @interface MyComponentScan2 {
@AliasFor(annotation = MyComponentScan.class, attribute = "scanBasePackages") // 隐性别名
String[] basePackages() default {};
/**
* 与 MyComponentScan 中的属性同名
*
* @return
*/
String[] scanBasePackages() default {};
}
测试结果也正常打印
@MyComponentScan2(scanBasePackages = "com.huajie.thinking.in.spring.annotation")
public class AttributeOverridesDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册 Configuration Class
context.register(AttributeOverridesDemo.class);
// 启动 Spring 应用上下文
context.refresh();
System.out.println(context.getBean(TestClass.class));
System.out.println(context.getBean(TestClass2.class));
// 关闭 Spring 应用上下文
context.close();
}
}
显性覆盖
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@MyComponentScan
public @interface MyComponentScan2 {
@AliasFor(annotation = MyComponentScan.class, attribute = "scanBasePackages") // 隐性别名
String[] basePackages() default {};
/**
* 与 MyComponentScan 中的属性同名
*
* @return
*/
String[] scanBasePackages() default {};
@AliasFor("scanBasePackages") // 显性覆盖
String[] packages() default {};
}
packages 覆盖了 scanBasePackages,同时又覆盖了 MyComponentScan 中的 scanBasePackages 属性。
5.Spring @Enable 模块驱动
@Enable 模块驱动是以 @Enable 为前缀的注解驱动编程模型。所谓模块是指具备相同领域的功能组件集合,组合所形成一个独立的单元。比如 Web Mvc 模块、Caching 模块、Async 模块等。
对应的注解:@EnableWebMvc、@EnableCaching、@EnableAsync
这一块可以参考文章:@Enable 模块驱动
6.Spring 条件注解
这一块可以参考文章:Spring Boot 条件装配
6.1 @Profile 示例
Spring 4.0 之后 @Profile 就是基于 @Conditional 来实现的。下面的示例演示的就是通过设置环境变量中 Profile 的值来控制 @Bean 是否生效。
@Configuration
public class ProfileDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
ConfigurableEnvironment environment = context.getEnvironment();
// 设置 Profile
environment.setActiveProfiles("even");
// 注册 Configuration Class
context.register(ProfileDemo.class);
// 启动 Spring 应用上下文
context.refresh();
System.out.println(context.getBean(Integer.class));
// 关闭 Spring 应用上下文
context.close();
}
@Bean
@Profile("odd")
public Integer odd() {// 奇数
return 1;
}
@Bean
@Profile("even")
public Integer even() {// 偶数
return 2;
}
}
6.2 @Conditional 改写
将上面的 even 方法修改为
@Bean
@Conditional(EvenProfileCondition.class)
public Integer even() {// 偶数
return 2;
}
EvenProfileCondition 类实现 Condition 接口
public class EvenProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 取到环境变量
Environment environment = context.getEnvironment();
// 对比 Profiles 是否存在 even 即可
String[] activeProfiles = environment.getActiveProfiles();
for (String profile: activeProfiles) {
if(Objects.equals(profile,"even")){
return true;// 匹配成功
}
}
return false;
}
}
6.3 @Conditional 实现原理
- 上下文对象 - org.springframework.context.annotation.ConditionContext
- 条件判断 - org.springframework.context.annotation.ConditionEvaluator
- 配置阶段 - org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase
- 判断入口 - org.springframework.context.annotation.ConfigurationClassPostProcessor
- org.springframework.context.annotation.ConfigurationClassParser
在 ConfigurationClassPostProcessor 其中的 ConfigurationClassParser 加入了一个条件评估的逻辑;当条件评估通过,BeanDefinition 就会继续往下走
源码调试
断点可以打在
-
ConfigurationClassPostProcessor 242 行
-
ConfigurationClassParser 221
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
条件评估的核心代码就在 shouldSkip 方法中,这里就不展开了。
7.面试题
7.1 Spring 模式注解有哪些?
@Component,以及其相关的派生注解,如:@Service、@Controller、@Repository、@Configuration
7.2 @EventListener 工作原理
-
EventListenerFactory 将标注有 @EventListener 的方法转化成一个 ApplicationListener
-
由于 EventListenerMethodProcessor 实现了 SmartInitializingSingleton 接口,那么在 Spring Bean 初始化的完成阶段会调用 EventListenerMethodProcessor 的 afterSingletonsInstantiated,而在这个方法中调用了 processBean 方法。
-
org.springframework.context.event.EventListenerMethodProcessor#processBean
追踪这个方法中的源码可以知道,最终还是通过 context.addApplicationListener(applicationListener);
将 Listener 添加到 Spring 的应用上下文中。
8.参考
- 极客时间-小马哥《小马哥讲Spring核心编程思想》