文章目录
- 一、Java注解入门大全
- 二、Spring 注解驱动编程发展历程
- 1、注解驱动启蒙时代:Spring Framework 1.x
- @Transactional
- @ManagedResource
- 2、注解驱动过渡时代:Spring Framework 2.x
- @Repository
- @Component
- 3、注解驱动黄金时代:Spring Framework 3.x
- 4、注解驱动完善时代:Spring Framework 4.x
- @Profile
- @Conditional
- 5、注解驱动当下时代:Spring Framework 5.x
- @Indexed
- 三、Spring 核心注解场景分类
- 1、Spring 模式注解
- 2、装配注解
- 3、依赖注入注解
- 四、Spring 注解编程模型
- 1、元注解(Meta-Annotations)
- @Repeatable
- 2、Spring 模式注解(Stereotype Annotations)
- (1)官方 Wiki 原文
- (2)@Component “派⽣性”
- (3)@Component “派⽣性”原理
- 3、Spring 组合注解(Composed Annotations)
- (1)基本定义
- (2)源码分析
- 4、Spring 注解属性别名(Attribute Aliases)
- (1)定义
- (2)显性别名
- (3)隐性别名
- (4)原理
- 5、Spring 注解属性覆盖(Attribute Overrides)
- (1)定义
- (2)隐性覆盖
- (3)显性覆盖
- 五、Spring @Enable 模块驱动
- 1、基本特性
- 2、自定义@Enable 模块
- 六、Spring 条件注解
- 1、@Profile
- 代码实例
- 2、@Conditional
- 代码实例
- 3、@Conditional 实现原理
- 七、SpringBoot、SpringCloud对Spring注解的扩展
- 1、Spring Boot 注解
- 2、Spring Cloud 注解
- 参考资料
一、Java注解入门大全
Jdk5开始引入注解,不得不说,注解的确可以大大的简化我们的日常开发,更是简化了框架的封装及配置。
关于java注解基础,请移步:
spring框架注解多?注解到底是个什么东西?这篇文章给你讲明白
二、Spring 注解驱动编程发展历程
1、注解驱动启蒙时代:Spring Framework 1.x
更确切点讲,是在Spring Framework1.2版本才正式引入的。
Spring Framework 1.x版本大致是在2003-2004年之间,JDK1.5引入注解是在2004年,所以这个时候Spring正是在JDK1.4-JDK1.5这个时代过渡。
@Transactional
@Transactional事实上在1.2版本就予以支持了,对本地事务进行了很好的管理,但是不能支持分布式事务和跨线程的事务。
@ManagedResource
@ManagedResource也是从Spring1.2开始支持了,主要是对Java的JMX(Java Management Extension)做了一个补充说明。
日常开发中很少看到这个注解,但是是一个非常关键的注解。
2、注解驱动过渡时代:Spring Framework 2.x
Spring Framework 2.x版本已经开始兼容JDK1.5了,但是不是强制使用JDK1.5,最低支持JDK1.2
@Repository
Spring2.0引入了@Repository,它相当于一个DDD的概念,一般用于DAO层,比@Component更早引入,但是自从@Component引入之后,也就是Spring2.5之后,对@Repository做了一些重构,使用@Component标注了。
@Component
Spring2.5引入了@Component,@Component有许多派生注解(@Service、@Controller、@Configuration、@Repository等等)。
2.0时代并没有提供@ComponentScan,@ComponentScan从Spring3.1才引入的,所以此时只能基于XML的方式进行配置。
3、注解驱动黄金时代:Spring Framework 3.x
Spring Framework 3.x开始,就慢慢的引入大量的注解,所以Spring3版本是一个黄金时代,会引入大量的注解。
@ComponentScan、@Bean、@Lazy、@Configuration、@ImportResource等等都是Spring3开始引入的。
4、注解驱动完善时代:Spring Framework 4.x
Spring Framework 4.x基本就完成了注解驱动模型,包括引入了条件注解等等。
@Profile
@Profile其实在3.1已经引入了,但是在Spring4做了一个重构,就是引入了@Conditional。
@Conditional
@Conditional是Spring4中有代表性的注解,就是我的Bean可以有条件的进行去加载。
5、注解驱动当下时代:Spring Framework 5.x
Spring Framework 5.x版本Spring并没有提供一些功能性的注解,引入了一些性能优化,大部分都是引入一些编译期间,协助程序员编程用的。
@Indexed
Spring5.0引入了@Indexed,主要是做一些性能优化,通过APT(Annotation Processor Tools)来进行编译时期生成元信息,帮助我们减少类的扫描等等,对于Spring的启动是有帮助的。
三、Spring 核心注解场景分类
1、Spring 模式注解
Spring 注解 | 场景说明 | 起始版本 |
@Repository | 数据仓储模式注解 | 2.0 |
@Component | 通用组件模式注解 | 2.5 |
@Service | 服务模式注解 | 2.5 |
@Controller | Web 控制器模式注解 | 2.5 |
@Configuration | 配置类模式注解 | 3.0 |
这五个注解其实本质上都是@Component,都是由@Component派生来的。
其中@Repository代表数据仓储,@Service表示服务层,只有语义上的区别,用法和@Component都是完全一样的。
@Controller 表示控制层,@Configuration强调这个类是一个配置类。
2、装配注解
Spring 注解 | 场景说明 | 起始版本 |
@ImportResource | 替换 XML 元素 <import> | 3.0 |
@Import | 导入 Configuration 类 | 3.0 |
@ComponentScan | 扫描指定 package 下标注 Spring 模式注解的类 | 3.1 |
3、依赖注入注解
Spring 注解 | 场景说明 | 起始版本 |
@Autowired | Bean 依赖注入,支持多种依赖查找方式 | 2.5 |
@Qualifier | 细粒度的 @Autowired 依赖查找 | 2.5 |
四、Spring 注解编程模型
1、元注解(Meta-Annotations)
元注解,就是可以标注在注解上面的注解。
官方 Wiki 原文:
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 @Documented from the java.lang.annotation package.
举例说明:
• java.lang.annotation.Documented
• java.lang.annotation.Inherited
• java.lang.annotation.Repeatable
本文第一节的java注解基础中也介绍过什么是元注解:
spring框架注解多?注解到底是个什么东西?这篇文章给你讲明白
@Repeatable
JDK8引入了@Repeatable注解,相当于是一个语法糖,在编译的时候会做一些相应的转换。
@Repeatable标注的注解,可以在类或方法或其他地方重复使用,这里就不做多的赘述了。
2、Spring 模式注解(Stereotype Annotations)
(1)官方 Wiki 原文
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.
(2)@Component “派⽣性”
元标注 @Component 的注解在 XML 元素 <context:component-scan> 或注解 @ComponentScan 扫描中“派生”了 @Component 的特性,并且从 Spring Framework 4.0 开始支持多层次“派⽣性”。
由于注解并不能像接口或者类那样有继承关系,它们只能通过元标注的方式来说明它们之间层次性的关系。
举例说明:
• @Repository
• @Service
• @Controller
• @Configuration
• @SpringBootConfiguration(Spring Boot)
@Component 注解是一个重要注解,表示它是一个组件,并且是一个范式注解。
被@Component 注解标注的类都会被Spring识别为一个Bean。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
String value() default "";
}
@Service 注解被@Component标注,就相当于@Service标注的类也会被扫描为一个Spring 的Bean
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
@AliasFor(annotation = Component.class)
String value() default "";
}
虽然用法是一样的,但是语义是不一样的,对于Spring来说,标注@Component和标注@Service的类并没有什么区别,但是对于开发者而言,标注@Service的类我们要认为是一个服务类,@Repository也类似。
这符合DDD领域驱动设计。
@SpringBootConfiguration注解其实是多层次的派生,它元标注了@Configuration,@Configuration又元标注了@Component,实际上有三层标注:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
(3)@Component “派⽣性”原理
核心组件 - org.springframework.context.annotation.ClassPathBeanDefinitionScanner
• org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider
资源处理 - 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.StandardAnnotationMetadata
注解元信息 - org.springframework.core.type.AnnotationMetadata
• ASM 实现 - org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor
• 反射实现 - org.springframework.core.type.StandardAnnotationMetadata
接下来我们对@Component的派生性原理做一下源码分析。
我们先从@ComponentScan作为突破口来分析源码。
@ComponentScan是和XML中的<context:component-scan>这个标签是异曲同工的。
使用示例:
@ComponentScan(value = "com.demo") // 指定 Class-Path(s)
public class MyComponentScanDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册 Configuration Class
context.register(MyComponentScanDemo.class);
// 启动 Spring 应用上下文
context.refresh();
TestClass testClass = context.getBean(TestClass.class);
System.out.println(testClass);
// 关闭 Spring 应用上下文
context.close();
}
}
@ComponentScan的value其实被赋予了一个别名,是一个数组:
@AliasFor("basePackages")
String[] value() default {};
我们先全局搜一下@ComponentScan在框架中是哪里处理到的:
我截取了org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass 的部分源码:
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName()); // 使用componentScanParser处理
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
进入ComponentScanAnnotationParser的parse方法:
// org.springframework.context.annotation.ComponentScanAnnotationParser#parse
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
scanner.setScopedProxyMode(scopedProxyMode);
}
else {
Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
}
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addIncludeFilter(typeFilter);
}
}
for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
for (TypeFilter typeFilter : typeFiltersFor(filter)) {
scanner.addExcludeFilter(typeFilter);
}
}
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
//获取使用@ComponentScan中设置的value值(也就是basePackages)
Set<String> basePackages = new LinkedHashSet<>(); // 保序、去重
String[] basePackagesArray = componentScan.getStringArray("basePackages");
for (String pkg : basePackagesArray) {
String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
Collections.addAll(basePackages, tokenized);
}
for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
if (basePackages.isEmpty()) {
basePackages.add(ClassUtils.getPackageName(declaringClass));
}
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
// 扫描所有定义的包
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
// org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan
// 返回值就是BeanDefinitionHolder的集合,里面存放着BeanDefinition信息
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 在每个包里面进行迭代,生成BeanDefinition。
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 将BeanDefinition注册、处理
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
candidate.setScope(scopeMetadata.getScopeName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
我们再看一下如何在这些包下面查找所有的Component的:
ClassPathScanningCandidateComponentProvider是ClassPathBeanDefinitionScanner的父类。
// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
// 如果使用了@Indexed就直接从配置文件索引中读,Spring5新加的,感兴趣后续再深入研究。
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
// 传统扫描方式
return scanCandidateComponents(basePackage);
}
}
前面我们了解到,@Component注解其实是添加了@Indexed注解,我们说可以提升性能,这里就用到了。
@Indexed是jdk8新加的、Spring5也新引入的,这里我们先不做深入研究,我们分析一下传统的包扫描方式。
// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
// 包搜索路径,只扫描.class文件
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()) { // jar包中默认都是可读取的
try {
// 元信息读取器(资源变成类的元信息)
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
if (isCandidateComponent(metadataReader)) { // 判断是否是一个正常的Component,根据过滤器来筛选
// 构造一个ScannedGenericBeanDefinition,携带注解元信息
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);
}
else {
if (debugEnabled) {
logger.debug("Ignored because not a concrete top-level class: " + resource);
}
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not matching any filter: " + resource);
}
}
}
catch (Throwable ex) {
throw new BeanDefinitionStoreException(
"Failed to read candidate component class: " + resource, ex);
}
}
else {
if (traceEnabled) {
logger.trace("Ignored because not readable: " + resource);
}
}
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
}
return candidates;
}
这里包扫描读取class文件,是根据ASM读取的,不需要加载类的信息(不经历加载链接初始化等过程),提高速度。
Spring5更是使用@Index,在编译期间就确定好了,速度进一步提升。
在判断是否是一个合法的Component时,使用了两个TypeFilter:
// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent(org.springframework.core.type.classreading.MetadataReader)
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;
}
private final List<TypeFilter> includeFilters = new LinkedList<>();
private final List<TypeFilter> excludeFilters = new LinkedList<>();
而includeFilters在初始化时,默认就是添加进了Component.class:
// org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#registerDefaultFilters
protected void registerDefaultFilters() {
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
}
try {
this.includeFilters.add(new AnnotationTypeFilter(
((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
}
catch (ClassNotFoundException ex) {
// JSR-330 API not available - simply skip.
}
}
所以,只要是标注了@Component的类,都会被读取为一个Bean。
所以,多层次的@Component,也都是可以注册为一个Bean的:
/**
* 自定义 Component "派生性"注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // 元注解,实现 @Component "派生性"
public @interface MyComponent {
}
/**
* {@link MyComponent} "派生"注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@MyComponent
public @interface MyComponent2 {
}
@MyComponent2
public class TestClass {
}
上面的示例代码TestClass,也是会被注册到IOC容器中。
从Spring 4.0开始支持多层次的@Component “派生”。
总结一下:
1、AnnotationConfigApplicationContext.register会注册启动类为AnnotatedGenericBeanDefinition,abd包括ClassMetadata和AnnotatedTypeMetadata
2、refresh->invokeBeanFactoryPostProcessors->PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
里面拿到ConfigurationAnnotationProcessor处理器处理(通过AnnotationConfigUtils注册)
3、通过过滤排除各internalXXX,确定启动类为配置类
4、创建ConfigurationClassParser,并调用parse分析
5、创建ConfigurationClass,参数AnnotationMetadata和beanName
6、通过ConfigurationClass,创建SourceClass,这里有一个疑惑,创建的时候传入Class,并非AnnotationMetadata,创建的时候会再次通过自省AnnotationMetadata.introspect再次分析注解元信息。实际在register生成bd时已经调用多一次。
7、调用doProcessConfigurationClass处理诸如@ImportResource,@PropertySources,@ComponentScans等
8、在处理ComponentScans时,创建ClassPathBeanDefinitionScanner。在scanner.doScan中
findCandidateComponents(String basePackage)->scanCandidateComponents->拼接路径classpath*:+basePackage+**/*.class
9、GenericApplicationContext->AbstractApplicationContext->findPathMatchingResources->getResources->findAllClassPathResources…->最终通过ClassLoader.getResources(path)获取路径下的所有Url资源并生成UrlResource
10、创建SimpleMetadataReader,通过ClassReader读取class文件,创建AnnotationMetadataReadingVisitor(ASM class visotor)。这一块逻辑不懂,classReader已经读入文件,还需要ASM做什么?
11、最终生成ScannedGenericBeanDefinition
12、通过postProcessBeanDefinition及AnnotationConfigUtils.processCommonDefinitionAnnotations,补充bd其他属性,比如是否lazy,初始化接口名称等等
13、注册bd结束
3、Spring 组合注解(Composed Annotations)
(1)基本定义
官方 Wiki 原文:
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. For example, an annotation named @TransactionalService that is meta-annotated with Spring’s @Transactional and @Service annotations is a composed annotation that combines the semantics of @Transactional and @Service. @TransactionalService is technically also a custom stereotype annotation.
基本定义:
Spring 组合注解(Composed Annotations)中的元注允许是 Spring 模式注解(Stereotype Annotation)与其他 Spring 功能性注解的任意组合。
(2)源码分析
Spring中组合注解的运用其实并不是很多,但是SpringBoot中大量的运用了组合注解。
@SpringBootApplication注解就组合了许多注解,将多个注解进行合并:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration // 元标注了@Configuration
@EnableAutoConfiguration // Enable模块驱动
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) // 包扫描
public @interface SpringBootApplication {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
这其中涉及一个很重要的API:AnnotationAttributes,它继承了LinkedHashMap,key是注解的属性名,value是值。
里面存储着注解相关的很多关键信息,后续再对其进行深入的分析。
4、Spring 注解属性别名(Attribute Aliases)
(1)定义
官方 Wiki 原文:
An attribute override is an annotation attribute that overrides (or shadows) an annotationattribute in a meta-annotation. Attribute overrides can be categorized as follows.
• Implicit Overrides: given attribute A in annotation @One and attribute A in annotation @Two, if @One is meta-annotated with @Two, then attribute A in annotation @One is an implicit override for attribute A in annotation @Two based solely on a naming convention (i.e., both attributes are named A).
• Explicit Overrides: if attribute A is declared as an alias for attribute B in a meta-annotation via @AliasFor, then A is an explicit override for B.
• Transitive Explicit Overrides: if attribute A in annotation @One is an explicit override for attribute B in annotation @Two and B is an explicit override for attribute C in annotation @Three, then A is a transitive explicit override for C following the law of transitivity.
(2)显性别名
我们看@ComponentScan 注解,value属性用@AliasFor(“basePackages”)标注了,我们认为value与basePackages是等价的。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
当使用@ComponentScan 注解时,以下操作是等价的:
@ComponentScan(basePackages = "com.demo") // 指定 Class-Path(s)
//@ComponentScan(value = "com.demo") // 指定 Class-Path(s)
public class ComponentScanDemo {
(3)隐性别名
我们看@SpringBootApplication注解,使用@AliasFor(annotation = EnableAutoConfiguration.class),我们认为该属性等价于EnableAutoConfiguration中的相同属性。
@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 {
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
如果@SpringBootApplication 想要继承@EnableAutoConfiguration的exclude属性的话,我们可以使用以上方式来实现。
例如我自定义@MyComponentScan,可以与@ComponentScan关联,用法与@ComponentScan一样:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ComponentScan
public @interface MyComponentScan {
// 这里attribute 写value和basePackages效果是一样的
@AliasFor(annotation = ComponentScan.class, attribute = "value") // 隐性别名
// 相当于"多态",子注解提供新的属性方法引用"父"(元)注解中的属性方法
String[] scanBasePackages() default {};
}
@MyComponentScan(scanBasePackages= "com.demo") // 指定 Class-Path(s)
public class ComponentScanDemo {
别名可以传递的,再自定义一个@MyComponentScan2 ,同理:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@MyComponentScan
public @interface MyComponentScan2 {
@AliasFor(annotation = MyComponentScan.class, attribute = "scanBasePackages") // 隐性别名
String[] basePackages() default {};
(4)原理
参考接口 org.springframework.core.annotation.MergedAnnotations
5、Spring 注解属性覆盖(Attribute Overrides)
(1)定义
官方 Wiki 原文:
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 attribute A in annotation @Two, if @One is meta-annotated with @Two, then attribute A in annotation @One is an implicit override for attribute A in annotation @Two based solely on a naming convention (i.e., both attributes are named A).
• Explicit Overrides: if attribute A is declared as an alias for attribute B in a meta-annotation via @AliasFor, then A is an explicit override for B.
• Transitive Explicit Overrides: if attribute A in annotation @One is an explicit override for attribute B in annotation @Two and B is an explicit override for attribute C in annotation @Three, then A is a transitive explicit override for C following the law of transitivity.
(2)隐性覆盖
在隐性覆盖的情况下,注解和元注解出现同名属性的时候,注解会覆盖元注解同名属性中的内容,事实上是相当于一个继承关系,子类可以覆盖父类的实现。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ComponentScan
public @interface MyComponentScan3 {
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] packages() default {};
// @ComponentScan也有同名属性,会覆盖原属性,@MyComponentScan3这个basePackages的效果与@ComponentScan是不同的
String[] basePackages() default {};
}
(3)显性覆盖
@ComponentScan的value和basePackages就是显性覆盖,语义相同,但是互相排斥。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
五、Spring @Enable 模块驱动
1、基本特性
@Enable 模块驱动是以 @Enable 为前缀的注解驱动编程模型。所谓“模块”是指具备相同领域的功能组件集合,组合所形成⼀个独⽴的单元。⽐如 Web MVC 模块、AspectJ代理模块、Caching(缓存)模块、JMX(Java 管理扩展)模块、Async(异步处理)模块等。
举例说明
• @EnableWebMvc
• @EnableTransactionManagement
• @EnableCaching
• @EnableMBeanExport
• @EnableAsync
2、自定义@Enable 模块
(1)驱动注解:@EnableXXX,导入注解:@Import 具体实现
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* 激活 "HelloWorld" 模块注解
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 第二步:通过 @Import 注解导入具体实现
//@Import(HelloWorldConfiguration.class) // 方法一: 通过 Configuration Class 实现
//@Import(HelloWorldImportSelector.class)// 方法二:通过 ImportSelector 接口实现
@Import(HelloWorldImportBeanDefinitionRegistrar.class)// 方法三:通过 ImportBeanDefinitionRegistrar
public @interface EnableHelloWorld { // 第一步:通过 @EnableXXX 命名
}
(2)@Import 具体实现有三种方式:
① 基于 Configuration Class
public class HelloWorldConfiguration {
@Bean
public String helloWorld() {
return "Hello,World";
}
}
② 基于 ImportSelector 接口实现
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* HelloWorld 模块 {@link ImportSelector} 实现
*
*/
public class HelloWorldImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.demo.HelloWorldConfiguration"}; // 导入1个或多个类
}
}
③ 基于 ImportBeanDefinitionRegistrar 接口实现
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* "HelloWorld" 模块 {@link ImportBeanDefinitionRegistrar}
*/
public class HelloWorldImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
AnnotatedGenericBeanDefinition beanDefinition = new AnnotatedGenericBeanDefinition(HelloWorldConfiguration.class);
BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
}
}
(3)使用
@EnableHelloWorld
public class EnableModuleDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册 Configuration Class
context.register(EnableModuleDemo.class);
// 启动 Spring 应用上下文
context.refresh();
String helloWorld = context.getBean("helloWorld", String.class);
System.out.println(helloWorld);
// 关闭 Spring 应用上下文
context.close();
}
}
六、Spring 条件注解
1、@Profile
基于配置条件注解 - @org.springframework.context.annotation.Profile
• 关联对象 - org.springframework.core.env.Environment 中的 Profiles
• 实现变化:从 Spring 4.0 开始,@Profile 基于 @Conditional 实现
@Profile通常用于通过一种配置的方式来隔离某一种环境,比如说测试环境、生产环境等。
代码实例
import org.springframework.context.annotation.*;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
/**
* {@link Profile} 示例
* @see Environment#getActiveProfiles() // 获取激活的环境
*/
@Configuration
public class ProfileDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
// 注册 Configuration Class
context.register(ProfileDemo.class);
// 获取 Environment 对象(可配置的)
ConfigurableEnvironment environment = context.getEnvironment();
// 默认 profiles = [ "odd" ] (兜底 profiles)
environment.setDefaultProfiles("odd");
// 增加活跃 profiles(优先)
// environment.addActiveProfile("even");
// 启动 Spring 应用上下文
context.refresh();
Integer number = context.getBean("number", Integer.class);
System.out.println(number);
// 关闭 Spring 应用上下文
context.close();
}
@Bean(name = "number")
@Profile("odd") // 奇数
public Integer odd() {
return 1;
}
@Bean(name = "number")
@Profile("even") // 偶数
public Integer even() {
return 2;
}
}
这里要注意:
① 如果使用了@Profile,不设置Environment中的profile的话,是不会生效的。
② 如果设置了ActiveProfile,会优先,并且会覆盖默认的profile。
③ 可以使用启动参数–spring.profiles.active = even或者-Dspring.profiles.active=even 来设置活跃的profile。
2、@Conditional
基于编程条件注解 - @org.springframework.context.annotation.Conditional
• 关联对象 - org.springframework.context.annotation.Condition 具体实现
代码实例
上面代码我们稍微改一下:
@Bean(name = "number")
// @Profile("even") // 偶数
@Conditional(EvenProfileCondition.class)
public Integer even() {
return 2;
}
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* 偶数 Profile 条件
*/
public class EvenProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 条件上下文
Environment environment = context.getEnvironment();
return environment.acceptsProfiles("even"); // 返回一个或多个给定profile是否处于活动状态
}
}
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
ConditionEvaluator的关键方法shouldSkip,判断是否应该跳过这个项目(@Configuration、@Bean)
// org.springframework.context.annotation.ConditionEvaluator#shouldSkip(AnnotatedTypeMetadata, ConfigurationPhase)
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 设置是@Configuration还是@Bean的阶段
if (phase == null) {
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
List<Condition> conditions = new ArrayList<>();
// 从metadata中获取条件类型
for (String[] conditionClasses : getConditionClasses(metadata)) {
for (String conditionClass : conditionClasses) {
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
conditions.add(condition);
}
}
// 排序
AnnotationAwareOrderComparator.sort(conditions);
// 判断条件是否符合
for (Condition condition : conditions) {
ConfigurationPhase requiredPhase = null;
if (condition instanceof ConfigurationCondition) {
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
}
if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
return true;
}
}
return false;
}
在加载Bean、Configuration时,会先调用shouldSkip这个方法,判断Conditional条件是否符合,选择是否跳过该组件的加载。
org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass的部分源码:
// Process any @ComponentScan annotations
Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
for (AnnotationAttributes componentScan : componentScans) {
// The config class is annotated with @ComponentScan -> perform the scan immediately
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// Check the set of scanned definitions for any further config classes and parse recursively if needed
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
七、SpringBoot、SpringCloud对Spring注解的扩展
1、Spring Boot 注解
注解 | 场景说明 | 起始版本 |
@SpringBootConfiguration | Spring Boot 配置类 | 1.4.0 |
@SpringBootApplication | Spring Boot 应用引导注解 | 1.2.0 |
@EnableAutoConfiguration | Spring Boot 激活自动转配 | 1.0.0 |
2、Spring Cloud 注解
注解 | 场景说明 | 起始版本 |
@SpringCloudApplication | Spring Cloud 应用引导注解 | 1.0.0 |
@EnableDiscoveryClient | Spring Cloud 激活服务发现客户端注解 | 1.0.0 |
@EnableCircuitBreaker | Spring Cloud 激活熔断注解 | 1.0.0 |
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public @interface SpringCloudApplication {
}
参考资料
极客时间-《小马哥讲 Spring 核心编程思想》