首页 > 编程语言 >Spring注解驱动原理及源码,深入理解Spring注解驱动

Spring注解驱动原理及源码,深入理解Spring注解驱动

时间:2023-04-03 20:42:49浏览次数:44  
标签:Spring org springframework annotation 注解 驱动 class


文章目录

  • 一、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 核心编程思想》


标签:Spring,org,springframework,annotation,注解,驱动,class
From: https://blog.51cto.com/u_13540373/6167348

相关文章

  • Spring 类型转换详解,SpringBean创建时属性类型转换源码详解
    文章目录一、概述1、Spring类型转换的实现2、使用场景3、源码分析二、基于JavaBeans接口的类型转换1、代码实例2、Spring內建PropertyEditor扩展ByteArrayPropertyEditor3、自定义PropertyEditor扩展整合到springframework代码实例SpringPropertyEditor的设计缺陷三、Spr......
  • SpringBoot启动异常的错误①
    java:无法访问org.springframework.boot.SpringApplication错误的类文件:/D:/maven/repository/org/springframework/boot/spring-boot/3.0.5/spring-boot-3.0.5.jar!/org/springframework/boot/SpringApplication.class类文件具有错误的版本61.0,应为52.0 2023-04......
  • S5PV210开发 -- 串口驱动开发
    上篇文章讲的UART,更多的是硬件相关的知识。接下来进入正题了,串口驱动开发。一、阅读原理图我们用的是UART2串口,则接收管脚XuRXD2复用GPA1_0,发送管脚XuTXD2复用GPA1_1二、S5PV210UART (1)通用异步接收器和发送器的概述 (p-853)S5PV210中的通用异步接收器和发送器(UART)提供四......
  • IDEA Spring-boot 使用@Component注解的工具类,用@Autowired注入 @Service或者@Reposit
    IDEASpring-boot使用@Component注解的工具类,用@Autowired注入@Service或者@Repository会空指针(使用@PostContruct)原文链接:https://blog.csdn.net/ld_secret/article/details/104627597/使用idea编译器时,对于spring-boot的项目,大都使用注解,那么:一、现象:@Component标注的U......
  • 设计模式(三十一)----综合应用-自定义Spring框架-自定义Spring IOC-定义解析器、IOC容
    3定义解析器相关类3.1BeanDefinitionReader接口BeanDefinitionReader是用来解析配置文件并在注册表中注册bean的信息。定义了两个规范:获取注册表的功能,让外界可以通过该对象获取注册表对象。加载配置文件,并注册bean数据。/***@versionv1.0*@ClassName:BeanDe......
  • springboot 日志
    <loggername="com.sinoservices.chainwork.bms"level="INFO"/><loggername="org.hibernate.orm.deprecation"level="error"/><loggername="druid"additivity="true"><levelval......
  • Hystrix(一):为什么@EnableCircuitBreaker和@HystrixCommand能驱动Hystrix
    一、@EnableCircuitBreakerEnableCircuitBreaker源码如下:从源码看出实例化了@EnableCircuitBreaker注解实例化了EnableCircuitBreakerImportSelector这个类。再来看EnableCircuitBreakerImportSelector源码:EnableCircuitBreakerImportSelector继承了SpringFactoryImportSelector,Spr......
  • 使用Spring-data进行Redis操作
     Redis相信大家都听说过,它是一个开源的key-value缓存数据库,有很多Java的客户端支持,比较有名的有Jedis,JRedis等(见这里)。当然我们可以使用客户端的原生代码实现redis的操作,但实际上在spring中就已经集成了这些客户端的使用,下面我们就以Jedis为例来介绍一下Spring中关于Redis的配置。......
  • 解决java注解处理器生成的方法,在编译时报错“找不到符号”
    我的注解处理器,添加的其中一个方法中有一段AST代码如下:JCTree.JCFieldAccessobjectsIsNull=maker.Select(maker.Ident(names.fromString("java.util.Objects")),names.fromString("isNull"));JCTree.JCIfifExpr1=maker.If(maker.Apply(List.nil(),objectsI......
  • 26-springboot-thymeleaf字符串拼接-常量-符号
    Thymeleaf字符串拼接一种是字符串拼接:<spanth:text="'当前是第'+${sex}+'页,共'+${sex}+'页'"></span>另一种更简洁的方式,使用“|”减少了字符串的拼接:<spanth:text="|当前是第${sex}页,共${sex}页|"></span>Thymeleaf可直接使用的常量和符号1、所有......