元素有一个
* annotation-config 属性; 但是,此注解没有。这是因为
* 在几乎所有使用 @ComponentScan 的情况下,默认的注解配置
* 处理(例如处理 @Autowired 及其朋友们)都是预期的。此外,
* 使用 AnnotationConfigApplicationContext 时,总是会注册注解配置处理器,
* 这意味着在 @ComponentScan 级别尝试禁用它们都会被忽略。
*
* 有关使用示例,请参见 Configuration @Configuration 的 Javadoc。
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Sam Brannen
* @since 3.1
* @see Configuration
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {
/**
* #basePackages 的别名。
* 如果不需要其他属性,则允许更简洁的注解声明,例如,@ComponentScan("org.my.pkg")
* 而不是 @ComponentScan(basePackages = "org.my.pkg")。
*/
@AliasFor("basePackages")
String[] value() default {};
/**
* 扫描带注解的组件的基础包。
* #value 是此属性的别名(且与此属性互斥)。
* 使用 #basePackageClasses 作为基于类型安全的替代方法
* 来指定要扫描注解的组件的包。将扫描每个指定类的包。
*/
@AliasFor("value")
String[] basePackages() default {};
/**
* 指定要扫描的包的类型安全替代方法。每个指定类的包都会被扫描。
* 考虑在每个包中创建一个特殊的无操作标记类或接口,
* 除了被此属性引用之外,没有其他用途。
*/
Class>[] basePackageClasses() default {};
/**
* 在Spring容器内为检测到的组件命名的 BeanNameGenerator 类。
* BeanNameGenerator 接口的默认值表明处理此 @ComponentScan 注解的扫描器
* 应使用它的继承的bean命名生成器,例如默认的
* AnnotationBeanNameGenerator 或在启动时提供给应用上下文的任何自定义实例。
*/
Class extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
/**
* 用于解析检测到的组件范围的 ScopeMetadataResolver。
*/
Class extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
/**
* 指示是否应为检测到的组件生成代理,这在以代理风格使用范围时可能是必要的。
* 默认值是延迟到执行实际扫描的组件扫描器的默认行为。
* 注意,设置此属性会覆盖为 #scopeResolver 设置的任何值。
*/
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
/**
* 控制适用于组件检测的类文件。
* 考虑使用 #includeFilters 和 #excludeFilters
* 来采用更灵活的方法。
*/
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
/**
* 指示是否应启用使用 @Component @Repository, @Service, 或 @Controller 注解的类的自动检测。
*/
boolean useDefaultFilters() default true;
/**
* 指定哪些类型有资格进行组件扫描。
* 进一步从 #basePackages 中的所有内容缩小到匹配给定过滤器或过滤器的基包中的所有内容。
* 注意,这些过滤器将附加到默认过滤器(如果指定)。即使它与默认过滤器不匹配(例如,没有使用 @Component 注解),
* 任何匹配给定过滤器的基包下的类型都将被包括。
*/
Filter[] includeFilters() default {};
/**
* 指定哪些类型不适合进行组件扫描。
*/
Filter[] excludeFilters() default {};
/**
* 指定是否应注册扫描的beans以进行延迟初始化。
* 默认值是 false;如果需要,切换为 true。
*/
boolean lazyInit() default false;
/**
* 声明用作 ComponentScan#includeFilters include filter 或
* ComponentScan#excludeFilters exclude filter 的类型过滤器。
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
/**
* 要使用的过滤器类型。
* 默认为 FilterType#ANNOTATION。
* @see #classes
* @see #pattern
*/
FilterType type() default FilterType.ANNOTATION;
/**
* #classes 的别名。
*/
@AliasFor("classes")
Class>[] value() default {};
/**
* 用作过滤器的类或类。
* 根据 #type 属性的配置值,以下表格解释了如何解释这些类
* ...
* 这部分包含了一个表格和其它详细说明,由于格式限制,需要额外的处理来适应中文文档
* ...
*/
@AliasFor("value")
Class>[] classes() default {};
/**
* 用作过滤器的模式(或模式),作为指定类 #value 的替代。
* 如果 #type 设置为 FilterType#ASPECTJ ASPECTJ,这是一个 AspectJ 类型模式表达式。
* 如果 #type 设置为 FilterType#REGEX REGEX,这是一个正则模式,用于匹配完全限定的类名。
*/
String[] pattern() default {};
}
}
```
`ScopedProxyMode` 是一个枚举,定义了不同的作用域代理选项,用于决定如何为特定的作用域 bean 创建代理。作用域代理是 Spring 中一个高级特性,允许在不同的上下文中共享 bean 实例,如请求或会话。此枚举的主要用途是为这些作用域 bean 提供不同的代理机制。
```java
/**
* 枚举各种作用域代理选项。
*
* 为了更完整地讨论什么是作用域代理,请查看 Spring 参考文档中标题为 '作为依赖的作用域 beans' 的部分。
*
* @author Mark Fisher
* @since 2.5
* @see ScopeMetadata
*/
public enum ScopedProxyMode {
/**
* 默认通常等于 #NO,除非在组件扫描指令级别配置了不同的默认值。
*/
DEFAULT,
/**
* 不创建一个作用域代理。
* 当与非单例作用域实例一起使用时,这种代理模式通常不太有用,如果要作为依赖项使用,
* 它应该优先使用 #INTERFACES 或 #TARGET_CLASS 代理模式。
*/
NO,
/**
* 创建一个JDK动态代理,实现目标对象的类所暴露的所有接口。
*/
INTERFACES,
/**
* 创建一个基于类的代理(使用CGLIB)。
*/
TARGET_CLASS
}
```
`FilterType` 是一个枚举,定义了与 `@ComponentScan` 注解结合使用时的不同类型过滤器选项。这些过滤器用于决定在组件扫描过程中哪些组件应被包括或排除。
```java
/**
* 与 ComponentScan @ComponentScan 结合使用的类型过滤器的枚举。
* 该枚举定义了在组件扫描过程中可以用于过滤组件的不同类型。
*
* @author Mark Fisher
* @author Juergen Hoeller
* @author Chris Beams
* @since 2.5
* @see ComponentScan
* @see ComponentScan#includeFilters()
* @see ComponentScan#excludeFilters()
* @see org.springframework.core.type.filter.TypeFilter
*/
public enum FilterType {
/**
* 过滤带有指定注解的候选项。
* @see org.springframework.core.type.filter.AnnotationTypeFilter
*/
ANNOTATION,
/**
* 过滤可以赋值给指定类型的候选项。
* @see org.springframework.core.type.filter.AssignableTypeFilter
*/
ASSIGNABLE_TYPE,
/**
* 过滤与指定的AspectJ类型模式表达式匹配的候选项。
* @see org.springframework.core.type.filter.AspectJTypeFilter
*/
ASPECTJ,
/**
* 过滤与指定的正则表达式模式匹配的候选项。
* @see org.springframework.core.type.filter.RegexPatternTypeFilter
*/
REGEX,
/**
* 使用给定的自定义 org.springframework.core.type.filter.TypeFilter 实现来过滤候选项。
*/
CUSTOM
}
```
### 四、主要功能
1. **指定扫描的包**
+ 通过 `basePackages` 和 `basePackageClasses` 属性,用户可以明确告诉 Spring 在哪些包中查找带有 `@Component`、`@Service`、`@Repository` 和 `@Controller` 等注解的类。
2. **自动扫描**
+ 如果用户没有明确指定要扫描的包,则默认从声明 `@ComponentScan` 的类所在的包开始进行扫描。
3. **过滤扫描的组件**
+ 通过 `includeFilters` 和 `excludeFilters` 属性,用户可以更精细地控制哪些组件应被扫描或排除。
4. **其他配置**
+ 此注解还提供了其他属性,如 `nameGenerator`(为检测到的组件命名)、`scopeResolver`(解析组件的范围)、`scopedProxy`(是否为组件生成代理)等,以提供更高级的配置。
### 五、最佳实践
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类。在初始化上下文后,该程序会遍历并打印所有在 Spring 容器中定义的 beans 的名字。
```java
public class ComponentScanApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanName = " + beanDefinitionName);
}
}
}
```
在`MyConfiguration`类中,Spring 扫描 `com.xcs.spring` 包及其子包,包括所有 `SpecialComponent` 类型的组件,但排除所有 `AdminService` 类型的组件。
```java
@Configuration
@ComponentScan(
basePackages = "com.xcs.spring",
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SpecialComponent.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AdminService.class)
)
public class MyConfiguration {
}
```
`UserRepository` 的类,位于 `com.xcs.spring.repository` 包中,并用 `@Repository` 注解标记。
```java
package com.xcs.spring.repository;
@Repository
public class UserRepository {
}
```
`AdminService` 和 `UserService`,它们都位于 `com.xcs.spring.service` 包中并分别用 `@Service` 注解标记。
```java
package com.xcs.spring.service;
@Service
public class AdminService {
}
@Service
public class UserService {
}
```
`SpecialComponent` 的类,它位于 `com.xcs.spring.special` 包中,没有使用spring中的任何注解标记。
```java
package com.xcs.spring.special;
public class SpecialComponent {
}
```
运行结果发现,`UserRepository` 将被自动检测并注册为一个 Spring bean,因为它位于我们指定的 `com.xcs.spring` 包路径下。`UserService` 将被自动检测并注册为一个 Spring bean,因为它位于我们指定的 `com.xcs.spring` 包路径下。但是,由于 `@ComponentScan` 配置中使用了 `excludeFilters` 明确排除了 `AdminService`,所以即使 `AdminService` 位于 `com.xcs.spring` 包路径下,它也不会被注册为一个 Spring bean。虽然`SpecialComponent` 类是一个没有任何 Spring 注解的普通 Java 类。但通过使用 `@ComponentScan` 的 `includeFilters` 和 `FilterType.ASSIGNABLE_TYPE`,我们可以强制 Spring 上下文扫描并注册它为一个 bean,即使它没有标记为 `@Component` 或其他 Spring 注解。
```java
beanName = org.springframework.context.annotation.internalConfigurationAnnotationProcessor
beanName = org.springframework.context.annotation.internalAutowiredAnnotationProcessor
beanName = org.springframework.context.event.internalEventListenerProcessor
beanName = org.springframework.context.event.internalEventListenerFactory
beanName = myConfiguration
beanName = userRepository
beanName = userService
beanName = specialComponent
```
### 六、时序图
~~~mermaid
sequenceDiagram
Title: @ComponentScan注解时序图
ComponentScanApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)
AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()
AbstractApplicationContext->>AbstractApplicationContext:invokeBeanFactoryPostProcessors(beanFactory)
AbstractApplicationContext->>PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors)
PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate:invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup)
PostProcessorRegistrationDelegate->>ConfigurationClassPostProcessor:postProcessBeanDefinitionRegistry(registry)
ConfigurationClassPostProcessor->>ConfigurationClassPostProcessor:processConfigBeanDefinitions(registry)
ConfigurationClassPostProcessor->>ConfigurationClassParser:ConfigurationClassParser(...)
ConfigurationClassParser-->>ConfigurationClassPostProcessor:返回解析解析器
ConfigurationClassPostProcessor->>ConfigurationClassParser:parser.parse(candidates)
ConfigurationClassParser->>ConfigurationClassParser:parse(metadata, String beanName)
ConfigurationClassParser->>ConfigurationClassParser:processConfigurationClass(configClass,filter)
ConfigurationClassParser->>ConfigurationClassParser:doProcessConfigurationClass(configClass,sourceClass,filter)
ConfigurationClassParser->>ComponentScanAnnotationParser:parse(componentScan,declaringClass)
ComponentScanAnnotationParser->>ClassPathBeanDefinitionScanner:ClassPathBeanDefinitionScanner(registry,useDefaultFilters,environment,resourceLoader)
ClassPathBeanDefinitionScanner->>ClassPathBeanDefinitionScanner:registerDefaultFilters()
ClassPathBeanDefinitionScanner-->>ComponentScanAnnotationParser:返回扫描器
ComponentScanAnnotationParser->>ClassPathBeanDefinitionScanner:doScan(basePackages)
ClassPathBeanDefinitionScanner->>ClassPathScanningCandidateComponentProvider:findCandidateComponents(basePackage)
ClassPathScanningCandidateComponentProvider->>ClassPathScanningCandidateComponentProvider:scanCandidateComponents(basePackage)
ClassPathScanningCandidateComponentProvider-->>ClassPathBeanDefinitionScanner:返回BeanDefinition
ClassPathBeanDefinitionScanner->>ClassPathBeanDefinitionScanner:registerBeanDefinition(definitionHolder,registry)
ClassPathBeanDefinitionScanner->>BeanDefinitionReaderUtils:registerBeanDefinition(definitionHolder, registry)
BeanDefinitionReaderUtils->>DefaultListableBeanFactory:registerBeanDefinition(beanName,beanDefinition)
ClassPathBeanDefinitionScanner-->>ComponentScanAnnotationParser:返回BeanDefinition
~~~
### 七、源码分析
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类。在初始化上下文后,该程序会遍历并打印所有在 Spring 容器中定义的 beans 的名字。
```java
public class ComponentScanApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanName = " + beanDefinitionName);
}
}
}
```
在`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中,执行了三个步骤,我们重点关注`refresh()`方法。
```java
public AnnotationConfigApplicationContext(Class>... componentClasses) {
this();
register(componentClasses);
refresh();
}
```
在`org.springframework.context.support.AbstractApplicationContext#refresh`方法中我们重点关注一下`finishBeanFactoryInitialization(beanFactory)`这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。
```java
@Override
public void refresh() throws BeansException, IllegalStateException {
// ... [代码部分省略以简化]
// 调用在上下文中注册为bean的工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors`方法中,又委托了`PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()`进行调用。
```java
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors`方法中,首先调用了 `BeanDefinitionRegistryPostProcessor`(这是 `BeanFactoryPostProcessor` 的子接口)。它专门用来在所有其他 bean 定义加载之前修改默认的 bean 定义。
```java
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) {
// ... [代码部分省略以简化]
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors`方法中,循环调用了实现`BeanDefinitionRegistryPostProcessor`接口中的`postProcessBeanDefinitionRegistry(registry)`方法
```java
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
.tag("postProcessor", postProcessor::toString);
postProcessor.postProcessBeanDefinitionRegistry(registry);
postProcessBeanDefRegistry.end();
}
}
```
在`org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry`方法中,调用了`processConfigBeanDefinitions`方法,该方法的主要目的是处理和注册配置类中定义的beans。
```java
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ... [代码部分省略以简化]
processConfigBeanDefinitions(registry);
}
```
在`org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions`方法中,这个方法主要处理了配置类的解析和验证,并确保了所有在配置类中定义的beans都被正确地注册到Spring的bean定义注册表中。
```java
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// ... [代码部分省略以简化]
// 步骤1:创建一个用于解析配置类的解析器
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// 步骤2:初始化候选配置类集合以及已解析配置类集合
Set candidates = new LinkedHashSet<>(configCandidates);
Set alreadyParsed = new HashSet<>(configCandidates.size());
// 步骤3:循环处理所有候选配置类,直至没有候选类为止
do {
// 步骤3.1 解析配置类
parser.parse(candidates);
// 步骤3.2 验证配置类
parser.validate();
// 获取解析后的配置类,并从中移除已经处理过的
Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// 步骤4:如果reader为空,则创建一个新的Bean定义读取器
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// 步骤5:使用读取器为解析的配置类加载Bean定义
this.reader.loadBeanDefinitions(configClasses);
// ... [代码部分省略以简化]
} while (!candidates.isEmpty());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#parse`方法中,主要是遍历所有的配置类候选者,并对每一个带有注解的Bean定义进行解析。这通常涉及到查找该配置类中的@Bean方法、组件扫描指令等,并将这些信息注册到Spring容器中。
```java
public void parse(Set configCandidates) {
// ... [代码部分省略以简化]
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#parse(metadata, beanName)`方法中,将注解元数据和Bean名称转化为一个配置类,然后对其进行处理。处理配置类是Spring配置驱动的核心,它涉及到许多关键操作,如处理`@ComponentScan`注解等等。
```java
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass`方法中,处理一个给定的配置类。它首先递归地处理配置类及其父类,以确保所有相关的配置都被正确地读取并解析。在递归处理完所有相关配置后,它将配置类添加到已解析的配置类的映射中。
```java
protected void processConfigurationClass(ConfigurationClass configClass, Predicate filter) throws IOException {
// ... [代码部分省略以简化]
// 步骤1:递归地处理配置类及其超类层次结构
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
} while (sourceClass != null);
// 步骤2:将处理后的配置类放入映射中
this.configurationClasses.put(configClass, configClass);
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass`方法中,这个方法的目标是处理和解析标有 `@Configuration` 的类,执行组件扫描,并确保所有相关的配置类都被递归地解析。
```java
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate filter)
throws IOException {
// ... [代码部分省略以简化]
// 处理任何 @ComponentScan 注解
// 获取当前类(sourceClass)的所有 @ComponentScan 和 @ComponentScans 注解的属性
Set componentScans = AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
// 如果存在 @ComponentScan 或 @ComponentScans 注解,并且该类没有被条件评估排除
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
// 遍历每一个 @ComponentScan 注解
for (AnnotationAttributes componentScan : componentScans) {
// 对标有 @ComponentScan 的配置类进行立即扫描
Set scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
// 检查扫描到的定义中是否有任何进一步的配置类,如果需要,则递归解析
for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
if (bdCand == null) {
bdCand = holder.getBeanDefinition();
}
// 检查 BeanDefinition 是否是一个配置类的候选者
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
// 如果是,递归解析它
parse(bdCand.getBeanClassName(), holder.getBeanName());
}
}
}
}
// ... [代码部分省略以简化]
// 没有父类 -> 处理完成
return null;
}
```
在`org.springframework.context.annotation.ComponentScanAnnotationParser#parse`方法中,主要目的是为 `@ComponentScan` 配置的类提供了详细的处理,并指导了如何根据给定的属性配置和执行组件扫描。
```java
public Set parse(AnnotationAttributes componentScan, final String declaringClass) {
// 步骤1. 创建一个新的扫描器
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
// 步骤2. 根据nameGenerator属性设置Bean名称生成器
Class extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
BeanUtils.instantiateClass(generatorClass));
// 步骤3. 设置作用域代理模式或者作用域元数据解析器
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));
}
// 步骤4. 设置资源模式
scanner.setResourcePattern(componentScan.getString("resourcePattern"));
// 步骤5. 根据includeFilters和excludeFilters属性添加类型过滤器
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);
}
}
// 步骤6. 设置bean是否为懒加载
boolean lazyInit = componentScan.getBoolean("lazyInit");
if (lazyInit) {
scanner.getBeanDefinitionDefaults().setLazyInit(true);
}
// 步骤7. 确定扫描器的基础包
Set 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));
}
// 步骤8. 确保声明@ComponentScan的类本身不被注册为bean
scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
@Override
protected boolean matchClassName(String className) {
return declaringClass.equals(className);
}
});
// 步骤9. 使用配置好的扫描器执行实际的组件扫描
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
```
我们来到`org.springframework.context.annotation.ComponentScanAnnotationParser#parse`方法中的步骤1。在`org.springframework.context.annotation.ClassPathBeanDefinitionScanner#ClassPathBeanDefinitionScanner`方法中,首先在这个构造方法初始化了一个新的`ClassPathBeanDefinitionScanner`对象,根据传入的参数决定是否使用默认过滤器,并设置了其环境和资源加载器。
```java
public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
Environment environment, @Nullable ResourceLoader resourceLoader) {
// 断言确保注册表不为空
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
// 将传入的BeanDefinitionRegistry赋值给成员变量registry
this.registry = registry;
// 根据useDefaultFilters决定是否注册默认的过滤器
if (useDefaultFilters) {
registerDefaultFilters();
}
// 设置扫描器的环境
setEnvironment(environment);
// 设置资源加载器
setResourceLoader(resourceLoader);
}
```
在`org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#registerDefaultFilters`方法中,此方法主要用于注册默认的类型过滤器。它首先注册了用于查找带有`@Component`注解的类的过滤器。然后,它尝试注册两个JSR标准的注解过滤器:JSR-250的`@ManagedBean`和JSR-330的`@Named`。如果相关的类不在类路径上,那么这两个过滤器将不会被注册。
```java
protected void registerDefaultFilters() {
// 添加一个过滤器来包括带有@Component注解的类
this.includeFilters.add(new AnnotationTypeFilter(Component.class));
// 获取当前类的类加载器
ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
try {
// 尝试添加一个过滤器来包括带有JSR-250 'javax.annotation.ManagedBean'注解的类
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(如Java EE 6中包含的)不可用,仅仅跳过
}
try {
// 尝试添加一个过滤器来包括带有JSR-330 'javax.inject.Named'注解的类
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不可用,仅仅跳过
}
}
```
我们来到`org.springframework.context.annotation.ComponentScanAnnotationParser#parse`方法中的步骤9。在`org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan`方法中,主要目标是找到指定`basePackages`中所有的组件,并为它们创建 `BeanDefinition`。这些 `BeanDefinition` 之后会被 Spring 容器用来创建实际的 bean 实例。
```java
protected Set doScan(String... basePackages) {
// 断言确保至少有一个基础包被指定
Assert.notEmpty(basePackages, "At least one base package must be specified");
// 用于保存找到的bean定义的集合
Set beanDefinitions = new LinkedHashSet<>();
// 遍历每个基础包
for (String basePackage : basePackages) {
// 步骤1. 在给定的基础包中找到所有候选的bean定义
Set candidates = findCandidateComponents(basePackage);
// 遍历找到的bean定义
for (BeanDefinition candidate : candidates) {
// 步骤2. 解析bean的作用域元数据
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
// 设置bean的作用域
candidate.setScope(scopeMetadata.getScopeName());
// 步骤3. 生成bean的名字
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// 步骤4. 如果是AbstractBeanDefinition,进行后处理
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
// 步骤5. 如果是AnnotatedBeanDefinition,处理常见的注解定义
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 步骤6. 检查给定的bean名字是否已经存在,如果不存在,进行注册
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
// 步骤7. 应用作用域代理模式,如有必要为bean创建代理
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 将bean定义加入集合中
beanDefinitions.add(definitionHolder);
// 步骤8. 在bean注册表中注册bean定义
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
// 返回所有注册的bean定义
return beanDefinitions;
}
```
我们来到`org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan`方法中的步骤1。在`org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#findCandidateComponents`方法中,主要提供了两种方式查找组件:通过预先生成的索引(如果可用且支持)或通过传统的扫描方式(我们重点关注传统的扫描方式)。
```java
public Set findCandidateComponents(String basePackage) {
// 如果存在组件索引并且支持include过滤器
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
// 从索引中添加候选组件
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
// 扫描给定基础包中的候选组件
return scanCandidateComponents(basePackage);
}
}
```
在`org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#scanCandidateComponents`方法中,首先是构建搜索路径,用于在类路径中搜索指定包,然后是扫描类路径,获取匹配的资源(通常是 `.class` 文件),再然后是对于每个资源,检查是否是候选组件,例如是否有 `@Component` 注解,最后对于是候选组件的类,创建一个 `BeanDefinition` 对象并添加到结果集中。
```java
private Set scanCandidateComponents(String basePackage) {
// 用于保存候选的Bean定义
Set candidates = new LinkedHashSet<>();
try {
// 构建包搜索路径,例如:"classpath*:com/example/*"
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 使用模式解析器获取所有匹配的资源(即.class文件)
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
// ... [代码部分省略以简化]
for (Resource resource : resources) {
// ... [代码部分省略以简化]
// 检查资源是否可读
if (resource.isReadable()) {
try {
// 使用元数据读取器获取类的元数据
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
// 检查类是否是候选组件(例如,是否带有@Component注释)
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
// 再次检查Bean定义是否是候选组件
if (isCandidateComponent(sbd)) {
// ... [代码部分省略以简化]
candidates.add(sbd);
}
else {
// ... [代码部分省略以简化]
}
}
else {
// ... [代码部分省略以简化]
}
}
catch (Throwable ex) {
// ... [代码部分省略以简化]
}
}
else {
// ... [代码部分省略以简化]
}
}
}
catch (IOException ex) {
// ... [代码部分省略以简化]
}
return candidates;
}
```
在`org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider#isCandidateComponent`方法中,首先确保类不在排除列表中,然后检查它是否在包含列表中,并确保它满足任何其他指定条件。
```java
protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
// 遍历所有的排除过滤器
for (TypeFilter tf : this.excludeFilters) {
// 如果当前类与任一排除过滤器匹配,则直接返回false,说明不是候选组件
if (tf.match(metadataReader, getMetadataReaderFactory())) {
return false;
}
}
// 遍历所有的包含过滤器
for (TypeFilter tf : this.includeFilters) {
// 如果当前类与任一包含过滤器匹配
if (tf.match(metadataReader, getMetadataReaderFactory())) {
// 判断该组件是否满足特定的条件
return isConditionMatch(metadataReader);
}
}
// 默认返回false,说明不是候选组件
return false;
}
```
我们来到`org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan`方法中的步骤6。在org.springframework.context.annotation.ClassPathBeanDefinitionScanner#checkCandidate方法中,确保Spring容器中没有重名的、不兼容的bean定义。
```java
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
// 检查bean定义注册表中是否已包含给定名称的bean定义
if (!this.registry.containsBeanDefinition(beanName)) {
return true; // 如果不存在相同名称的bean定义,则返回true
}
// 获取已存在的bean定义
BeanDefinition existingDef = this.registry.getBeanDefinition(beanName);
// 获取原始的bean定义(如果有的话)
BeanDefinition originatingDef = existingDef.getOriginatingBeanDefinition();
if (originatingDef != null) {
existingDef = originatingDef;
}
// 检查给定的bean定义与已存在的bean定义是否兼容
if (isCompatible(beanDefinition, existingDef)) {
return false; // 如果它们是兼容的,则返回false
}
// 如果给定的bean定义与已存在的bean定义不兼容,则抛出异常
throw new ConflictingBeanDefinitionException("Annotation-specified bean name '" + beanName +
"' for bean class [" + beanDefinition.getBeanClassName() + "] conflicts with existing, " +
"non-compatible bean definition of same name and class [" + existingDef.getBeanClassName() + "]");
}
```
我们来到`org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan`方法中的步骤8。在`org.springframework.context.annotation.ClassPathBeanDefinitionScanner#registerBeanDefinition`方法中,主要调用 `BeanDefinitionReaderUtils` 类的 `registerBeanDefinition` 方法,用于实际的 `BeanDefinition` 注册过程。
```java
protected void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) {
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
}
```
在`org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition`方法中,主要用于将提供的 `BeanDefinitionHolder` 中的 `BeanDefinition` 及其所有别名注册到 `BeanDefinitionRegistry` 中。对于`@ComponentScan`的扫描和注册阶段而言,当`registerBeanDefinition`方法被调用时,已经完成了。但对于整个Spring容器的生命周期来说,还有其他重要的步骤将在后续发生,如bean的生命周期回调、bean的实例化、bean的初始化等。
```java
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// 获取 bean 的主名称,并在 registry 中注册它
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// 如果提供了 bean 的别名,则注册这些别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
```
### 八、注意事项
**默认扫描**
+ 如果未指定具体的包,`@ComponentScan` 默认会扫描声明此注解的类所在的包及其子包。
**性能考虑**
+ 避免扫描不必要的包,因为这可能导致性能问题。尤其是在大型项目中,指定扫描的精确路径可以加速启动时间。
**默认过滤器**
+ 默认情况下,`@ComponentScan` 使用的过滤器会搜索带有 `@Component`, `@Service`, `@Repository`, 和 `@Controller` 的类。可以通过 `includeFilters` 和 `excludeFilters` 属性进行定制。
**冲突的 Bean 名称**
+ 确保没有重复的 Bean 名称,否则可能会导致 `BeanDefinitionStoreException`。
**使用 `basePackages` 和 `basePackageClasses`**
+ `basePackages` 允许我们指定要扫描的包的名称,而 `basePackageClasses` 允许我们指定一个或多个类,Spring 将扫描这些类所在的包。
**避免使用多个配置**
+ 不建议在同一个配置类中使用多个 `@ComponentScan`。如果确实需要,考虑使用 `@ComponentScans`。
**代理模式**
+ 考虑如何使用 `scopedProxy` 属性,特别是当我们使用非单例作用域的 beans 时。
**注解属性的覆盖**
+ 当多个 `@ComponentScan` 在多个配置类中定义时,后面的定义将覆盖前面的定义。这里需要我们自己去确认。
**对于大型项目,考虑使用模块化**
+ 在大型项目中,为了更好的管理和维护,可以考虑将应用分成多个模块,每个模块有其自己的配置类和 `@ComponentScan`。
### 九、总结
#### 最佳实践总结
1. **应用启动**
+ 在 `ComponentScanApplication` 的主方法中,使用 `AnnotationConfigApplicationContext` 初始化了 Spring 上下文,并将配置类 `MyConfiguration` 传递给它。这告诉 Spring 在 `MyConfiguration` 类中查找配置信息。
2. **配置类**
+ `MyConfiguration` 类被标记为 `@Configuration`,表明它是一个配置类。这个类进一步使用 `@ComponentScan` 注解指定了 Spring 应该在哪里寻找组件。具体来说,Spring 将扫描 `com.xcs.spring` 包及其所有子包。
3. **扫描规则**
+ 在 `@ComponentScan` 中,我们使用 `includeFilters` 明确指定 `SpecialComponent` 类被包含在 Spring 容器中,即使它没有使用任何 Spring 注解。同时,使用 `excludeFilters` 指定 `AdminService` 类不应该被 Spring 容器管理,即使它被标记为一个 `@Service`。
4. **组件类**:
- `UserRepository` 类在 `com.xcs.spring.repository` 包中,并被标记为 `@Repository`,因此它自动被 Spring 容器管理。
- `UserService` 类在 `com.xcs.spring.service` 包中,并被标记为 `@Service`,因此它也自动被 Spring 容器管理。
- `AdminService` 虽然也被标记为 `@Service`,但由于 `@ComponentScan` 的 `excludeFilters` 配置,它没有被 Spring 容器管理。
- `SpecialComponent` 类没有使用任何 Spring 注解,但由于 `@ComponentScan` 的 `includeFilters` 配置,它被 Spring 容器管理。
5. **运行结果**
+ 当应用启动时,所有被 Spring 容器管理的 beans 的名字都被打印出来,这包括了 `UserRepository`, `UserService`, 和 `SpecialComponent`。不包括 `AdminService`,因为它被排除了。
#### 源码分析总结
1. **应用启动**
+ 通过 `AnnotationConfigApplicationContext` 的构造方法,传入配置类 `MyConfiguration`,来启动Spring应用。
2. **刷新上下文**
+ 在构造方法内部,调用了 `refresh()` 方法开始执行容器的刷新操作。
3. **执行BeanFactory的后处理器**
+ `invokeBeanFactoryPostProcessors(beanFactory)` 方法被调用,它主要执行 `BeanDefinitionRegistryPostProcessor` 和 `BeanFactoryPostProcessor`。其中, `BeanDefinitionRegistryPostProcessor` 是在所有其他bean定义加载之前,用来修改默认的bean定义。
4. **处理配置类**
+ `ConfigurationClassPostProcessor` 是一个核心的后处理器,它会解析配置类(如带有 `@Configuration` 的类),找到 `@ComponentScan` 注解并解析它的属性,然后进行组件扫描。
5. **执行组件扫描**
+ 通过 `ComponentScanAnnotationParser` 类进行详细的扫描操作。它创建一个 `ClassPathBeanDefinitionScanner` 对象,设置其属性(如是否使用默认过滤器、资源加载器、作用域解析器、资源模式、包含和排除的过滤器等),然后扫描指定的基础包。
6. **扫描候选组件**
+ 对于每个基础包,它会查找所有的组件,并为这些组件创建 `BeanDefinition` 对象。
7. **注册Bean定义**
+ 找到的组件都会被注册到Spring容器中。这是通过调用 `registerBeanDefinition` 方法来完成的。如果在容器中已存在同名的bean定义,会进行冲突检查。
8. **完成组件扫描**
+ 当所有的基础包都被扫描完成,`@ComponentScan` 的操作就执行结束了。
================================================
FILE: spring-annotation/spring-annotation-componentScan/pom.xml
================================================
spring-annotation
com.xcs.spring
0.0.1-SNAPSHOT
4.0.0
spring-annotation-componentScan
================================================
FILE: spring-annotation/spring-annotation-componentScan/src/main/java/com/xcs/spring/ComponentScanApplication.java
================================================
package com.xcs.spring;
import com.xcs.spring.config.MyConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 2023年08月07日 16时21分
**/
public class ComponentScanApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanName = " + beanDefinitionName);
}
}
}
================================================
FILE: spring-annotation/spring-annotation-componentScan/src/main/java/com/xcs/spring/config/MyConfiguration.java
================================================
package com.xcs.spring.config;
import com.xcs.spring.service.AdminService;
import com.xcs.spring.special.SpecialComponent;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
/**
* @author xcs
* @date 2023年08月07日 16时25分
**/
@Configuration
@ComponentScan(
basePackages = "com.xcs.spring",
includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = SpecialComponent.class),
excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AdminService.class)
)
public class MyConfiguration {
}
================================================
FILE: spring-annotation/spring-annotation-componentScan/src/main/java/com/xcs/spring/repository/UserRepository.java
================================================
package com.xcs.spring.repository;
import org.springframework.stereotype.Repository;
/**
* @author xcs
* @date 2023年10月07日 11时51分
**/
@Repository
public class UserRepository {
}
================================================
FILE: spring-annotation/spring-annotation-componentScan/src/main/java/com/xcs/spring/service/AdminService.java
================================================
package com.xcs.spring.service;
import org.springframework.stereotype.Service;
/**
* @author xcs
* @date 2023年10月07日 11时51分
**/
@Service
public class AdminService {
}
================================================
FILE: spring-annotation/spring-annotation-componentScan/src/main/java/com/xcs/spring/service/UserService.java
================================================
package com.xcs.spring.service;
import org.springframework.stereotype.Service;
/**
* @author xcs
* @date 2023年10月07日 11时50分
**/
@Service
public class UserService {
}
================================================
FILE: spring-annotation/spring-annotation-componentScan/src/main/java/com/xcs/spring/special/SpecialComponent.java
================================================
package com.xcs.spring.special;
/**
* @author xcs
* @date 2023年10月07日 11时52分
**/
public class SpecialComponent {
}
================================================
FILE: spring-annotation/spring-annotation-conditional/README.md
================================================
## @Conditional
- [@Conditional](#conditional)
- [一、基本信息](#一基本信息)
- [二、注解描述](#二注解描述)
- [三、注解源码](#三注解源码)
- [四、主要功能](#四主要功能)
- [五、最佳实践](#五最佳实践)
- [在@Bean上使用](#在bean上使用)
- [在@Configuration上使用](#在configuration上使用)
- [自定义组合注解](#自定义组合注解)
- [六、时序图](#六时序图)
- [七、源码分析](#七源码分析)
- [八、注意事项](#八注意事项)
- [九、总结](#九总结)
- [最佳实践总结](#最佳实践总结)
- [源码分析总结](#源码分析总结)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [我的CSDN](https://blog.csdn.net/duzhuang2399/article/details/133800722) 📚 **文章目录** - [所有文章](https://github.com/xuchengsheng/spring-reading) 🔗 **源码地址** - [@Conditional源码](https://github.com/xuchengsheng/spring-reading/tree/master/spring-annotation/spring-annotation-conditional)
### 二、注解描述
`@Conditional`注解,是用来基于满足某些特定条件来决定一个Bean是否应该被注册到Spring容器的。这提供了一种灵活的方式来根据环境、配置或其他因素来决定是否激活或者创建某个Bean。
### 三、注解源码
`@Conditional`注解是 Spring 框架自 3.0 版本开始引入的一个核心注解,用于指示一个组件只在所有指定条件匹配时才能被注册。
```java
/**
* 表明只有当所有指定的条件都满足时,组件才有资格被注册。
*
* 条件是可以在bean定义被注册前以编程方式确定的任何状态(参考 Condition 获取详情)。
*
* @Conditional 注解可以以下列方式使用:
*
* 作为直接或间接使用 @Component 注解的任何类的类型级别注解,包括 Configuration @Configuration 类
* 作为元注解,用于组合自定义的范型注解
* 作为任何 Bean @Bean 方法的方法级别注解
*
*
* 如果一个 @Configuration 类标记为 @Conditional,与该类关联的所有 @Bean 方法、Import @Import 注解,
* 和 ComponentScan @ComponentScan 注解都将受到这些条件的限制。
*
* 注意:不支持 @Conditional 注解的继承;任何从超类或从被覆盖的方法继承的条件都不会被考虑。
* 为了强制这些语义,@Conditional 本身未声明为 java.lang.annotation.Inherited @Inherited;
* 此外,任何用 @Conditional 作为元注解的自定义组成注解也不应声明为 @Inherited。
*
* @author Phillip Webb
* @author Sam Brannen
* @since 4.0
* @see Condition
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
/**
* 所有必须满足的 {@link Condition} 类,以便组件可以被注册。
*/
Class extends Condition>[] value();
}
```
### 四、主要功能
1. **条件化 Bean 注册**
+ 可以根据特定的条件来决定是否创建并注册一个 Bean。这允许我们根据环境、配置或其他因素动态地选择哪些 Bean 需要被实例化。
2. **条件化配置类**
+ 不仅可以对单个 Bean 使用,还可以对整个配置类使用。如果配置类上的条件不满足,那么该配置类中定义的所有 Beans 都不会被注册。
3. **灵活性**
+ 与一个实现了 `Condition` 接口的类一起使用。这个接口允许我们定义自己的条件逻辑,使得其可以非常灵活地根据各种场景来决定是否注册 Bean。
4. **与其他注解组合**
+ 除了与 `@Bean` 和 `@Configuration` 注解一起使用外,`@Conditional` 还可以作为元注解,用于创建自定义的组合注解,这些组合注解内部使用 `@Conditional` 来应用条件逻辑。
5. **对整个配置类的影响**
+ 当 `@Conditional` 用于配置类时,不仅仅是该类,还有与该类关联的所有 `@Bean` 方法、`@Import` 注解和 `@ComponentScan` 注解都将受到条件的影响。
6. **不支持继承**
+ `@Conditional` 注解本身不是继承的,因此,从父类或接口继承的条件不会被子类考虑。
### 五、最佳实践
#### 在@Bean上使用
首先来看看启动类入口,首先设置一个系统属性`enable.bean`为`true`,然后上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,最后打印了Spring上下文中所有的bean定义名称。
```java
public class ConditionBeanApplication {
public static void main(String[] args) {
System.setProperty("enable.bean","true");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBeanConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
}
}
```
`MyBeanConfiguration`,其中定义了两个bean:`user1`和`user2`。`user1` bean的创建是基于条件的,具体取决于`BeanPropertyCondition`条件的结果。而`user2` bean则无条件创建。
```java
@Configuration
public class MyBeanConfiguration {
@Bean
@Conditional(BeanPropertyCondition.class)
public User1 user1() {
return new User1();
}
@Bean
public User2 user2() {
return new User2();
}
}
```
`BeanPropertyCondition`。这个实现会根据`enable.bean`属性的值决定是否满足条件。具体来说:如果环境属性`enable.bean`的值是`true`,则`user1` bean会被创建并添加到Spring容器。如果`enable.bean`不是`true`(或者没有设置这个属性),`user1` bean不会被创建。
```java
public class BeanPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "true".equals(context.getEnvironment().getProperty("enable.bean"));
}
}
```
定义两个简单的Java类:`User1`和`User2`。
```java
public class User1 {
}
public class User2 {
}
```
当`enable.bean`为`true`运行结果发现,根据`enable.bean`属性的值来注册`user1` bean,而`user2` bean则不受此属性的影响。
```java
beanDefinitionName = myBeanConfiguration
beanDefinitionName = user1
beanDefinitionName = user2
```
当`enable.bean`为`false`运行结果发现,`enable.bean`值为`false`,所以条件不满足。因此`user1`bean不会被注册,`user2` bean不受任何条件的影响。
```java
beanDefinitionName = myBeanConfiguration
beanDefinitionName = user2
```
#### 在@Configuration上使用
首先来看看启动类入口,首先设置一个系统属性`enable.config`为`true`,然后上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,最后打印了Spring上下文中所有的bean定义名称。
```java
public class ConditionConfigurationApplication {
public static void main(String[] args) {
System.setProperty("enable.config","true");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfigConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
}
}
```
`MyConfigConfiguration`,其中定义了两个bean:`user3`和`user4`。当`ConfigPropertyCondition`条件不满足时`MyConfigConfiguration`配置类不被激活,该配置类中定义的`user3`和`user4`不会被注册。
```java
@Configuration
@Conditional(ConfigPropertyCondition.class)
public class MyConfigConfiguration {
@Bean
public User3 user3() {
return new User3();
}
@Bean
public User4 user4() {
return new User4();
}
}
```
`ConfigPropertyCondition`。这个实现会根据`enable.config`属性的值决定是否满足条件。具体来说:当`enable.config`设置为`true`,`ConfigPropertyCondition`满足,`MyConfigConfiguration`配置类被激活,`user3`和`user4` beans都将被注册到Spring上下文。当`enable.config`设置为`false`或未设置,`ConfigPropertyCondition`不满足,`MyConfigConfiguration`配置类不被激活,`user3`和`user4` beans都不会被注册。
```java
public class ConfigPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "true".equals(System.getProperty("enable.config"));
}
}
```
定义两个简单的Java类:`User3`和`User4`。
```java
public class User3 {
}
public class User4 {
}
```
当`enable.config`为`true`运行结果发现,`MyConfigConfiguration`中的`user3`和`user4`都被注册了。
```java
beanDefinitionName = myConfigConfiguration
beanDefinitionName = user3
beanDefinitionName = user4
```
当`enable.config`为`false`运行结果发现,我们不会看到任何与 `MyConfigConfiguration` (包括它自己)相关的 beans 被注册了。
```java
无任何bean
```
#### 自定义组合注解
首先来看看启动类入口,首先设置一个系统属性`enable.custom`为`true`,然后上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,最后打印了Spring上下文中所有的bean定义名称。
```java
public class ConditionCustomApplication {
public static void main(String[] args) {
System.setProperty("enable.custom","true");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyCustomConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
}
}
```
`MyCustomConfiguration`,其中定义了两个bean:`user5`和`user6`。如果`@ConditionalOnCustomActive`的条件满足,`MyCustomConfiguration`配置类将被激活,在此配置类中定义`user5`和`user6`将被注册到Spring容器中。如果`@ConditionalOnCustomActive`的条件不满足,`MyCustomConfiguration`配置类将不被激活。`user5`和`user6` beans都不会被注册。
```java
@Configuration
@ConditionalOnCustomActive
public class MyCustomConfiguration {
@Bean
public User5 user5() {
return new User5();
}
@Bean
public User6 user6() {
return new User6();
}
}
```
`@ConditionalOnCustomActive`定义了一个组合注解,并通过`@Conditional`元注解将其关联到`CustomActiveCondition`。
```java
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(CustomActiveCondition.class)
public @interface ConditionalOnCustomActive {
}
```
`CustomActiveCondition`。这个实现会根据`enable.custom`属性的值决定是否满足条件。具体来说:当`enable.custom`设置为`true`,`CustomActiveCondition`满足条件,因为`matches`方法会返回`true`,`MyCustomConfiguration`配置类由于带有`@ConditionalOnCustomActive`注解(该注解内部引用了`CustomActiveCondition`)将被激活,在该配置类中定义的`user5`和`user6`将被注册到Spring容器中。当`enable.custom`设置为`false`或未设置`CustomActiveCondition`不满足条件,因为`matches`方法会返回`false`,由于`MyCustomConfiguration`带有`@ConditionalOnCustomActive`注解,该配置类不被激活。`user5`和`user6` beans都不会被注册。
```java
public class CustomActiveCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "true".equals(System.getProperty("enable.custom"));
}
}
```
定义两个简单的Java类:`User5`和`User6`。
```java
public class User5 {
}
public class User6 {
}
```
当`enable.custom`为`true`运行结果发现,我们的组合注解 `@ConditionalOnCustomActive` 和相应的条件 `CustomActiveCondition` 正常工作,正确地根据 `enable.custom` 系统属性的值来激活 `MyCustomConfiguration` 配置类。
```java
beanDefinitionName = myCustomConfiguration
beanDefinitionName = user5
beanDefinitionName = user6
```
当`enable.custom`为`false`运行结果发现,与 `MyCustomConfiguration` 相关的任何 beans(包括它自己)都不会被注册到 Spring 容器中。
```java
无任何bean
```
### 六、时序图
~~~mermaid
sequenceDiagram
ConditionCustomApplication->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext(componentClasses)
启动上下文
AnnotationConfigApplicationContext-->>ConfigurationApplication: 返回context
返回上下文实例
AnnotationConfigApplicationContext->>AnnotationConfigApplicationContext: register(componentClasses)
注册组件类
AnnotationConfigApplicationContext->>AnnotatedBeanDefinitionReader: register(componentClasses)
读取器注册类
AnnotatedBeanDefinitionReader-->>AnnotatedBeanDefinitionReader: registerBean(beanClass)
注册Bean类
AnnotatedBeanDefinitionReader-->>AnnotatedBeanDefinitionReader: doRegisterBean(beanClass,name,qualifiers, supplier,customizers)
执行Bean注册
AnnotatedBeanDefinitionReader->>ConditionEvaluator:shouldSkip(metadata)
ConditionEvaluator->>ConditionEvaluator:shouldSkip(metadata,phase)
ConditionEvaluator->>ConditionEvaluator:getConditionClasses(metadata)
Note right of ConditionEvaluator: 返回 List
ConditionEvaluator->>ConditionEvaluator:getCondition(conditionClassName,classloader)
ConditionEvaluator->>AnnotationAwareOrderComparator:sort(conditions)
ConditionEvaluator->>CustomActiveCondition:matches(context,metadata)
CustomActiveCondition->>ConditionEvaluator:返回true or false
ConditionEvaluator->>AnnotatedBeanDefinitionReader:返回true or false
AnnotatedBeanDefinitionReader->>AnnotatedBeanDefinitionReader:如果shouldSkip返回是true,跳过Bean的注册
~~~
### 七、源码分析
首先来看看启动类入口,首先设置一个系统属性`enable.custom`为`true`,然后上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,最后打印了Spring上下文中所有的bean定义名称。
```java
public class ConditionCustomApplication {
public static void main(String[] args) {
System.setProperty("enable.custom","true");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyCustomConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
}
}
```
在`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中,执行了三个步骤,我们重点关注`refresh()`方法。
```java
public AnnotationConfigApplicationContext(Class>... componentClasses) {
this();
register(componentClasses);
refresh();
}
```
在`org.springframework.context.annotation.AnnotationConfigApplicationContext#register`方法中,主要是允许我们注册一个或多个组件类(例如,那些使用 `@Component`, `@Service`, `@Repository`, `@Controller`, `@Configuration` 等注解的类)到Spring容器。
```java
@Override
public void register(Class>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register")
.tag("classes", () -> Arrays.toString(componentClasses));
this.reader.register(componentClasses);
registerComponentClass.end();
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#register`方法中,遍历每一个传入的组件类,并逐一调用另一个方法来完成实际的注册工作。
```java
public void register(Class>... componentClasses) {
for (Class> componentClass : componentClasses) {
registerBean(componentClass);
}
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#registerBean(beanClass)`方法中,主要目的是快速注册一个 bean 类型,而不需要指定其他详细的配置或参数。
```java
public void registerBean(Class> beanClass) {
doRegisterBean(beanClass, null, null, null, null);
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean`方法中,主要用于条件性注册 bean 的逻辑,只有当特定的条件满足时,bean 才会被注册。
```java
private void doRegisterBean(Class beanClass, @Nullable String name,
@Nullable Class extends Annotation>[] qualifiers, @Nullable Supplier supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
// 基于给定的 bean 类创建一个带注解的 bean 定义。
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
// 利用条件评估器检查是否应该跳过当前 bean 的注册。
// 如果 bean 不满足指定的条件,那么将直接返回,不继续执行后续的注册逻辑。
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.annotation.ConditionEvaluator#shouldSkip(metadata)`方法中,又委托给另一个版本的 `shouldSkip` 方法,并为第二个参数传入 `null`。
```java
public boolean shouldSkip(AnnotatedTypeMetadata metadata) {
return shouldSkip(metadata, null);
}
```
在`org.springframework.context.annotation.ConditionEvaluator#shouldSkip(metadata,phase)`方法中,主要目的是决定是否应根据给定的条件(通常由 `@Conditional` 注解定义)跳过某个配置类或 bean 的注册。
```java
/**
* 根据提供的元数据和配置阶段判断是否应跳过某个操作或逻辑。
*
* @param metadata 元数据,与注解相关。
* @param phase 当前的配置阶段,可能为 null。
* @return 如果应跳过,则返回 true;否则返回 false。
*/
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
// 如果元数据为空或未标注 @Conditional 注解,则不跳过。
if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
return false;
}
// 如果没有指定配置阶段,确定正确的配置阶段。
if (phase == null) {
// 如果元数据是注解元数据,并且是配置候选项,则选择 PARSE_CONFIGURATION 阶段。
if (metadata instanceof AnnotationMetadata &&
ConfigurationClassUtils.isConfigurationCandidate((AnnotationMetadata) metadata)) {
return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION);
}
// 否则选择 REGISTER_BEAN 阶段。
return shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN);
}
// 获取所有的条件,并从相关的条件类实例化它们。
List conditions = new ArrayList<>();
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;
}
```
在`org.springframework.context.annotation.ConditionEvaluator#getConditionClasses`方法中,从提供的注解元数据中获取与 `@Conditional` 注解关联的条件类的名称。
```java
/**
* 从提供的注解元数据中获取与 @Conditional 注解关联的条件类的名称。
*
* @param metadata 元数据,通常与某个 bean 或配置类的注解相关。
* @return 一个列表,其中包含与 @Conditional 注解关联的条件类的名称。如果没有相关的条件类,则返回一个空列表。
*/
private List getConditionClasses(AnnotatedTypeMetadata metadata) {
// 获取 @Conditional 注解的所有属性值。
MultiValueMap attributes = metadata.getAllAnnotationAttributes(Conditional.class.getName(), true);
// 试图从属性值中获取 "value",它应该是一个指向条件类名称的引用。
Object values = (attributes != null ? attributes.get("value") : null);
// 返回条件类名称的列表,或者如果没有条件类,则返回一个空列表。
return (List) (values != null ? values : Collections.emptyList());
}
```
在`org.springframework.context.annotation.ConditionEvaluator#getCondition`方法中,根据给定的条件类名称和类加载器实例化一个 `Condition` 对象。
```java
/**
* 根据提供的条件类名称和类加载器实例化一个 Condition 对象。
*
* @param conditionClassName 条件类的完全限定名。
* @param classloader 用于加载条件类的类加载器,可以为 null。
* @return 实例化的 Condition 对象。
*/
private Condition getCondition(String conditionClassName, @Nullable ClassLoader classloader) {
// 使用类加载器解析并加载指定的条件类。
Class> conditionClass = ClassUtils.resolveClassName(conditionClassName, classloader);
// 实例化解析的条件类并返回。
return (Condition) BeanUtils.instantiateClass(conditionClass);
}
```
### 八、注意事项
1. **实现 `Condition` 接口**
+ 为了使用 `@Conditional`, 我们需要实现 `Condition` 接口。该接口只有一个方法,`matches(ConditionContext context, AnnotatedTypeMetadata metadata)`,我们需要在这里放置我们的条件逻辑。
2. **类级别和方法级别**:
- `@Conditional` 可以应用于 `@Bean` 方法,用于控制特定 bean 的创建。
- 也可以应用于 `@Configuration` 类,控制整个配置类的加载。
3. **组合多个条件**
+ 可以使用 `@Conditional` 注解的数组形式来组合多个条件,所有条件都必须满足才能创建 bean。
4. **与其他注解的组合**
+ `@Conditional` 可以与其他注解一起使用,如 `@Profile`。但是注意他们之间的交互效果。例如,如果一个 bean 标记为特定的 `@Profile` 并且使用 `@Conditional`,那么两者都必须为 true 才能创建该 bean。
5. **避免复杂的逻辑**
+ 虽然 `Condition` 允许我们编写任意的条件逻辑,但最好避免过于复杂。简单明了的逻辑更易于理解和维护。
6. **性能**
+ `matches` 方法可能会在应用的生命周期中被多次调用,因此应确保其执行效率,避免在此方法中进行高开销的操作。
7. **配合 `ConditionContext`**:
+ `ConditionContext` 提供了关于当前应用上下文、环境属性、系统属性等的信息,可以使我们的条件判断更加具体和强大。
8. **自定义条件注解**
+ 为了重用或组合多个条件,我们可以创建自己的条件注解。例如,我们可以创建一个 `@ConditionalOnCustomActive` 注解,它封装了检查`enable.custom`的条件。
9. **注意与 `@Profile` 的区别**
+ 虽然 `@Conditional` 和 `@Profile` 在某些情况下可以达到相同的效果,但它们的目的不同。`@Profile` 基于环境,而 `@Conditional` 更加通用,允许我们基于任意条件创建 bean。
### 九、总结
#### 最佳实践总结
1. **基于`@Bean`的条件配置**
- **场景描述**
- 在单个bean的创建上应用条件。
- **实现方法**
- 通过在`@Bean`注解方法上直接使用`@Conditional`。
- **结果**:
- 当条件满足(如`enable.bean`为`true`),特定的bean(如`user1`)会被注册。
- 当条件不满足,该bean不会被注册。
2. **基于`@Configuration`的条件配置**
- **场景描述**
- 控制整个配置类的激活状态,从而影响该配置中定义的所有beans。
- **实现方法**
- 在`@Configuration`注解的类上直接使用`@Conditional`。
- **结果**:
- 当条件满足(如`enable.config`为`true`),配置类被激活,其内部的所有beans(如`user3`和`user4`)都会被注册。
- 当条件不满足,配置类及其内部定义的所有beans都不会被注册。
3. **使用自定义组合注解**
- **场景描述**
- 创建自己的条件注解,以提供更清晰、更简洁的语法,或为特定的业务逻辑封装条件逻辑。
- **实现方法**
- 定义一个新的注解(如`@ConditionalOnCustomActive`),并使用`@Conditional`元注解将其关联到特定的条件类。
- **结果**:
- 当条件满足(如`enable.custom`为`true`),带有`@ConditionalOnCustomActive`注解的配置类或bean会被注册。
- 当条件不满足,它们不会被注册。
#### 源码分析总结
1. **初始化与启动**
- 通过 `AnnotationConfigApplicationContext` 构造函数初始化 Spring 上下文,并通过 `register` 和 `refresh` 方法完成 bean 的注册和容器的刷新。
2. **注册组件类**
+ 使用 `AnnotatedBeanDefinitionReader` 来注册组件类。在 `register` 方法中,每一个组件类都会通过 `registerBean` 方法进行注册。
3. **条件检查**
+ 在 `doRegisterBean` 方法中,执行了核心的条件检查逻辑。使用 `ConditionEvaluator` 来评估与给定 bean 或配置类关联的条件是否满足。
4. **元数据检查**
+ `ConditionEvaluator` 会首先检查提供的元数据(通常与特定的 bean 或配置类的注解关联)是否包含 `@Conditional` 注解。如果没有,直接进行下一步。如果存在,继续检查条件是否满足。
5. **条件匹配**
+ `ConditionEvaluator` 获取与 `@Conditional` 注解关联的所有条件类,并为每一个条件类创建一个实例。随后,它会遍历所有的条件,并检查它们是否满足。这是通过调用每一个条件的 `matches` 方法来完成的。如果任何一个条件不满足,整个条件检查逻辑返回 `false`,表示不应注册与 `@Conditional` 注解关联的 bean 或配置类。
6. **条件实例化**
+ 通过 `getCondition` 方法,`ConditionEvaluator` 能够根据提供的条件类名称和类加载器实例化一个 `Condition` 对象。
================================================
FILE: spring-annotation/spring-annotation-conditional/pom.xml
================================================
spring-annotation
com.xcs.spring
0.0.1-SNAPSHOT
4.0.0
spring-annotation-conditional
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/bean/ConditionBeanApplication.java
================================================
package com.xcs.spring.bean;
import com.xcs.spring.bean.config.MyBeanConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 2023年08月07日 16时21分
**/
public class ConditionBeanApplication {
public static void main(String[] args) {
System.setProperty("enable.bean","false");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBeanConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
}
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/bean/condition/BeanPropertyCondition.java
================================================
package com.xcs.spring.bean.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class BeanPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "true".equals(context.getEnvironment().getProperty("enable.bean"));
}
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/bean/config/MyBeanConfiguration.java
================================================
package com.xcs.spring.bean.config;
import com.xcs.spring.bean.condition.BeanPropertyCondition;
import com.xcs.spring.bean.entity.User1;
import com.xcs.spring.bean.entity.User2;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBeanConfiguration {
@Bean
@Conditional(BeanPropertyCondition.class)
public User1 user1() {
return new User1();
}
@Bean
public User2 user2() {
return new User2();
}
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/bean/entity/User1.java
================================================
package com.xcs.spring.bean.entity;
public class User1 {
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/bean/entity/User2.java
================================================
package com.xcs.spring.bean.entity;
public class User2 {
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/configuration/ConditionConfigurationApplication.java
================================================
package com.xcs.spring.configuration;
import com.xcs.spring.configuration.config.MyConfigConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 2023年08月07日 16时21分
**/
public class ConditionConfigurationApplication {
public static void main(String[] args) {
System.setProperty("enable.config","false");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfigConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
}
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/configuration/condition/ConfigPropertyCondition.java
================================================
package com.xcs.spring.configuration.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class ConfigPropertyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "true".equals(System.getProperty("enable.config"));
}
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/configuration/config/MyConfigConfiguration.java
================================================
package com.xcs.spring.configuration.config;
import com.xcs.spring.configuration.condition.ConfigPropertyCondition;
import com.xcs.spring.configuration.entity.User3;
import com.xcs.spring.configuration.entity.User4;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
@Conditional(ConfigPropertyCondition.class)
public class MyConfigConfiguration {
@Bean
public User3 user3() {
return new User3();
}
@Bean
public User4 user4() {
return new User4();
}
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/configuration/entity/User3.java
================================================
package com.xcs.spring.configuration.entity;
public class User3 {
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/configuration/entity/User4.java
================================================
package com.xcs.spring.configuration.entity;
public class User4 {
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/custom/ConditionCustomApplication.java
================================================
package com.xcs.spring.custom;
import com.xcs.spring.custom.config.MyCustomConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 2023年08月07日 16时21分
**/
public class ConditionCustomApplication {
public static void main(String[] args) {
System.setProperty("enable.custom","false");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyCustomConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanDefinitionName = " + beanDefinitionName);
}
}
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/custom/annotation/ConditionalOnCustomActive.java
================================================
package com.xcs.spring.custom.annotation;
import com.xcs.spring.custom.condition.CustomActiveCondition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Conditional(CustomActiveCondition.class)
public @interface ConditionalOnCustomActive {
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/custom/condition/CustomActiveCondition.java
================================================
package com.xcs.spring.custom.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
public class CustomActiveCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return "true".equals(System.getProperty("enable.custom"));
}
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/custom/config/MyCustomConfiguration.java
================================================
package com.xcs.spring.custom.config;
import com.xcs.spring.custom.annotation.ConditionalOnCustomActive;
import com.xcs.spring.custom.entity.User5;
import com.xcs.spring.custom.entity.User6;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnCustomActive
public class MyCustomConfiguration {
@Bean
public User5 user5() {
return new User5();
}
@Bean
public User6 user6() {
return new User6();
}
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/custom/entity/User5.java
================================================
package com.xcs.spring.custom.entity;
public class User5 {
}
================================================
FILE: spring-annotation/spring-annotation-conditional/src/main/java/com/xcs/spring/custom/entity/User6.java
================================================
package com.xcs.spring.custom.entity;
public class User6 {
}
================================================
FILE: spring-annotation/spring-annotation-configuration/README.md
================================================
## @Configuration
- [@Configuration](#configuration)
- [一、基本信息](#一基本信息)
- [二、注解描述](#二注解描述)
- [三、注解源码](#三注解源码)
- [四、主要功能](#四主要功能)
- [五、最佳实践](#五最佳实践)
- [proxyBeanMethods设置为true](#proxybeanmethods设置为true)
- [proxyBeanMethods设置为false](#proxybeanmethods设置为false)
- [六、时序图](#六时序图)
- [初始化流程](#初始化流程)
- [注册流程](#注册流程)
- [增强流程](#增强流程)
- [七、源码分析](#七源码分析)
- [初始化流程](#初始化流程-1)
- [注册流程](#注册流程-1)
- [增强流程](#增强流程-1)
- [八、注意事项](#八注意事项)
- [九、总结](#九总结)
- [最佳实践总结](#最佳实践总结)
- [源码分析总结](#源码分析总结)
- [十、常见问题](#十常见问题)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [我的CSDN](https://blog.csdn.net/duzhuang2399/article/details/132212963) 📚 **文章目录** - [所有文章](https://github.com/xuchengsheng/spring-reading) 🔗 **源码地址** - [@Configuration源码](https://github.com/xuchengsheng/spring-reading/blob/master/spring-annotation/spring-annotation-configuration/README.md)
### 二、注解描述
`@Configuration` 是 Spring 框架中提供的一个核心注解,它指示一个类声明了一个或多个 `@Bean` 定义方法,这些方法由 Spring 容器管理并执行,以便在运行时为 bean 实例化、配置和初始化对象。
### 三、注解源码
`@Configuration`注解是 Spring 框架自 3.0 版本开始引入的一个核心注解,标记一个类为 Spring 的配置类,该类可能包含一个或多个 `@Bean` 方法来定义和配置 Spring beans,其中一个`value` 属性允许用户明确指定与 `@Configuration` 类关联的 Spring bean 定义的名称。如果未指定,名称会自动生成,另外一个`proxyBeanMethods` 属性决定是否应代理 `@Bean` 方法来强制实施 bean 生命周期行为,如确保返回的是单例 bean 实例。
```java
/**
* @Configuration 是一个核心注解,用于指示该类是一个 Spring 配置类,
* 可能包含一个或多个 @Bean 定义方法。此注解与 XML 配置是等效的,但以编程方式
* 提供了更多的类型安全和灵活性。它通常与 @Bean、@ComponentScan 和其他注解结合使用,
* 为 Spring 应用程序上下文定义 beans 和配置。
*
* 通过 @Component 注解,@Configuration 被视为一个组件,
* 这意味着 Spring 的组件扫描可以自动检测和处理它。
*
* 例如,当在应用程序上下文中注册 @Configuration 类时,
* 该类中定义的 @Bean 方法将被解析,并在上下文中注册相应的 beans。
*
* @author Rod Johnson
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
* @see Bean
* @see Profile
* @see Import
* @see ImportResource
* @see ComponentScan
* @see Lazy
* @see PropertySource
* @see AnnotationConfigApplicationContext
* @see ConfigurationClassPostProcessor
* @see org.springframework.core.env.Environment
* @see org.springframework.test.context.ContextConfiguration
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
/**
* 明确指定与 @Configuration 类相关的 Spring bean 定义的名称。
* 如果未指定(这是常见情况),则会自动生成 bean 的名称。
* 这个自定义名称仅在 @Configuration 类通过组件扫描被检测到,
* 或直接提供给 AnnotationConfigApplicationContext 时才有效。
* 如果 @Configuration 类以传统的 XML bean 定义方式注册,
* 则 bean 元素的名称/ID 会优先。
*/
@AliasFor(annotation = Component.class)
String value() default "";
/**
* 指定是否应代理 @Bean 方法,以强制执行 bean 生命周期行为。
* 例如,即使在用户代码中直接调用了 @Bean 方法,也要返回共享的单例 bean 实例。
* 这个特性需要方法拦截,通过在运行时生成的 CGLIB 子类来实现,
* 这带有一些限制,如配置类和其方法不能声明为 final。
*
* 默认为 true,这允许在配置类内部通过直接方法调用进行"bean之间的引用",
* 以及从另一个配置类对此配置的 @Bean 方法的外部调用。
* 如果这不是必需的,因为此特定配置的每个 @Bean 方法都是独立的,
* 并设计为容器使用的简单工厂方法,可以将此标志设置为 false 以避免 CGLIB 子类处理。
*
* 关闭 bean 方法拦截实际上是独立处理 @Bean 方法,
* 就像在非 @Configuration 类上声明的那样,即 "@Bean 简易模式"。
* 在行为上,它等同于删除 @Configuration 注解。
*/
boolean proxyBeanMethods() default true;
}
```
### 四、主要功能
1. **Bean定义方法**
+ `@Configuration` 类中可以包含一个或多个使用 `@Bean` 注解的方法,这些方法用于创建和配置应用程序上下文中的 beans。
1. **代理支持**
+ 当 `@Configuration` 中的 `proxyBeanMethods` 属性设置为 `true`(这是默认值)时,`@Bean` 方法会被代理以确保正确的 bean 生命周期。这允许在一个配置类中,一个 `@Bean` 方法调用另一个 `@Bean` 方法并返回单例 bean 实例。
1. **组件扫描**
+ 由于 `@Configuration` 注解本身带有 `@Component` 注解,因此它可以被 Spring 的组件扫描机制自动检测。这意味着在启用组件扫描的应用程序上下文中,只需声明 `@Configuration` 类而无需明确注册。
1. **模块化和组合**
+ 通过使用 `@Import` 注解,我们可以将多个 `@Configuration` 类组合在一起,从而实现配置的模块化。此外,`@Profile` 注解可以与 `@Configuration` 一起使用,以提供基于环境或其他条件的配置。
1. **属性源和属性占位符**
+ `@Configuration` 类可以与 `@PropertySource` 注解结合使用,从而将属性文件的值导入 Spring 环境。这些值可以使用 `@Value` 注解或直接通过 `Environment` API 注入到 beans 中。
1. **与其他注解结合**:
+ `@Configuration` 类通常与其他 Spring 注解(如 `@ComponentScan`、`@PropertySource` 等)结合使用,以提供全面的配置机制。
### 五、最佳实践
#### proxyBeanMethods设置为true
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类。然后从Spring上下文中获取一个`MyConfiguration`类型的bean,最后调用了 `myBean` 方法两次,并将其结果打印到控制台。
```java
public class ConfigurationApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
MyConfiguration configuration = context.getBean(MyConfiguration.class);
System.out.println(configuration.myBean());
System.out.println(configuration.myBean());
}
}
```
`MyConfiguration` 类定义了一个名为 `myBean` 的 bean,这个 bean 的类型是 `MyBean`。每次从 Spring 容器请求这个 bean 时,都会得到同一个 `MyBean` 的实例。
```java
// proxyBeanMethods默认就是true,此处不设置
@Configuration
public class MyConfiguration {
@Bean
public MyBean myBean(){
return new MyBean();
}
}
```
`MyBean` 的简单类,它没有任何属性或方法。
```java
public class MyBean {
}
```
运行结果发现,两次对 `MyBean` 对象的引用,这两个引用具有相同的 hashcode (`@f736069`),表示它们引用的是相同的对象,因为在 `MyConfiguration` 类中,`myBean()` 方法被 `@Bean` 注解标记,它默认返回单例对象。当我们在 `ConfigurationApplication` 的 `main` 方法中两次调用 `myBean()` 方法时,Spring 容器都返回相同的 `MyBean` 实例。
```java
com.xcs.spring.bean.MyBean@f736069
com.xcs.spring.bean.MyBean@f736069
```
#### proxyBeanMethods设置为false
将 `proxyBeanMethods` 设置为 `false` 时,此代理行为被禁用。这意味着,如果我们在配置类内部多次调用同一个 `@Bean` 方法,每次都会创建一个新的实例。
```java
@Configuration(proxyBeanMethods = false)
public class MyConfiguration {
@Bean
public MyBean myBean(){
return new MyBean();
}
}
```
运行结果发现,两次对 `MyBean` 对象的引用,这两个引用具有不相同的 hashcode (`@3b69e7d1`,`@815b41f`),表示它们引用的是不相同的对象。因为我们在 `@Configuration` 注解设置了 `proxyBeanMethods = false`,并在 `ConfigurationApplication` 的 `main` 方法中两次调用 `myBean()` 方法,每次调用都会创建一个新的 `MyBean` 实例,这就是为什么我们看到两个不同的 hashcodes。
```
com.xcs.spring.bean.MyBean@3b69e7d1
com.xcs.spring.bean.MyBean@815b41f
```
### 六、时序图
时序图主要分为三个关键步骤
#### 初始化流程
- 当 `AnnotationConfigApplicationContext` 被实例化时,它开始初始化过程。
- 在这个过程中,`AnnotatedBeanDefinitionReader` 会被创建。这个读取器负责解析带注解的类,并将其转化为 Spring 可理解的 `BeanDefinition`。
- 然后,Spring 的核心工具类 `AnnotationConfigUtils` 会注册一些默认的处理器,特别是 `ConfigurationClassPostProcessor`,这是处理 `@Configuration` 注解的核心类。
~~~mermaid
sequenceDiagram
ConfigurationApplication->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext(componentClasses)
启动上下文
AnnotationConfigApplicationContext-->>ConfigurationApplication: 返回context
返回实例
AnnotationConfigApplicationContext->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext()
构造函数
AnnotationConfigApplicationContext->>AnnotatedBeanDefinitionReader: AnnotatedBeanDefinitionReader(registry)
创建读取器
AnnotatedBeanDefinitionReader-->>AnnotationConfigApplicationContext: 返回reader
AnnotatedBeanDefinitionReader-->>AnnotationConfigUtils: registerAnnotationConfigProcessors(registry)
注册处理器
AnnotationConfigUtils-->>AnnotationConfigUtils: registerAnnotationConfigProcessors(registry,source)
处理器注册
AnnotationConfigUtils-->>AnnotationConfigUtils: registerPostProcessor(registry,definition,beanName)
后置处理器
AnnotationConfigUtils-->>DefaultListableBeanFactory: registerBeanDefinition(beanName, beanDefinition)
注册Bean定义
~~~
#### 注册流程
- 使用 `AnnotationConfigApplicationContext` 的 `register` 方法,配置类(带有 `@Bean` 方法的类)会被注册到Spring上下文中。
- `AnnotatedBeanDefinitionReader` 负责解析这些配置类,并创建相应的 `BeanDefinition`。
- 这些 `BeanDefinition` 最后会在 `DefaultListableBeanFactory` 中被注册,该工厂是 Spring IOC 容器的核心部分,它管理所有的 beans 和其定义。
~~~mermaid
sequenceDiagram
ConfigurationApplication->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext(componentClasses)
启动上下文
AnnotationConfigApplicationContext-->>ConfigurationApplication: 返回context
返回上下文实例
AnnotationConfigApplicationContext->>AnnotationConfigApplicationContext: register(componentClasses)
注册组件类
AnnotationConfigApplicationContext->>AnnotatedBeanDefinitionReader: register(componentClasses)
读取器注册类
AnnotatedBeanDefinitionReader-->>AnnotatedBeanDefinitionReader: registerBean(beanClass)
注册Bean类
AnnotatedBeanDefinitionReader-->>AnnotatedBeanDefinitionReader: doRegisterBean(beanClass,name,qualifiers, supplier,customizers)
执行Bean注册
AnnotatedBeanDefinitionReader-->>BeanDefinitionReaderUtils: registerBeanDefinition(definitionHolder,registry)
注册Bean定义
BeanDefinitionReaderUtils-->>DefaultListableBeanFactory: registerBeanDefinition(beanName,beanDefinition)
工厂存Bean定义
~~~
#### 增强流程
- 当容器开始刷新(通过 `refresh` 方法),它会启动 beans 的创建和初始化过程。
- 在这个过程中,所有的 `BeanFactoryPostProcessor` 会被触发,特别是 `ConfigurationClassPostProcessor`。
- `ConfigurationClassPostProcessor` 的主要职责是增强 `@Configuration` 类,确保 `@Bean` 方法的正确代理行为。它使用 `ConfigurationClassEnhancer` 来增强类,使其能够正确地管理 bean 的生命周期,并确保,例如,单例 beans 只被创建一次。
~~~mermaid
sequenceDiagram
ConfigurationApplication->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext(componentClasses)
启动上下文
AnnotationConfigApplicationContext-->>ConfigurationApplication: 返回context
返回上下文实例
AnnotationConfigApplicationContext-->>AnnotationConfigApplicationContext: refresh
刷新容器
AnnotationConfigApplicationContext-->>AnnotationConfigApplicationContext: invokeBeanFactoryPostProcessors
触发后处理器
AnnotationConfigApplicationContext-->>PostProcessorRegistrationDelegate: invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors)
委派后处理
PostProcessorRegistrationDelegate-->>PostProcessorRegistrationDelegate: invokeBeanFactoryPostProcessors(postProcessors,beanFactory)
执行后处理
PostProcessorRegistrationDelegate-->>ConfigurationClassPostProcessor: postProcessBeanFactory(beanFactory)
处理配置类
ConfigurationClassPostProcessor-->>ConfigurationClassPostProcessor: enhanceConfigurationClasses(beanFactory)
增强配置类
ConfigurationClassPostProcessor-->>ConfigurationClassEnhancer: enhance(configClass,classLoader)
执行增强操作
ConfigurationClassEnhancer-->>ConfigurationClassEnhancer: createClass(enhancer)
创建增强类
ConfigurationClassEnhancer-->>ConfigurationClassPostProcessor: 增强后的Class类
~~~
### 七、源码分析
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类。然后从Spring上下文中获取一个`MyConfiguration`类型的bean,最后调用了 `myBean` 方法两次,并将其结果打印到控制台。
```java
public class ConfigurationApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
MyConfiguration configuration = context.getBean(MyConfiguration.class);
System.out.println(configuration.myBean());
System.out.println(configuration.myBean());
}
}
```
在`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中,执行了三个步骤。
```java
public AnnotationConfigApplicationContext(Class>... componentClasses) {
// 步骤1. 这个构造方法初始化了基本的配置读取器和类路径扫描器
this();
// 步骤2. 这个方法将这些类注册到 Spring 上下文中,这样它们可以被识别并进一步处理。
register(componentClasses);
// 步骤3. 这个方法触发整个Spring容器的启动过程
refresh();
}
```
#### 初始化流程
我们首先来到`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中步骤1。在`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`方法中,`AnnotationConfigApplicationContext` 的无参数构造函数中,初始化了 `AnnotatedBeanDefinitionReader` 和 `ClassPathBeanDefinitionScanner` 这两个核心组件。
```java
public AnnotationConfigApplicationContext() {
StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
this.reader = new AnnotatedBeanDefinitionReader(this);
createAnnotatedBeanDefReader.end();
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#AnnotatedBeanDefinitionReader`方法中,首先从注册表获取 `Environment`(配置环境的抽象)。如果没有获取到,它会创建一个新的环境,最后调用另一个构造函数来完成 `AnnotatedBeanDefinitionReader` 的实例化。
```java
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
this(registry, getOrCreateEnvironment(registry));
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#AnnotatedBeanDefinitionReader(registry,environment)`方法中,首先是设置了内部的条件评估器 (`conditionEvaluator`),条件评估器用于处理如 `@Conditional` 这样的注解。然后调用工具类 `AnnotationConfigUtils` 的 `registerAnnotationConfigProcessors` 方法来为注册表注册注解配置处理器,例如 `ConfigurationClassPostProcessor`,这是处理 `@Configuration` 注解的核心类。
```java
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
Assert.notNull(environment, "Environment must not be null");
this.registry = registry;
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
```
在`org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(registry)`方法中,该方法直接调用另一个重载版本的 `registerAnnotationConfigProcessors`,传入的 `registry` 和一个 `null` 值作为第二个参数。
```java
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
registerAnnotationConfigProcessors(registry, null);
}
```
在`org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(registry,source)`方法中,这个方法主要是确保了 `ConfigurationClassPostProcessor`被注册到指定的注册表中,从而保证了 `@Configuration` 注解及相关功能能够被正确处理。
```java
public static Set registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
// ... [代码部分省略以简化]
Set beanDefs = new LinkedHashSet<>(8);
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// ... [代码部分省略以简化]
return beanDefs;
}
```
#### 注册流程
然后我们来到`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中步骤2。在org.springframework.context.annotation.AnnotationConfigApplicationContext#register方法中,主要是允许我们注册一个或多个组件类(例如,那些使用 `@Component`, `@Service`, `@Repository`, `@Controller`, `@Configuration` 等注解的类)到Spring容器。
```java
@Override
public void register(Class>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register")
.tag("classes", () -> Arrays.toString(componentClasses));
this.reader.register(componentClasses);
registerComponentClass.end();
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#register`方法中,遍历每一个传入的组件类,并逐一调用另一个方法来完成实际的注册工作。
```java
public void register(Class>... componentClasses) {
for (Class> componentClass : componentClasses) {
registerBean(componentClass);
}
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#registerBean(beanClass)`方法中,主要目的是快速注册一个 bean 类型,而不需要指定其他详细的配置或参数。
```java
public void registerBean(Class> beanClass) {
doRegisterBean(beanClass, null, null, null, null);
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean`方法中,主要负责将给定的 bean 类型及其相关配置注册到Spring容器中。处理 bean 名称的生成、bean 定义的创建和注册,以及应用任何必要的代理模式。
```java
private void doRegisterBean(Class beanClass, @Nullable String name,
@Nullable Class extends Annotation>[] qualifiers, @Nullable Supplier supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
// ... [代码部分省略以简化]
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
// ... [代码部分省略以简化]
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
```
在`org.springframework.beans.factory.support.BeanDefinitionReaderUtils#registerBeanDefinition`方法中,主要负责bean定义及其所有相关别名都被注册到指定的 `BeanDefinitionRegistry`。这是 Spring 容器内部使用的一个实用方法,用于确保 bean 定义和其别名都正确存储,从而可以在后续的容器生命周期中被正确访问和使用。
```java
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException {
// Register bean definition under primary name.
String beanName = definitionHolder.getBeanName();
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());
// Register aliases for bean name, if any.
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
registry.registerAlias(beanName, alias);
}
}
}
```
#### 增强流程
然后我们来到`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中步骤3。在`org.springframework.context.support.AbstractApplicationContext#refresh`方法中我们重点关注一下`finishBeanFactoryInitialization(beanFactory)`这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。
```java
@Override
public void refresh() throws BeansException, IllegalStateException {
// ... [代码部分省略以简化]
// 调用在上下文中注册为bean的工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors`方法中,又委托了`PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()`进行调用。
```java
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors`方法中,主要是处理Spring容器在启动时如何处理 `BeanFactoryPostProcessor` 的核心逻辑。
```java
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) {
// ... [代码部分省略以简化]
// Now, invoke the postProcessBeanFactory callback of all processors handled so far.
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors`方法中,遍历提供的 `BeanFactoryPostProcessor` 集合,其中主要方法`postProcessBeanFactory` 是一个允许我们介入并修改 `BeanFactory` 的扩展点。在此实现中增强通过 `@Configuration` 注解定义的配置类。
```java
private static void invokeBeanFactoryPostProcessors(
Collection extends BeanFactoryPostProcessor> postProcessors, ConfigurableListableBeanFactory beanFactory) {
for (BeanFactoryPostProcessor postProcessor : postProcessors) {
StartupStep postProcessBeanFactory = beanFactory.getApplicationStartup().start("spring.context.bean-factory.post-process")
.tag("postProcessor", postProcessor::toString);
postProcessor.postProcessBeanFactory(beanFactory);
postProcessBeanFactory.end();
}
}
```
在`org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanFactory`方法中,主要目的是为 `@Configuration` 标注的类进行增强。
```java
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// ... [代码部分省略以简化]
enhanceConfigurationClasses(beanFactory);
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.annotation.ConfigurationClassPostProcessor#enhanceConfigurationClasses`方法中,主要是对标记为 `@Configuration` 的类进行增强,确保它们的 `@Bean` 方法在每次调用时都返回相同的实例(除非它们是原型作用域的)。这是通过使用 `ConfigurationClassEnhancer` 来创建代理类实现的。这种代理确保了 Spring IoC 容器的正确行为,尤其是对于配置类。
```java
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
// ... [代码部分省略以简化]
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// If a @Configuration class gets proxied, always proxy the target class
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// Set enhanced subclass of the user-specified bean class
Class> configClass = beanDef.getBeanClass();
Class> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
// ... [代码部分省略以简化]
beanDef.setBeanClass(enhancedClass);
}
}
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.annotation.ConfigurationClassEnhancer#enhance`方法中,首先是查看该类还没有被增强,则创建一个新的增强器并生成一个代理类;如果它已经被增强,那么直接返回原始类。这种增强确保了 `@Configuration` 类中的 `@Bean` 方法在每次调用时都返回相同的实例。
```java
public Class> enhance(Class> configClass, @Nullable ClassLoader classLoader) {
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
// ... [代码部分省略以简化]
return configClass;
}
Class> enhancedClass = createClass(newEnhancer(configClass, classLoader));
// ... [代码部分省略以简化]
return enhancedClass;
}
```
在`org.springframework.context.annotation.ConfigurationClassEnhancer#newEnhancer`方法中,主要负责为给定的配置类创建一个用于生成代理类的 `Enhancer` 对象。
```java
private Enhancer newEnhancer(Class> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(configSuperClass);
enhancer.setInterfaces(new Class>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
```
在`org.springframework.context.annotation.ConfigurationClassEnhancer#createClass`方法中,使用提供的 `Enhancer` 对象来创建增强后的类,并为这个类注册静态回调。
```java
private Class> createClass(Enhancer enhancer) {
Class> subclass = enhancer.createClass();
// Registering callbacks statically (as opposed to thread-local)
// is critical for usage in an OSGi environment (SPR-5932)...
Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
return subclass;
}
```
在`org.springframework.context.annotation.ConfigurationClassEnhancer#createClass`方法中,使用了一个名为 `CALLBACKS` 的静态常量数组,它包含了三个回调对象。这些回调对象在CGLIB库中用于拦截和处理增强(代理)类的方法调用。
```java
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
```
在`org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#intercept`方法中,主要是对 `@Bean` 方法的拦截逻辑,确保它们在被调用时总是返回正确的bean实例。这是通过结合检查当前方法、解析bean名称、处理特殊情况(如 `FactoryBeans` 或作用域代理)以及从bean工厂解析实际的bean引用来实现的。
```java
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
// 获取关联的 BeanFactory 通过反射读取了代理类中的$$beanFactory字段
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
// 确定当前 @Bean 方法对应的 bean 名称
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// 检查当前的 @Bean 方法是否定义了一个作用域代理
if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}
// FactoryBeans 在 Spring 中是特殊的 beans,它们不产生 bean 实例本身,而是产生其他 beans。
// 此代码块处理了当 FactoryBean 被请求时的情况,
// 确保返回的是 FactoryBean 创建的实际 bean,而不是 FactoryBean 本身。
if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
factoryContainsBean(beanFactory, beanName)) {
// 此部分代码省略,但它处理 FactoryBean 创建的 bean 的返回和增强
}
// 检查当前的方法是否是正在被工厂调用的工厂方法
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// 如果是,直接调用方法的原始实现
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
// 尝试从 bean 工厂中解析并返回 bean 的引用
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
```
在`org.springframework.context.annotation.ConfigurationClassEnhancer.BeanMethodInterceptor#resolveBeanReference`方法中,主要责任是确保能够从 `BeanFactory` 中安全、正确地获取到bean实例,并处理所有相关的边缘情况和潜在异常。
```java
private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs,
ConfigurableBeanFactory beanFactory, String beanName) {
// 判断bean是否正在创建中
boolean alreadyInCreation = beanFactory.isCurrentlyInCreation(beanName);
try {
// 如果bean正在创建中,暂时将其设置为不在创建中,以避免异常
if (alreadyInCreation) {
beanFactory.setCurrentlyInCreation(beanName, false);
}
boolean useArgs = !ObjectUtils.isEmpty(beanMethodArgs);
// 对于单例的bean,如果bean方法的参数包含null,则可能预期它们会被自动装配
if (useArgs && beanFactory.isSingleton(beanName)) {
for (Object arg : beanMethodArgs) {
if (arg == null) {
useArgs = false;
break;
}
}
}
// 根据上面的判断,从BeanFactory中获取bean实例
Object beanInstance = (useArgs ? beanFactory.getBean(beanName, beanMethodArgs) :
beanFactory.getBean(beanName));
// 检查获取的bean实例是否与@Bean方法的返回类型兼容
if (!ClassUtils.isAssignableValue(beanMethod.getReturnType(), beanInstance)) {
if (beanInstance.equals(null)) {
// 如果返回了特定的NullBean实例,进行相应的处理
// ... [日志输出代码]
beanInstance = null;
}
else {
// 抛出异常,说明有一个同名但类型不兼容的bean覆盖了当前的bean
String msg = String.format("@Bean method %s.%s ...",
// ... [代码省略以简化]
);
throw new IllegalStateException(msg);
}
}
// 如果当前正在调用另一个@Bean方法,处理其依赖关系
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
if (currentlyInvoked != null) {
String outerBeanName = BeanAnnotationHelper.determineBeanNameFor(currentlyInvoked);
beanFactory.registerDependentBean(beanName, outerBeanName);
}
return beanInstance;
}
finally {
// 清理阶段,恢复bean的创建状态
if (alreadyInCreation) {
beanFactory.setCurrentlyInCreation(beanName, true);
}
}
}
```
### 八、注意事项
1. **单例保证**:
- 在 `@Configuration` 类中,如果一个方法被标记为 `@Bean` 并被多次调用,它不会多次实例化一个 bean,而是返回同一个实例。这是因为 CGLIB 增强了 `@Configuration` 类,以确保 bean 的单例特性。
2. **proxyBeanMethods属性**:
- `@Configuration(proxyBeanMethods = false)` 会关闭 CGLIB 代理的生成。这样做可以提高性能,但可能会导致单例 bean 的引用不一致,特别是在同一个配置类中直接调用其他 `@Bean` 方法时。
3. **防止循环引用**:
- 在 `@Configuration` 类中,避免创建循环依赖。这可能会导致创建 bean 时出现问题。
4. **使用`@Profile`**:
- 可以与 `@Configuration` 一起使用 `@Profile` 注解,以根据当前环境条件决定是否加载某个配置。
5. **避免使用 `final`**:
- 由于 `@Configuration` 类是通过 CGLIB 增强的,因此它们不能是 `final` 类型,同样,它们的方法也不能声明为 `final`。
### 九、总结
#### 最佳实践总结
1. **`proxyBeanMethods = true`(默认)**:
- 当在 `@Configuration` 类中调用一个由 `@Bean` 注解的方法时,Spring 容器确保每次都返回同一个 bean 实例。这是通过 CGLIB 代理实现的,该代理拦截对该方法的所有调用并返回 bean 的单例实例。这就是为什么在 `ConfigurationApplication` 的 `main` 方法中,两次调用 `myBean()` 方法都返回具有相同 hashcode 的 `MyBean` 实例。
2. **`proxyBeanMethods = false`**:
- 在这种配置下,`@Configuration` 类中的方法不再被代理。因此,如果在配置类内部多次调用同一个 `@Bean` 方法,每次调用都会创建一个新的 bean 实例。在 `ConfigurationApplication` 的 `main` 方法中,两次调用 `myBean()` 方法会返回具有不同 hashcode 的 `MyBean` 实例,这证明了两次调用返回了两个不同的对象。
#### 源码分析总结
1. **初始化流程**:
- 当使用 `AnnotationConfigApplicationContext` 启动 Spring 应用时,会调用其构造函数,该函数执行三个主要步骤:初始化、注册和刷新。
- 初始化过程中,Spring 上下文创建 `AnnotatedBeanDefinitionReader` 和 `ClassPathBeanDefinitionScanner`。`AnnotatedBeanDefinitionReader` 负责注册通过注解定义的 beans。
- `AnnotationConfigUtils.registerAnnotationConfigProcessors` 方法确保必要的后处理器(如 `ConfigurationClassPostProcessor`)注册到 Spring 容器中,从而能够识别和处理 `@Configuration` 类。
2. **注册流程**:
- 使用 `AnnotatedBeanDefinitionReader` 将配置类(如 `MyConfiguration`)注册到 Spring 容器中。
- 针对每个 `@Bean` 方法,`BeanDefinition`(bean 定义)被创建和注册到 `BeanDefinitionRegistry` 中。
3. **增强流程**:
- 在容器的刷新过程中,`invokeBeanFactoryPostProcessors` 方法被调用,以执行所有的 `BeanFactoryPostProcessor` 实现。
- `ConfigurationClassPostProcessor` 是一个关键的后处理器,它识别配置类并进行增强。
- 对于每个标记为 `@Configuration` 的类,通过 `ConfigurationClassEnhancer` 创建一个 CGLIB 代理类。
- 这个代理确保对配置类中的 `@Bean` 方法的每次调用都返回同一个 bean 实例,除非它是原型作用域的。
- 当应用上下文启动完成后,对于任何请求的 bean,代理的 `@Bean` 方法会从 Spring 容器中返回已存在的 bean 实例,而不是重新创建一个新的实例。
### 十、常见问题
1. **@Configuration中full模式与lite模式如何选择?**
`@Configuration` 注解有两种模式:`full` 和 `lite`。它们在功能和性能上有所不同。了解它们的优缺点有助于为特定的场景做出合适的选择。
+ Full 模式
- 启用方式:在 `@Configuration` 注解中不设置 `proxyBeanMethods` 或将其设置为 `true`。
- 功能:当在配置类中的 `@Bean` 方法内部调用另一个 `@Bean` 方法时,Spring 会确保返回的是容器中的单例bean,而不是一个新的实例。这是通过CGLIB代理实现的。
- 优势:保持单例语义,确保容器中的单例Bean在配置类中的调用中始终是单例的。
- 劣势:需要通过CGLIB创建配置类的子类,可能带来一些性能开销,增加了启动时间,可能与某些库不兼容,这些库期望操作实际类而不是其CGLIB代理。
+ +Lite 模式
- 启用方式:在 `@Configuration` 注解中设置 `proxyBeanMethods` 为 `false`。
- 功能:禁用CGLIB代理。`@Bean` 方法之间的调用就像普通的Java方法调用,每次都会创建一个新的实例。
- 优势:更快的启动时间,因为不需要通过CGLIB增强配置类,对于简单的注入,这种模式可能更为简洁和直接。
- 劣势:不保持单例语义。如果在一个 `@Bean` 方法内部调用另一个 `@Bean` 方法,会创建一个新的bean实例。
+ 如何选择
- 如果我们的配置中需要确保在配置类中调用的bean始终是Spring容器中的单例bean,选择full模式。
- 如果我们的配置类只是简单地定义beans并注入依赖,且不需要在配置类方法之间共享单例实例,选择lite模式。
- 如果我们关心应用的启动性能,特别是在云环境或微服务中,使用lite模式可能更合适,因为它避免了额外的CGLIB处理。
最终,根据项目的具体需求和场景选择合适的模式。如果没有特殊的单例需求,推荐使用lite模式,因为它更简单且启动性能更好。
================================================
FILE: spring-annotation/spring-annotation-configuration/pom.xml
================================================
spring-annotation
com.xcs.spring
0.0.1-SNAPSHOT
4.0.0
spring-annotation-configuration
================================================
FILE: spring-annotation/spring-annotation-configuration/src/main/java/com/xcs/spring/ConfigurationApplication.java
================================================
package com.xcs.spring;
import com.xcs.spring.config.MyConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 2023年08月07日 16时21分
**/
public class ConfigurationApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
MyConfiguration configuration = context.getBean(MyConfiguration.class);
System.out.println(configuration.myBean());
System.out.println(configuration.myBean());
}
}
================================================
FILE: spring-annotation/spring-annotation-configuration/src/main/java/com/xcs/spring/bean/MyBean.java
================================================
package com.xcs.spring.bean;
/**
* @author xcs
* @date 2023年08月07日 16时26分
**/
public class MyBean {
}
================================================
FILE: spring-annotation/spring-annotation-configuration/src/main/java/com/xcs/spring/config/MyConfiguration.java
================================================
package com.xcs.spring.config;
import com.xcs.spring.bean.MyBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author xcs
* @date 2023年08月07日 16时25分
**/
@Configuration
public class MyConfiguration {
@Bean
public MyBean myBean(){
return new MyBean();
}
}
================================================
FILE: spring-annotation/spring-annotation-dependsOn/README.md
================================================
## @DependsOn
- [@DependsOn](#dependson)
- [一、基本信息](#一基本信息)
- [二、注解描述](#二注解描述)
- [三、注解源码](#三注解源码)
- [四、主要功能](#四主要功能)
- [五、最佳实践](#五最佳实践)
- [六、时序图](#六时序图)
- [Bean注册时序图](#bean注册时序图)
- [Bean创建时序图](#bean创建时序图)
- [Bean销毁时序图](#bean销毁时序图)
- [六、源码分析](#六源码分析)
- [Bean注册源码分析](#bean注册源码分析)
- [Bean创建源码分析](#bean创建源码分析)
- [Bean销毁源码分析](#bean销毁源码分析)
- [七、注意事项](#七注意事项)
- [八、总结](#八总结)
- [最佳实践总结](#最佳实践总结)
- [源码分析总结](#源码分析总结)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [我的CSDN](https://blog.csdn.net/duzhuang2399/article/details/133800615) 📚 **文章目录** - [所有文章](https://github.com/xuchengsheng/spring-reading) 🔗 **源码地址** - [@DependsOn源码](https://github.com/xuchengsheng/spring-reading/tree/master/spring-annotation/spring-annotation-dependsOn)
### 二、注解描述
`@DependsOn`注解,用于定义 Bean 初始化顺序。有时,我们可能会碰到某些 Bean 需要在其他 Bean 之前被初始化的情况。在这种情况下,我们可以使用 `@DependsOn` 注解来明确指定 Bean 的初始化顺序。
### 三、注解源码
`@DependsOn`注解是 Spring 框架自 3.0 版本开始引入的一个核心注解,其中`value`属性是 `@DependsOn` 注解的主要属性,它允许我们定义当前bean依赖的其他bean的名称。
```java
/**
* 当前bean所依赖的其他bean。任何指定的bean都保证在这个bean之前被容器创建。
* 在少数情况下使用,当一个bean不通过属性或构造函数参数明确地依赖于另一个bean,
* 而是依赖于另一个bean的初始化的副作用时。
*
* depends-on 声明既可以指定初始化时的依赖,又可以在单例bean的情况下,指定对应的销毁时的依赖。
* 定义了 depends-on 关系的依赖bean会首先被销毁,然后再销毁给定的bean。
* 因此,depends-on 声明也可以控制关闭顺序。
*
* 可以在直接或间接使用 org.springframework.stereotype.Component 注解的任何类上,
* 或在使用 Bean 注解的方法上使用。
*
* 在类级别使用 DependsOn 在未使用组件扫描的情况下不会产生任何效果。
* 如果通过XML声明了使用 DependsOn 注解的类,DependsOn 注解的元数据会被忽略,
* 而 会被考虑。
*
* @author Juergen Hoeller
* @since 3.0
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DependsOn {
// 定义当前bean所依赖的其他bean的名称。
String[] value() default {};
}
```
### 四、主要功能
1. **初始化顺序**
+ 使用 `@DependsOn` 可以确保某个或某些 bean 在当前 bean 之前被初始化。这在某个 bean 的初始化逻辑依赖于另一个 bean 的副作用时特别有用。
2. **销毁顺序(仅限单例 bean)**
+ 除了影响初始化顺序,`@DependsOn` 也会影响单例 bean 的销毁顺序。依赖关系中的 bean 会在它们所依赖的 bean 之前被销毁。
3. **指定多个依赖**
+ `@DependsOn` 允许我们指定多个依赖,这意味着我们可以确保多个 bean 都在当前 bean 之前被初始化。
### 五、最佳实践
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,最后调用`context.close()`方法关闭容器。
```java
public class DependsOnApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
context.close();
}
}
```
这里使用`@Bean`注解,定义了三个Bean,是为了确保`BeanA`,`BeanB`,`BeanC`被 Spring 容器执行,其中`BeanA`依赖于 `BeanB`,`BeanB`依赖于 `BeanC`,`BeanC`没有明确的依赖关系。
```java
@Configuration
public class MyConfiguration {
@Bean
@DependsOn("beanB")
public BeanA beanA() {
return new BeanA();
}
@Bean
@DependsOn("beanC")
public BeanB beanB() {
return new BeanB();
}
@Bean
public BeanC beanC() {
return new BeanC();
}
}
```
`BeanA`, `BeanB`, 和 `BeanC`,每一个都有各种的构造函数与实现 `DisposableBean` 接口,
```java
public class BeanA implements DisposableBean {
public BeanA() {
System.out.println("BeanA Initialized");
}
@Override
public void destroy() throws Exception {
System.out.println("BeanA Destroyed");
}
}
public class BeanB implements DisposableBean {
public BeanB() {
System.out.println("BeanB Initialized");
}
@Override
public void destroy() throws Exception {
System.out.println("BeanB Destroyed");
}
}
public class BeanC implements DisposableBean {
public BeanC() {
System.out.println("BeanC Initialized");
}
@Override
public void destroy() throws Exception {
System.out.println("BeanC Destroyed");
}
}
```
运行结果发现,通过 `@DependsOn` 注解和 `DisposableBean` 接口的 `destroy()` 方法,我们不仅可以控制 bean 的初始化顺序,还可以控制它们的销毁顺序。
```assembly
BeanC Initialized
BeanB Initialized
BeanA Initialized
BeanA Destroyed
BeanB Destroyed
BeanC Destroyed
PS
初始化的顺序为:
1. `BeanC` (`BeanC Initialized` 将会被打印)
2. `BeanB` (`BeanB Initialized` 将会被打印)
3. `BeanA` (`BeanA Initialized` 将会被打印)
当关闭 Spring 容器时,销毁的顺序是与初始化的顺序相反:
1. `BeanA` (`BeanA Destroyed` 将会被打印)
2. `BeanB` (`BeanB Destroyed` 将会被打印)
3. `BeanC` (`BeanC Destroyed` 将会被打印)
```
### 六、时序图
#### Bean注册时序图
~~~mermaid
sequenceDiagram
DependsOnApplication->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext(componentClasses)
启动上下文
AnnotationConfigApplicationContext-->>ConfigurationApplication: 返回context
返回上下文实例
AnnotationConfigApplicationContext->>AnnotationConfigApplicationContext: register(componentClasses)
注册组件类
AnnotationConfigApplicationContext->>AnnotatedBeanDefinitionReader: register(componentClasses)
读取器注册类
AnnotatedBeanDefinitionReader-->>AnnotatedBeanDefinitionReader: registerBean(beanClass)
注册Bean类
AnnotatedBeanDefinitionReader-->>AnnotatedBeanDefinitionReader: doRegisterBean(beanClass,name,qualifiers, supplier,customizers)
执行Bean注册
AnnotatedBeanDefinitionReader->>AnnotationConfigUtils:processCommonDefinitionAnnotations(abd)
处理通用定义注解
AnnotationConfigUtils-->>AnnotationConfigUtils:processCommonDefinitionAnnotations(abd,metadata)
解析DependsOn注解并存储在BeanDefinition中
AnnotatedBeanDefinitionReader->>BeanDefinitionReaderUtils: registerBeanDefinition(definitionHolder,registry)
注册Bean定义
BeanDefinitionReaderUtils->>DefaultListableBeanFactory: registerBeanDefinition(beanName,beanDefinition)
工厂存Bean定义
~~~
#### Bean创建时序图
~~~mermaid
sequenceDiagram
DependsOnApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)
创建应用上下文
AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()
刷新应用上下文
AbstractApplicationContext->>AbstractApplicationContext:finishBeanFactoryInitialization(beanFactory)
完成bean工厂初始化
AbstractApplicationContext->>DefaultListableBeanFactory:preInstantiateSingletons()
预实例化单例beans
DefaultListableBeanFactory->>+AbstractBeanFactory:getBean(name)
获取bean实例
AbstractBeanFactory->>AbstractBeanFactory:doGetBean(name,requiredType,args,typeCheckOnly)
具体获取bean逻辑
AbstractBeanFactory->>AbstractBeanDefinition:获取bean所依赖的bean名称
AbstractBeanDefinition->>AbstractBeanFactory:返回被依赖的bean名称
AbstractBeanFactory->>DefaultSingletonBeanRegistry:isDependent(beanName,dependentBeanName)
检查依赖关系
DefaultSingletonBeanRegistry->>DefaultSingletonBeanRegistry:isDependent(beanName,dependentBeanName,alreadySeen)
检查依赖关系
DefaultSingletonBeanRegistry->>AbstractBeanFactory:返回是否存在依赖 true or false
AbstractBeanFactory->>-AbstractBeanFactory:getBean(name)
获取被依赖bean实例(递归)
~~~
#### Bean销毁时序图
~~~mermaid
sequenceDiagram
DisposableBeanApplication->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext(componentClasses )
应用开始初始化上下文
AnnotationConfigApplicationContext-->>DisposableBeanApplication:初始化完成
DisposableBeanApplication->>AbstractApplicationContext:close()
请求关闭上下文
AbstractApplicationContext->>AbstractApplicationContext:doClose()
执行关闭逻辑
AbstractApplicationContext->>AbstractApplicationContext:destroyBeans()
开始销毁beans
AbstractApplicationContext->>DefaultListableBeanFactory:destroySingletons()
销毁单例beans
DefaultListableBeanFactory->>DefaultSingletonBeanRegistry:super.destroySingletons()
调父类销毁方法
DefaultSingletonBeanRegistry-->>DefaultListableBeanFactory:destroySingleton(beanName)
销毁单个bean
DefaultListableBeanFactory->>DefaultSingletonBeanRegistry:super.destroySingleton(beanName)
调父类销毁bean方法
DefaultSingletonBeanRegistry->>DefaultSingletonBeanRegistry:destroyBean(beanName,bean)
执行销毁bean操作
DefaultSingletonBeanRegistry->>DefaultSingletonBeanRegistry:删除被依赖映射关系(dependentBeanMap)
DefaultSingletonBeanRegistry->>DefaultSingletonBeanRegistry:删除依赖映射关系(dependenciesForBeanMap)
~~~
### 六、源码分析
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,最后调用`context.close()`方法关闭容器。
```java
public class DependsOnApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
context.close();
}
}
```
在`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中,执行了三个步骤。
```java
public AnnotationConfigApplicationContext(Class>... componentClasses) {
// 步骤1. 这个构造方法初始化了基本的配置读取器和类路径扫描器
this();
// 步骤2. 这个方法将这些类注册到 Spring 上下文中,这样它们可以被识别并进一步处理。
register(componentClasses);
// 步骤3. 这个方法触发整个Spring容器的启动过程
refresh();
}
```
#### Bean注册源码分析
首先我们来到`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中步骤2。在`org.springframework.context.annotation.AnnotationConfigApplicationContext#register`方法中,主要是允许我们注册一个或多个组件类(例如,那些使用 `@Component`, `@Service`, `@Repository`, `@Controller`, `@Configuration` 等注解的类)到Spring容器。
```java
@Override
public void register(Class>... componentClasses) {
Assert.notEmpty(componentClasses, "At least one component class must be specified");
StartupStep registerComponentClass = this.getApplicationStartup().start("spring.context.component-classes.register")
.tag("classes", () -> Arrays.toString(componentClasses));
this.reader.register(componentClasses);
registerComponentClass.end();
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#register`方法中,遍历每一个传入的组件类,并逐一调用另一个方法来完成实际的注册工作。
```java
public void register(Class>... componentClasses) {
for (Class> componentClass : componentClasses) {
registerBean(componentClass);
}
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#registerBean(beanClass)`方法中,主要目的是快速注册一个 bean 类型,而不需要指定其他详细的配置或参数。
```java
public void registerBean(Class> beanClass) {
doRegisterBean(beanClass, null, null, null, null);
}
```
在`org.springframework.context.annotation.AnnotatedBeanDefinitionReader#doRegisterBean`方法中,主要是处理 bean 定义上的`@DependsOn` 注解。
```java
private void doRegisterBean(Class beanClass, @Nullable String name,
@Nullable Class extends Annotation>[] qualifiers, @Nullable Supplier supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
// ... [代码部分省略以简化]
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.annotation.AnnotationConfigUtils#processCommonDefinitionAnnotations`方法中,主要目的是将具体的注解处理逻辑委托给另一个方法,以此来进行真正的配置工作。
```java
public static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd) {
processCommonDefinitionAnnotations(abd, abd.getMetadata());
}
```
在`org.springframework.context.annotation.AnnotationConfigUtils#processCommonDefinitionAnnotations(abd,metadata)`方法中,使用 `attributesFor(metadata, DependsOn.class)` 方法从 `metadata` 中获取 `@DependsOn` 注解的属性。这可能会返回 `AnnotationAttributes` 对象,这个对象提供了方便的方法来访问注解的属性值。如果找到了 `@DependsOn` 注解(即 `dependsOn` 不为 `null`),则从该注解中获取 `value` 属性。这个属性是一个字符串数组,代表了其他 Bean 的名称,当前 Bean 依赖于这些名称。使用 `abd.setDependsOn()` 方法设置这个 Bean 依赖的其他 Bean 名称。
```java
static void processCommonDefinitionAnnotations(AnnotatedBeanDefinition abd, AnnotatedTypeMetadata metadata) {
// ... [代码部分省略以简化]
AnnotationAttributes dependsOn = attributesFor(metadata, DependsOn.class);
if (dependsOn != null) {
abd.setDependsOn(dependsOn.getStringArray("value"));
}
// ... [代码部分省略以简化]
}
```
#### Bean创建源码分析
然后我们来到`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中步骤3。在`org.springframework.context.support.AbstractApplicationContext#refresh`方法中,我们重点关注一下`finishBeanFactoryInitialization(beanFactory)`这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。
```java
@Override
public void refresh() throws BeansException, IllegalStateException {
// ... [代码部分省略以简化]
// 实例化所有剩余非懒加载的单列Bean对象
finishBeanFactoryInitialization(beanFactory);
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization`方法中,会继续调用`DefaultListableBeanFactory`类中的`preInstantiateSingletons`方法来完成所有剩余非懒加载的单列Bean对象。
```java
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// ... [代码部分省略以简化]
// 完成所有剩余非懒加载的单列Bean对象。
beanFactory.preInstantiateSingletons();
}
```
在`org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons`方法中,主要的核心目的是预先实例化所有非懒加载的单例bean。在Spring的上下文初始化完成后,该方法会被触发,以确保所有单例bean都被正确地创建并初始化。其中`getBean(beanName)`是此方法的核心操作。对于容器中定义的每一个单例bean,它都会调用`getBean`方法,这将触发bean的实例化、初始化及其依赖的注入。如果bean之前没有被创建过,那么这个调用会导致其被实例化和初始化。
```java
public void preInstantiateSingletons() throws BeansException {
// ... [代码部分省略以简化]
// 循环遍历所有bean的名称
for (String beanName : beanNames) {
getBean(beanName);
}
// ... [代码部分省略以简化]
}
```
在`org.springframework.beans.factory.support.AbstractBeanFactory#getBean()`方法中,又调用了`doGetBean`方法来实际执行创建Bean的过程,传递给它bean的名称和一些其他默认的参数值。此处,`doGetBean`负责大部分工作,如查找bean定义、创建bean(如果尚未创建)、处理依赖关系等。
```java
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
```
在`org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean`方法中,从 `BeanDefinition` 中,首先提取由 `@DependsOn` 注解定义的依赖关系,并将这些依赖存储在 `dependsOn` 字符串数组中。接着,系统会遍历当前 bean 的每一个依赖。通过使用 `isDependent(beanName, dep)` 方法,Spring 检查是否存在循环依赖。如果发现当前 bean 同时也是 `dep` bean 的依赖,那么 Spring 将抛出 `BeanCreationException`,因为这明确地表示了一个循环依赖。接着,系统使用 `registerDependentBean(dep, beanName)` 方法来通知 Spring 容器,表示 `beanName` 依赖于其他的 bean。最后,通过 `getBean(dep)` 方法,系统会尝试初始化并获取该依赖 bean 的实例。
```java
protected T doGetBean(
String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// ... [代码部分省略以简化]
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
checkMergedBeanDefinition(mbd, beanName, args);
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
for (String dep : dependsOn) {
if (isDependent(beanName, dep)) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
}
registerDependentBean(dep, beanName);
try {
getBean(dep);
}
catch (NoSuchBeanDefinitionException ex) {
throw new BeanCreationException(mbd.getResourceDescription(), beanName,
"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
}
}
}
// ... [代码部分省略以简化]
}
```
在`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isDependent`方法中,用于检查一个 bean 是否直接或间接地依赖于另一个 bean。
```java
protected boolean isDependent(String beanName, String dependentBeanName) {
synchronized (this.dependentBeanMap) {
return isDependent(beanName, dependentBeanName, null);
}
}
```
在`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#isDependent(beanName, dependentBeanName, alreadySeen)`方法中,用于递归地检查一个 bean 是否依赖于另一个 bean。它通过跟踪直接和间接的依赖关系来实现这一点。
```java
private boolean isDependent(String beanName, String dependentBeanName, @Nullable Set alreadySeen) {
// 检查bean是否已在已处理的bean集合中,如果是,则返回false,以避免无限递归
if (alreadySeen != null && alreadySeen.contains(beanName)) {
return false;
}
// 获取bean的规范名称(可能涉及转换或别名解析)
String canonicalName = canonicalName(beanName);
// 从依赖映射中获取bean的直接依赖
Set dependentBeans = this.dependentBeanMap.get(canonicalName);
// 如果bean没有任何直接依赖,则返回false
if (dependentBeans == null) {
return false;
}
// 如果bean的直接依赖包含目标依赖bean,则返回true
if (dependentBeans.contains(dependentBeanName)) {
return true;
}
// 递归检查bean的每一个直接依赖,看它们是否间接依赖于目标依赖bean
for (String transitiveDependency : dependentBeans) {
// 如果还没有创建已处理的bean集合,那么创建它
if (alreadySeen == null) {
alreadySeen = new HashSet<>();
}
// 将当前bean添加到已处理的bean集合中
alreadySeen.add(beanName);
// 递归检查
if (isDependent(transitiveDependency, dependentBeanName, alreadySeen)) {
return true;
}
}
// 如果上述所有检查都未确认存在依赖关系,则返回false
return false;
}
```
在`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#registerDependentBean`方法中,记录了两个 beans 之间的依赖关系,并确保了这种关系是双向的,即 A 依赖于 B,同时 B 被 A 依赖。这在解析和管理 bean 之间的复杂依赖关系时非常有用。
```java
public void registerDependentBean(String beanName, String dependentBeanName) {
// 获取bean的规范名称(可能涉及转换或别名解析)
String canonicalName = canonicalName(beanName);
// 同步代码块,确保线程安全地更新bean的依赖映射
synchronized (this.dependentBeanMap) {
// 获取或创建bean的依赖集合
Set dependentBeans =
this.dependentBeanMap.computeIfAbsent(canonicalName, k -> new LinkedHashSet<>(8));
// 如果依赖bean名尚未添加,则添加;否则直接返回
if (!dependentBeans.add(dependentBeanName)) {
return;
}
}
// 同步代码块,确保线程安全地更新bean的被依赖映射(反向依赖)
synchronized (this.dependenciesForBeanMap) {
// 获取或创建被依赖bean的反向依赖集合
Set dependenciesForBean =
this.dependenciesForBeanMap.computeIfAbsent(dependentBeanName, k -> new LinkedHashSet<>(8));
// 添加反向依赖信息
dependenciesForBean.add(canonicalName);
}
}
```
#### Bean销毁源码分析
在`org.springframework.context.support.AbstractApplicationContext#close`方法中,首先是启动了一个同步块,它同步在 `startupShutdownMonitor` 对象上。这确保了在给定时刻只有一个线程可以执行这个块内的代码,防止多线程导致的资源竞争或数据不一致,然后是调用了 `doClose` 方法,最后是为 JVM 注册了一个关闭钩子。
```java
@Override
public void close() {
synchronized (this.startupShutdownMonitor) {
doClose();
// ... [代码部分省略以简化]
}
}
```
在`org.springframework.context.support.AbstractApplicationContext#doClose`方法中,又调用了 `destroyBeans` 方法。
```java
protected void doClose() {
// ... [代码部分省略以简化]
// Destroy all cached singletons in the context's BeanFactory.
destroyBeans();
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.AbstractApplicationContext#destroyBeans`方法中,首先是调用了`getBeanFactory()`返回 Spring 的 `BeanFactory` ,然后在获得的 `BeanFactory` 上,调用了 `destroySingletons` 方法,这个方法的目的是销毁所有在 `BeanFactory` 中缓存的单例 beans。
```java
protected void destroyBeans() {
getBeanFactory().destroySingletons();
}
```
在`org.springframework.beans.factory.support.DefaultListableBeanFactory#destroySingletons`方法中,首先是调用了父类的 `destroySingletons` 方法,为了确保继承自父类的销毁逻辑得到了执行。
```java
@Override
public void destroySingletons() {
super.destroySingletons();
// ... [代码部分省略以简化]
}
```
在`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroySingletons`方法中,首先是在`disposableBeans` 字段上,从其键集合中获取所有的 bean 名称,并将它们转换为一个字符串数组。`disposableBeans` 可能包含了实现了 `DisposableBean` 接口的 beans,这些 beans 需要在容器销毁时特殊处理,最后倒序循环,从最后一个开始,销毁所有在 `disposableBeans` 列表中的 beans。这样做是为了确保依赖关系正确地处理,beans先被创建的应该后被销毁。
```java
public void destroySingletons() {
// ... [代码部分省略以简化]
String[] disposableBeanNames;
synchronized (this.disposableBeans) {
disposableBeanNames = StringUtils.toStringArray(this.disposableBeans.keySet());
}
for (int i = disposableBeanNames.length - 1; i >= 0; i--) {
destroySingleton(disposableBeanNames[i]);
}
// ... [代码部分省略以简化]
}
```
在`org.springframework.beans.factory.support.DefaultListableBeanFactory#destroySingleton`方法中,首先是调用了父类的 `destroySingleton` 方法,为了确保继承自父类的销毁逻辑得到了执行。
```java
@Override
public void destroySingleton(String beanName) {
super.destroySingleton(beanName);
// ... [代码部分省略以简化]
}
```
在`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroySingleton`方法中,首先是使用 `synchronized` 关键字在 `disposableBeans` 对象上进行同步,以确保在多线程环境中安全地访问和修改它,从 `disposableBeans` 集合中移除指定名称的 bean,并将其转换为 `DisposableBean` 类型,最后调用`destroyBean`方法执行实际的销毁操作。
```java
public void destroySingleton(String beanName) {
// ... [代码部分省略以简化]
destroyBean(beanName, disposableBean);
}
```
在`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#destroyBean`方法中, bean 从所有依赖它的其他 beans 的依赖列表中被移除,并且这个 bean 的所有已准备的依赖信息也被删除。
```java
protected void destroyBean(String beanName, @Nullable DisposableBean bean) {
// ... [代码部分省略以简化]
// 从其他beans的依赖中移除已销毁的bean
synchronized (this.dependentBeanMap) {
// 遍历存储依赖关系的map
for (Iterator>> it = this.dependentBeanMap.entrySet().iterator(); it.hasNext();) {
Map.Entry> entry = it.next();
// 获取当前bean的依赖列表
Set dependenciesToClean = entry.getValue();
// 从依赖列表中移除已销毁的bean
dependenciesToClean.remove(beanName);
// 如果当前bean的依赖列表为空,则从map中移除该条目
if (dependenciesToClean.isEmpty()) {
it.remove();
}
}
}
// 移除已销毁bean的已准备的依赖信息
this.dependenciesForBeanMap.remove(beanName);
}
```
### 七、注意事项
1. **避免循环依赖**
+ 确保不创建一个循环依赖的场景,即 Bean A 依赖 Bean B,同时 Bean B 又依赖 Bean A。这会导致 Spring 容器无法成功初始化这两个 beans。
2. **不要过度使用**
+ 只在真正需要控制初始化顺序时使用这个注解。过度使用可能使代码更难理解和维护。
3. **与构造器/属性注入结合使用**
+ 即使我们使用了 `@DependsOn`,如果一个 bean 需要另一个 bean 作为构造函数参数或属性,我们还是应该使用 `@Autowired` 或 XML 配置进行注入。
4. **销毁顺序**
+ `@DependsOn` 也会影响 beans 的销毁顺序。如果 Bean A 依赖于 Bean B,那么在销毁时,Bean A 会在 Bean B 之后被销毁。
5. **不是为运行时依赖**
+ 请注意,`@DependsOn` 只确保初始化顺序。如果我们的 bean 在运行时需要另一个 bean,那么我们应该考虑其他方法,如注入。
6. **与 `@Lazy` 结合使用**
+ 如果我们的 bean 使用了 `@Lazy` 注解(表示它会延迟初始化),同时又用 `@DependsOn` 指定了依赖,那么这可能会导致意外的初始化顺序,因为延迟初始化的 bean 可能不会按预期的顺序被初始化。
7. **组件扫描与显式声明**
+ 如果我们使用组件扫描(通过 `@ComponentScan`)并且在类级别使用了 `@DependsOn`,那么这个注解会生效。但如果通过 XML 定义了该 bean,并且还在类上使用了 `@DependsOn`,那么注解会被忽略,我们应该使用 XML 的 `depends-on` 属性来声明依赖。
8. **不适用于 `@Bean` 方法的参数**
+ 如果我们在 Java 配置类中使用 `@Bean` 方法定义 beans,并尝试通过方法参数注入依赖,那么 `@DependsOn` 不会对这些依赖产生影响,因为方法参数自然地声明了初始化顺序。
### 八、总结
#### 最佳实践总结
1. **启动类设置**
+ 我们使用 `AnnotationConfigApplicationContext` 来启动 Spring 容器,并指定了 `MyConfiguration` 作为配置类。当程序运行完毕,我们关闭了该容器。
2. **配置类与依赖声明**
+ 在 `MyConfiguration` 配置类中,我们使用 `@Bean` 注解定义了三个 bean:`BeanA`, `BeanB`, 和 `BeanC`。通过 `@DependsOn` 注解,我们明确地指定了它们之间的依赖关系,确保 `BeanA` 依赖于 `BeanB` 的初始化,而 `BeanB` 依赖于 `BeanC` 的初始化。
3. **Bean的声明与销毁逻辑**
+ 每个 bean 都实现了 `DisposableBean` 接口。在各自的构造函数中,它们打印一个消息表示它们已经被初始化,而在 `destroy` 方法中,它们打印一个消息表示它们已经被销毁。
4. **结果与结论**
+ 我们运行程序时,初始化的顺序遵循了我们通过 `@DependsOn` 注解定义的依赖关系。同样地,销毁的顺序与初始化顺序相反,这确保了所有的依赖都在被依赖的 bean 之前被销毁。
#### 源码分析总结
1. **启动和注册**
+ 使用 `AnnotationConfigApplicationContext` 启动 Spring 容器,并将配置类注册到 Spring 上下文中。
2. **Bean 注册分析**
- 在 `AnnotationConfigApplicationContext` 构造函数中,执行了注册和启动容器的两个关键步骤。
- `register` 方法允许我们将组件类(如使用 `@Component` 或 `@Configuration` 注解的类)注册到 Spring 容器。
- `AnnotatedBeanDefinitionReader` 负责注册这些类,然后在 `doRegisterBean` 方法中为给定的 `beanClass` 创建一个 bean 定义并配置它。
3. **处理 @DependsOn 注解**
- 在 bean 的定义过程中,Spring 将解析 `@DependsOn` 注解并存储其依赖关系。
- 这些关系将在后面的 bean 生命周期中使用,以确保按正确的顺序创建和销毁 beans。
4. **Bean 创建分析**
- 在容器启动过程的 `refresh` 方法中,会实例化所有的单例 beans。
- `preInstantiateSingletons` 方法会触发所有非懒加载单例 beans 的创建过程。
- 如果一个 bean 通过 `@DependsOn` 指定了依赖,这些依赖会首先被初始化。
5. **Bean 销毁分析**
- 在容器关闭时,会调用 `destroySingletons` 方法来销毁所有缓存的单例 beans。
- Beans 的销毁顺序与其创建顺序相反,以确保所有依赖项在销毁过程中得到正确的处理。
6. **处理循环依赖**
- Spring 会检查 `@DependsOn` 指定的依赖是否导致了循环依赖,如果是这种情况,Spring 会抛出异常。
================================================
FILE: spring-annotation/spring-annotation-dependsOn/pom.xml
================================================
spring-annotation
com.xcs.spring
0.0.1-SNAPSHOT
4.0.0
spring-annotation-dependsOn
================================================
FILE: spring-annotation/spring-annotation-dependsOn/src/main/java/com/xcs/spring/DependsOnApplication.java
================================================
package com.xcs.spring;
import com.xcs.spring.config.MyConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 2023年08月07日 16时21分
**/
public class DependsOnApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
context.close();
}
}
================================================
FILE: spring-annotation/spring-annotation-dependsOn/src/main/java/com/xcs/spring/bean/BeanA.java
================================================
package com.xcs.spring.bean;
import org.springframework.beans.factory.DisposableBean;
/**
* @author xcs
* @date 2023年10月09日 16时45分
**/
public class BeanA implements DisposableBean {
public BeanA() {
System.out.println("BeanA Initialized");
}
@Override
public void destroy() throws Exception {
System.out.println("BeanA Destroyed");
}
}
================================================
FILE: spring-annotation/spring-annotation-dependsOn/src/main/java/com/xcs/spring/bean/BeanB.java
================================================
package com.xcs.spring.bean;
import org.springframework.beans.factory.DisposableBean;
/**
* @author xcs
* @date 2023年10月09日 16时46分
**/
public class BeanB implements DisposableBean {
public BeanB() {
System.out.println("BeanB Initialized");
}
@Override
public void destroy() throws Exception {
System.out.println("BeanB Destroyed");
}
}
================================================
FILE: spring-annotation/spring-annotation-dependsOn/src/main/java/com/xcs/spring/bean/BeanC.java
================================================
package com.xcs.spring.bean;
import org.springframework.beans.factory.DisposableBean;
/**
* @author xcs
* @date 2023年10月09日 16时46分
**/
public class BeanC implements DisposableBean {
public BeanC() {
System.out.println("BeanC Initialized");
}
@Override
public void destroy() throws Exception {
System.out.println("BeanC Destroyed");
}
}
================================================
FILE: spring-annotation/spring-annotation-dependsOn/src/main/java/com/xcs/spring/config/MyConfiguration.java
================================================
package com.xcs.spring.config;
import com.xcs.spring.bean.BeanA;
import com.xcs.spring.bean.BeanB;
import com.xcs.spring.bean.BeanC;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
/**
* @author xcs
* @date 2023年08月07日 16时25分
**/
@Configuration
public class MyConfiguration {
@Bean
@DependsOn("beanB")
public BeanA beanA() {
return new BeanA();
}
@Bean
@DependsOn("beanC")
public BeanB beanB() {
return new BeanB();
}
@Bean
public BeanC beanC() {
return new BeanC();
}
}
================================================
FILE: spring-annotation/spring-annotation-import/README.md
================================================
## @Import
- [@Import](#import)
- [一、基本信息](#一基本信息)
- [二、注解描述](#二注解描述)
- [三、注解源码](#三注解源码)
- [四、主要功能](#四主要功能)
- [五、最佳实践](#五最佳实践)
- [六、时序图](#六时序图)
- [七、源码分析](#七源码分析)
- [八、注意事项](#八注意事项)
- [九、总结](#九总结)
- [最佳实践总结](#最佳实践总结)
- [源码分析总结](#源码分析总结)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [我的CSDN](https://blog.csdn.net/duzhuang2399/article/details/132806548) 📚 **文章目录** - [所有文章](https://github.com/xuchengsheng/spring-reading) 🔗 **源码地址** - [@Import源码](https://github.com/xuchengsheng/spring-reading/tree/master/spring-annotation/spring-annotation-import)
### 二、注解描述
`@Import` 是 Spring 框架的核心注解,用于导入配置类或组件到当前的 Spring 上下文中。它可以用于导入常规的 `@Configuration` 类、常规组件类,或实现了 `ImportSelector` 和 `ImportBeanDefinitionRegistrar` 接口的类。`ImportSelector` 允许根据条件动态地选择要导入的组件,而 `ImportBeanDefinitionRegistrar` 提供了一种以编程方式注册bean的方法。使用 `@Import` 注解,我们可以更灵活、模块化地组织 Spring 的配置,确保上下文中有所需的所有组件和配置。
### 三、注解源码
`@Import` 是 Spring 框架自 3.0 版本开始引入的一个核心注解。允许我们导入一个或多个组件类,这些类通常是 `@Configuration` 类。它在功能上相当于 Spring XML 中的 `` 元素,导入类型`@Configuration`类、`ImportSelector`、`ImportBeanDefinitionRegistrar`的实现以及其他常规组件类,在导入的 `@Configuration` 类中声明的 bean 定义应使用 `@Autowired` 进行注入。
```java
/**
* 表示要导入的一个或多个组件类 —— 通常是
* Configuration @Configuration 类。
*
* 提供与Spring XML中的 元素相同的功能。
* 允许导入 @Configuration 类、ImportSelector 和
* ImportBeanDefinitionRegistrar 的实现,以及常规组件
* 类 (从 4.2 开始;与 AnnotationConfigApplicationContext#register 相似)。
*
* 在导入的 @Configuration 类中声明的 @Bean 定义应通过
* org.springframework.beans.factory.annotation.Autowired @Autowired 注入。
* 可以自动注入bean本身,也可以自动注入声明bean的配置类实例。
* 后者允许在 @Configuration 类方法之间进行明确的、IDE友好的导航。
*
* 可以在类级别声明或作为元注解。
*
* 如果需要导入XML或其他非-@Configuration 的bean定义资源,
* 请改用 ImportResource @ImportResource 注解。
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
* @see Configuration
* @see ImportSelector
* @see ImportBeanDefinitionRegistrar
* @see ImportResource
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
/**
* 要导入的 Configuration @Configuration、ImportSelector、
* ImportBeanDefinitionRegistrar 或常规组件类。
*/
Class>[] value();
}
```
### 四、主要功能
1. **导入配置类**
+ 允许一个 `@Configuration` 类引入另一个 `@Configuration` 类。
2. **导入选择器**
+ 通过实现 `ImportSelector` 接口,可以动态地选择和导入配置类。
3. **手动注册Bean**
+ 通过实现 `ImportBeanDefinitionRegistrar` 接口,可以在运行时手动注册 bean。
4. **导入常规组件类**
+ 从 Spring 4.2 开始,还可以导入常规的组件类。
### 五、最佳实践
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,然后遍历并打印所有的bean定义名。
```java
public class ImportApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanName = " + beanDefinitionName);
}
}
}
```
使用`@Import` 注解允许导入其他组件或配置到当前的配置类。在 `MyConfiguration` 类中,它导入了四个不同的组件或选择器,第一个是`MyBean.class`一个常规Bean组件。第二个是`MyImportSelector.class`一个实现了 `ImportSelector` 的类,用于动态选择并导入配置。第三个是`MyDeferredImportSelector.class`一个实现了 `DeferredImportSelector` 的类,用于延迟地选择并导入配置。第四个是`MyImportBeanDefinitionRegistrar.class`一个实现了 `ImportBeanDefinitionRegistrar` 的类,用于手动注册bean。
```java
@Configuration
@Import({MyBean.class, MyImportSelector.class, MyDeferredImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MyConfiguration {
}
```
`MyImportSelector` 类提供了一种动态导入 `MyBeanA` 组件的机制。确保 `MyBeanA` 被加入到Spring的上下文中。
```java
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MyBeanA.class.getName()};
}
}
```
`MyDeferredImportSelector` 类提供了一种延迟导入 `MyBeanB` 组件的机制,确保 `MyBeanB` 被添加到Spring的上下文中。与普通的 `ImportSelector` 不同,`DeferredImportSelector` 允许在Spring处理完所有其他配置类之后再进行导入,从而确保某些特定的处理顺序。
```java
public class MyDeferredImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MyBeanB.class.getName()};
}
}
```
`MyImportBeanDefinitionRegistrar` 类提供手动注册 `MyBeanC` 组件到Spring容器的方法,而不依赖于组件扫描或其他自动配置机制。确保 `MyBeanC` 被添加到Spring的上下文中。
```java
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(MyBeanC.class);
registry.registerBeanDefinition(MyBeanC.class.getName(), beanDefinition);
}
}
```
使用`@Import`注解和其相关的选择器或注册器来将这些bean类导入到Spring上下文中
```java
public class MyBean {
}
public class MyBeanA {
}
public class MyBeanB {
}
public class MyBeanC {
}
```
### 六、时序图
~~~mermaid
sequenceDiagram
participant ImportApplication
participant AnnotationConfigApplicationContext
participant AbstractApplicationContext
participant PostProcessorRegistrationDelegate
participant ConfigurationClassPostProcessor
participant ConfigurationClassParser
participant DeferredImportSelectorHandler
participant DeferredImportSelectorGroupingHandler
participant DeferredImportSelectorGrouping
participant DefaultDeferredImportSelectorGroup
participant ConfigurationClassBeanDefinitionReader
participant MyImportSelector
participant MyDeferredImportSelector
participant MyImportBeanDefinitionRegistrar
participant DefaultListableBeanFactory
ImportApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)
初始化上下文
AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()
刷新上下文
AbstractApplicationContext->>AbstractApplicationContext:invokeBeanFactoryPostProcessors(beanFactory)
调用BeanFactory的后处理器
AbstractApplicationContext->>PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors)
委托调用BeanFactory的后处理器
PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate:invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup)
执行BeanDefinition的注册后处理器
PostProcessorRegistrationDelegate->>ConfigurationClassPostProcessor:postProcessBeanDefinitionRegistry(registry)
处理配置类
ConfigurationClassPostProcessor->>ConfigurationClassPostProcessor:processConfigBeanDefinitions(registry)
处理配置类bean的定义
ConfigurationClassPostProcessor->>ConfigurationClassParser:new ConfigurationClassParser()
创建配置类解析器
ConfigurationClassParser-->>ConfigurationClassPostProcessor:返回parser
ConfigurationClassPostProcessor->>ConfigurationClassParser:parser.parse(candidates)
解析候选类
ConfigurationClassParser->>ConfigurationClassParser:parse(metadata,beanName)
进一步解析类元数据
ConfigurationClassParser->>ConfigurationClassParser:processConfigurationClass(configClass,filter)
处理@Configuration类
ConfigurationClassParser->>+ConfigurationClassParser:doProcessConfigurationClass(configClass, sourceClass, filter)
实际处理配置类
ConfigurationClassParser-->>-ConfigurationClassParser:返回SourceClass
ConfigurationClassParser->>+ConfigurationClassParser:processImports(configClass, sourceClass, importCandidates, filter, true)
处理导入
ConfigurationClassParser->>MyImportSelector:selectImports(importingClassMetadata)
调用自定义的导入选择器
MyImportSelector-->>ConfigurationClassParser:返回Bean数组
ConfigurationClassParser->>DeferredImportSelectorHandler:process()
处理延迟导入选择器
DeferredImportSelectorHandler->>DeferredImportSelectorGroupingHandler:processGroupImports()
处理组导入
DeferredImportSelectorGroupingHandler->>DeferredImportSelectorGrouping:getImports()
获取导入
DeferredImportSelectorGrouping->>DefaultDeferredImportSelectorGroup:process(metadata,selector)
处理默认延迟导入选择器的组
DefaultDeferredImportSelectorGroup->>MyDeferredImportSelector:selectImports(importingClassMetadata)
调用自定义的导入选择器
MyDeferredImportSelector-->>DefaultDeferredImportSelectorGroup:返回Bean数组,存储在imports字段中
DeferredImportSelectorGrouping->>DefaultDeferredImportSelectorGroup:selectImports()
选择导入
DeferredImportSelectorGrouping-->>DeferredImportSelectorGroupingHandler:返回Iterable
DeferredImportSelectorGroupingHandler->>ConfigurationClassParser:processImports(configClass, sourceClass, importCandidates, filter, true)
再次处理导入
ConfigurationClassParser-->>-ConfigurationClassParser:递归处理@Configuration
ConfigurationClassPostProcessor->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitions(configClasses)
加载bean定义
ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator)
加载配置类的bean定义
ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:registerBeanDefinitionForImportedConfigurationClass(configClass)
注册导入的配置类的bean定义
ConfigurationClassBeanDefinitionReader->>DefaultListableBeanFactory:registerBeanDefinition(beanName,beanDefinition)
在bean工厂中注册bean定义
ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitionsFromRegistrars(registrars)
从注册器中加载bean定义
ConfigurationClassBeanDefinitionReader->>MyImportBeanDefinitionRegistrar:registerBeanDefinitions(importingClassMetadata,registry)
调用自定义的bean定义注册器
MyImportBeanDefinitionRegistrar->>DefaultListableBeanFactory:registerBeanDefinition(beanName,beanDefinition)
在bean工厂中注册bean定义
~~~
### 七、源码分析
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,然后遍历并打印所有的bean定义名。
```java
public class ImportApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanName = " + beanDefinitionName);
}
}
}
```
在`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中,执行了三个步骤,我们重点关注`refresh()`方法。
```java
public AnnotationConfigApplicationContext(Class>... componentClasses) {
this();
register(componentClasses);
refresh();
}
```
在`org.springframework.context.support.AbstractApplicationContext#refresh`方法中我们重点关注一下`finishBeanFactoryInitialization(beanFactory)`这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。
```java
@Override
public void refresh() throws BeansException, IllegalStateException {
// ... [代码部分省略以简化]
// 调用在上下文中注册为bean的工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors`方法中,又委托了`PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()`进行调用。
```java
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors`方法中,首先调用了 `BeanDefinitionRegistryPostProcessor`(这是 `BeanFactoryPostProcessor` 的子接口)。它专门用来在所有其他 bean 定义加载之前修改默认的 bean 定义。
```java
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) {
// ... [代码部分省略以简化]
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors`方法中,循环调用了实现`BeanDefinitionRegistryPostProcessor`接口中的`postProcessBeanDefinitionRegistry(registry)`方法
```java
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
.tag("postProcessor", postProcessor::toString);
postProcessor.postProcessBeanDefinitionRegistry(registry);
postProcessBeanDefRegistry.end();
}
}
```
在`org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry`方法中,调用了`processConfigBeanDefinitions`方法,该方法的主要目的是处理和注册配置类中定义的beans。
```java
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ... [代码部分省略以简化]
processConfigBeanDefinitions(registry);
}
```
在`org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions`方法中,这个方法主要处理了配置类的解析和验证,并确保了所有在配置类中定义的beans都被正确地注册到Spring的bean定义注册表中。
```java
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// ... [代码部分省略以简化]
// 步骤1:创建一个用于解析配置类的解析器
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// 步骤2:初始化候选配置类集合以及已解析配置类集合
Set candidates = new LinkedHashSet<>(configCandidates);
Set alreadyParsed = new HashSet<>(configCandidates.size());
// 步骤3:循环处理所有候选配置类,直至没有候选类为止
do {
// 步骤3.1 解析配置类
parser.parse(candidates);
// 步骤3.2 验证配置类
parser.validate();
// 获取解析后的配置类,并从中移除已经处理过的
Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// 步骤4:如果reader为空,则创建一个新的Bean定义读取器
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
// 步骤5:使用读取器为解析的配置类加载Bean定义
this.reader.loadBeanDefinitions(configClasses);
// ... [代码部分省略以简化]
} while (!candidates.isEmpty());
// ... [代码部分省略以简化]
}
```
我们来到`org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions`方法中的步骤3.1。在`org.springframework.context.annotation.ConfigurationClassParser#parse`方法中,主要是遍历所有的配置类候选者,并对每一个带有注解的Bean定义进行解析。这通常涉及到查找该配置类中的@Bean方法、组件扫描指令等,并将这些信息注册到Spring容器中。
```java
public void parse(Set configCandidates) {
// 遍历每个配置类的持有者
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
// 步骤1:如果Bean定义是AnnotatedBeanDefinition的实例,则获取其注解元数据并解析
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
// 步骤2:如果Bean定义是AbstractBeanDefinition并且已经有关联的Bean类,则直接解析该Bean类
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
// 步骤3:如果上述情况都不符合,则直接使用Bean的类名进行解析
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
// 如果在解析过程中捕获到BeanDefinitionStoreException,则直接抛出
catch (BeanDefinitionStoreException ex) {
throw ex;
}
// 对于其他类型的异常,封装并抛出一个新的BeanDefinitionStoreException
catch (Throwable ex) {
// ... [代码部分省略以简化]
}
}
// 步骤4:在所有配置类都被解析后,处理所有延迟的导入选择器
this.deferredImportSelectorHandler.process();
}
```
我们来到`org.springframework.context.annotation.ConfigurationClassParser#parse`方法的第一步,在`org.springframework.context.annotation.ConfigurationClassParser#parse(metadata, beanName)`方法中,将注解元数据和Bean名称转化为一个配置类,然后对其进行处理。处理配置类是Spring配置驱动的核心,它涉及到许多关键操作,如解析`@Bean`方法、处理`@ComponentScan`注解、处理`@Import`注解等。
```java
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass`方法中,处理一个给定的配置类。它首先递归地处理配置类及其父类,以确保所有相关的配置都被正确地读取并解析。在递归处理完所有相关配置后,它将配置类添加到已解析的配置类的映射中。
```java
protected void processConfigurationClass(ConfigurationClass configClass, Predicate filter) throws IOException {
// ... [代码部分省略以简化]
// 步骤1:递归地处理配置类及其超类层次结构
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
} while (sourceClass != null);
// 步骤2:将处理后的配置类放入映射中
this.configurationClasses.put(configClass, configClass);
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass`方法中,其中主要目的是处理给定`ConfigurationClass`中的`@Import`注解。在处理完所有的导入之后,它完成了任务并返回null。
```java
@Nullable
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate filter)
throws IOException {
// ... [代码部分省略以简化]
// 处理 sourceClass 上的所有 @Import 注解
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
// ... [代码部分省略以简化]
// 当前实现中,直接返回null,意味着没有超类需要进一步处理
return null;
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#processImports`方法中,主要分为三个步骤。第一步是对于`ImportSelector`,`processImports`方法主要完成了动态选择和导入类的功能,使得Spring配置更加灵活和模块化。第二步是`ImportBeanDefinitionRegistrar`提供了一个在bean定义注册过程中的插入点,允许动态、条件性配置。`processImports`方法确保这些逻辑在处理`@Import`时正确执行。第三步是当遇到一个不是`ImportSelector`或`ImportBeanDefinitionRegistrar`的类时,直接视其为一个普通的Spring组件或配置类,并按照常规的处理流程进行。这确保了所有通过`@Import`导入的类,不论它们的类型如何,都会被正确地注册和处理。
```java
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection importCandidates, Predicate exclusionFilter,
boolean checkForCircularImports) {
// 如果没有要导入的候选类,直接返回
if (importCandidates.isEmpty()) {
return;
}
// 如果设置了检查循环导入,并且当前的配置类在导入堆栈中形成了一个链,报告循环导入问题
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
// 将当前的配置类推入导入堆栈中
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
// 步骤1:判断候选类是否实现了 ImportSelector 接口
if (candidate.isAssignable(ImportSelector.class)) {
// 加载当前的候选类
Class> candidateClass = candidate.loadClass();
// 通过工具方法实例化 ImportSelector 接口的实现类
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
// 获取从 ImportSelector 指定的排除过滤器
Predicate selectorFilter = selector.getExclusionFilter();
// 如果选择器提供了额外的排除过滤器,则合并当前的排除过滤器
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
// 步骤1.1:检查该选择器是否是 DeferredImportSelector 的实例
if (selector instanceof DeferredImportSelector) {
// 如果是 DeferredImportSelector,使用特定的处理器来处理它
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 步骤1.2: 如果不是 DeferredImportSelector,则使用选择器来选择要导入的类
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
// 将这些类名转化为 SourceClass 集合
Collection importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
// 递归地处理这些要导入的类
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
// 步骤2:检查当前候选类是否实现了 ImportBeanDefinitionRegistrar 接口
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// 加载当前的候选类
Class> candidateClass = candidate.loadClass();
// 通过工具方法实例化 ImportBeanDefinitionRegistrar 接口的实现类
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
// 将实例化的 registrar 添加到当前的配置类中
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
// 步骤3:如果候选类既不是 ImportSelector 也不是 ImportBeanDefinitionRegistrar
else {
// 在导入堆栈中为当前的源类注册导入的类
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// 将候选类处理为一个配置类,并递归地处理它
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
// ... [代码部分省略以简化]
}
finally {
// 在处理完成后,从导入堆栈中弹出当前配置类
this.importStack.pop();
}
}
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#processImports`方法中的步骤1.2中,最后调用到我们自定义的实现逻辑中来,`selectImports`方法始终返回`MyBeanA`类的完全限定名,表示当这个`ImportSelector`被处理时,`MyBeanA`类将被导入到Spring应用上下文中。
```java
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MyBeanA.class.getName()};
}
}
```
我们回到`org.springframework.context.annotation.ConfigurationClassParser#parse`方法的第四步,在`org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorHandler#process`方法中,首先获取当前待处理的延迟导入选择器,然后使用`DeferredImportSelectorGroupingHandler`来按组处理它们,最后确保再次初始化延迟导入选择器列表,为下一次处理做准备。
```java
/**
* 处理所有注册的延迟导入选择器。
*/
public void process() {
// 获取当前的延迟导入选择器列表
List deferredImports = this.deferredImportSelectors;
// 将当前的延迟导入选择器列表设为null,表示它们即将被处理
this.deferredImportSelectors = null;
try {
// 如果存在待处理的延迟导入选择器
if (deferredImports != null) {
// 创建一个新的分组处理器,用于处理延迟导入选择器的分组逻辑
DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
// 对延迟导入选择器进行排序,确保按预期的顺序进行处理
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
// 遍历每个延迟导入选择器,使用分组处理器进行注册
deferredImports.forEach(handler::register);
// 使用分组处理器处理分组的导入
handler.processGroupImports();
}
}
// 确保在方法结束时,无论是否发生异常,都将延迟导入选择器列表重新初始化为一个新的空列表
finally {
this.deferredImportSelectors = new ArrayList<>();
}
}
```
在`org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGroupingHandler#processGroupImports`方法中,遍历每个已注册的延迟导入选择器分组,并对每个分组中的每个导入条目进行处理。处理的内容包括递归地处理所有相关的`@Import`和`@Bean`定义。
```java
/**
* 处理每个分组中的延迟导入选择器。
*/
public void processGroupImports() {
// 遍历每个已注册的延迟导入选择器分组
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
// 获取分组的候选类排除过滤器
Predicate exclusionFilter = grouping.getCandidateFilter();
// 遍历分组中的每个导入选择器
grouping.getImports().forEach(entry -> {
// 获取与当前导入条目关联的配置类
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
try {
// 对每个导入条目进行处理,包括递归地处理所有相关的@Import和@Bean定义
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
}
// 如果在处理过程中捕获到BeanDefinitionStoreException,则直接抛出
catch (BeanDefinitionStoreException ex) {
throw ex;
}
// 对于其他类型的异常,处理相关逻辑(此处简化,不展开详细的异常处理逻辑)
catch (Throwable ex) {
// ... [代码部分省略以简化]
}
});
}
}
```
在`org.springframework.context.annotation.ConfigurationClassParser.DeferredImportSelectorGrouping#getImports`方法中,遍历每个已注册的延迟导入选择器,将它们的相关信息传递给分组进行处理,然后最终使用分组实例来选择并返回要导入的类。
```java
public Iterable getImports() {
// 遍历每个已注册的延迟导入选择器
for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
// 对于每个延迟导入选择器,将其关联的配置类的元数据和其选择器传递给分组进行处理
this.group.process(deferredImport.getConfigurationClass().getMetadata(),
deferredImport.getImportSelector());
}
// 使用分组实例来选择并返回要导入的类
return this.group.selectImports();
}
```
在`org.springframework.context.annotation.ConfigurationClassParser.DefaultDeferredImportSelectorGroup#process`方法中,主要给传入的`DeferredImportSelector`,找出它选择导入的所有类,并将这些类与提供的注解元数据一起存储在一个列表中,以供以后使用。
```java
@Override
public void process(AnnotationMetadata metadata, DeferredImportSelector selector) {
// 使用选择器和提供的元数据选择要导入的类
for (String importClassName : selector.selectImports(metadata)) {
// 对于每个选定的导入类,创建一个新的Entry对象并将其添加到当前分组的导入列表中
this.imports.add(new Entry(metadata, importClassName));
}
}
```
最后调用到我们自定义的实现逻辑中来,`selectImports`方法始终返回`MyBeanB`类的完全限定名,表示当这个`DeferredImportSelector`被处理时,`MyBeanB`类将被导入到Spring应用上下文中。
```java
public class MyDeferredImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MyBeanB.class.getName()};
}
}
```
我们来到`org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions`方法中的步骤5。在`org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions`方法中,遍历给定的一组`ConfigurationClass`对象(这些对象代表的是`@Configuration`注解的类),并为每个类加载其相关的Bean定义。
```java
public void loadBeanDefinitions(Set configurationModel) {
TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
for (ConfigurationClass configClass : configurationModel) {
loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
}
}
```
在`org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass`方法中,如果配置类是通过`@Import`注解或其他机制导入的,则该方法会为其注册一个Bean定义,另外该方法会处理与`ConfigurationClass`关联的所有`ImportBeanDefinitionRegistrar`。这些注册器允许在解析配置类时以编程方式动态地添加额外的Bean定义。
```java
private void loadBeanDefinitionsForConfigurationClass(
ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
// ... [可能的初始化或其他预处理]
// 步骤1:如果这个配置类是由于@Import或其他机制而被导入的,那么为它注册一个Bean定义。
// 这确保了导入的@Configuration类也被视为一个Bean,并可以在ApplicationContext中被检索。
if (configClass.isImported()) {
registerBeanDefinitionForImportedConfigurationClass(configClass);
}
// ... [可能的其他处理或Bean定义加载]
// 步骤2:从当前配置类关联的ImportBeanDefinitionRegistrars加载Bean定义。
// ImportBeanDefinitionRegistrar允许我们在解析@Configuration类时进行编程式地注册额外的Bean定义。
loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
```
我们来到`org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass`方法中步骤1,在`org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#registerBeanDefinitionForImportedConfigurationClass`方法中,首先,它根据配置类的注解元数据创建一个Bean定义。然后,它确定并设置Bean的作用域,例如单例或原型。为Bean定义生成一个唯一的名称后,该方法处理了其上的常见注解,例如`@Lazy`和`@Primary`。如果Bean的作用域如请求或会话需要代理,会应用相应的代理。最后,它将Bean定义注册到Spring容器,并将生成的名称与原始配置类关联。这确保了导入的配置类在Spring中也可以作为一个常规Bean进行访问或注入。
```java
/**
* 为导入的ConfigurationClass注册Bean定义。
*
* @param configClass 要处理的ConfigurationClass
*/
private void registerBeanDefinitionForImportedConfigurationClass(ConfigurationClass configClass) {
// 获取configClass的元数据
AnnotationMetadata metadata = configClass.getMetadata();
// 创建基于元数据的Bean定义
AnnotatedGenericBeanDefinition configBeanDef = new AnnotatedGenericBeanDefinition(metadata);
// 解析Bean的作用域(例如:singleton, prototype)
ScopeMetadata scopeMetadata = scopeMetadataResolver.resolveScopeMetadata(configBeanDef);
configBeanDef.setScope(scopeMetadata.getScopeName());
// 为configBeanDef生成Bean名称
String configBeanName = this.importBeanNameGenerator.generateBeanName(configBeanDef, this.registry);
// 处理常见的注解定义(例如:@Lazy, @Primary)
AnnotationConfigUtils.processCommonDefinitionAnnotations(configBeanDef, metadata);
// 将Bean定义封装在BeanDefinitionHolder中,以携带其他配置元数据
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(configBeanDef, configBeanName);
// 根据需要应用作用域代理模式(例如,对于"request"和"session"作用域的Beans)
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 在Bean定义注册表中注册Bean定义
this.registry.registerBeanDefinition(definitionHolder.getBeanName(), definitionHolder.getBeanDefinition());
// 将生成的Bean名称设置到configClass中
configClass.setBeanName(configBeanName);
// 如果日志级别为TRACE,记录一条关于注册的消息
if (logger.isTraceEnabled()) {
logger.trace("Registered bean definition for imported class '" + configBeanName + "'");
}
}
```
我们来到`org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass`方法中步骤2,每个`ImportBeanDefinitionRegistrar`都有机会基于其自定义逻辑在Spring容器中注册额外的Bean定义。
```java
private void loadBeanDefinitionsFromRegistrars(Map registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
}
```
最后调用到我们自定义的实现逻辑中来,`MyImportBeanDefinitionRegistrar` 类提供手动注册 `MyBeanC` 组件到Spring容器的方法,而不依赖于组件扫描或其他自动配置机制。确保 `MyBeanC` 被添加到Spring的上下文中。
```java
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(MyBeanC.class);
registry.registerBeanDefinition(MyBeanC.class.getName(), beanDefinition);
}
}
```
### 八、注意事项
1. **避免循环引用**
+ 确保我们没有创建循环引用,即一个配置类导入另一个配置类,而后者又反过来导入前者。
2. **与`@ComponentScan`的关系**
+ `@Import`和`@ComponentScan`都可以用于注册bean,但是它们的用途稍有不同。`@ComponentScan`用于自动扫描和注册bean,而`@Import`用于明确地导入其他配置类。
3. **属性覆盖**
+ 如果从多个配置类中导入相同的bean定义,并设置了不同的属性或值,那么后导入的bean定义将覆盖先导入的bean定义。
4. **与`@Profile`的关系**
+ 可以结合使用`@Import`和`@Profile`,这样只有在特定的激活配置文件中才会导入某个配置类。
### 九、总结
#### 最佳实践总结
1. **启动类**
+ 通过`ImportApplication`类,我们初始化了Spring的上下文环境,选择了`AnnotationConfigApplicationContext`,这是Spring提供的使用Java注解配置方式。在该上下文中,我们指定了`MyConfiguration`作为主要的配置类,并遍历了上下文中所有已注册的bean名称。
2. **主配置类**
+ `MyConfiguration`类通过使用`@Import`注解导入了四种不同类型的组件或选择器:
- `MyBean`:一个普通的Java类。
- `MyImportSelector`:一个实现了`ImportSelector`接口的类。
- `MyDeferredImportSelector`:一个实现了`DeferredImportSelector`接口的类。
- `MyImportBeanDefinitionRegistrar`:一个实现了`ImportBeanDefinitionRegistrar`接口的类。
3. **选择器与注册器**:
- `MyImportSelector`通过实现`ImportSelector`接口,动态地选择并导入了`MyBeanA`类。
- `MyDeferredImportSelector`作为`DeferredImportSelector`的实现,延迟地选择并导入了`MyBeanB`类。与`ImportSelector`不同,它允许在所有其他配置类被处理后再进行导入。
- `MyImportBeanDefinitionRegistrar`提供了手动注册bean的功能。在这个示例中,它将`MyBeanC`手动注册到了Spring上下文中。
4. **组件定义**
+ 在我们最佳实践中包含四个Java类,分别为`MyBean`、`MyBeanA`、`MyBeanB`和`MyBeanC`,它们都被上述的选择器或注册器选择并导入到Spring上下文中。
#### 源码分析总结
1. **启动阶段**
+ 在Spring启动时,会使用`AnnotationConfigApplicationContext`来加载和解析配置类,这个配置类可能包含有`@Import`注解。
2. **解析配置类**
+ 在`AnnotationConfigApplicationContext`的构造函数中,通过`refresh()`方法开始刷新容器并解析Bean定义。进一步,在`AbstractApplicationContext#refresh`方法中,会调用`invokeBeanFactoryPostProcessors`方法来处理在上下文中注册为bean的工厂处理器。
3. **处理`@Import`注解**
+ 处理过程中会特别关注`BeanDefinitionRegistryPostProcessor`,这是`BeanFactoryPostProcessor`的子接口,专门用于在所有其他bean定义加载之前修改默认的bean定义。然后,系统解析配置类并验证其内容,这主要涉及查找该类中的@Bean方法、组件扫描指令等,并将这些信息注册到Spring容器中。
4. **处理`ImportSelector`和`DeferredImportSelector`**
+ 在上述解析过程中,如果配置类包含一个实现了`ImportSelector`或`DeferredImportSelector`接口的类,那么它们将被用来选择并导入其他的类。在示例中,`MyImportSelector`和`MyDeferredImportSelector`是这样的选择器,分别导入`MyBeanA`和`MyBeanB`。
5. **处理`ImportBeanDefinitionRegistrar`**
+ 在加载Bean定义的过程中,也会处理与`ConfigurationClass`关联的所有`ImportBeanDefinitionRegistrar`。这些注册器允许在解析时动态地、以编程方式地添加额外的Bean定义,如`MyImportBeanDefinitionRegistrar`示例中为`MyBeanC`进行的手动注册。
6. **注册导入的配置类**
+ 如果一个`@Configuration`类是由于`@Import`或其他机制导入的,Spring不仅会处理它的`@Bean`方法和其他注解,还会为这个类本身注册一个Bean定义,以便它也可以被注入到其他Bean或由应用程序检索。
7. **额外Bean的注册**
+ 通过`ImportBeanDefinitionRegistrar`和其自定义逻辑,能够根据需要将任意数量的额外Bean定义注册到容器中。
================================================
FILE: spring-annotation/spring-annotation-import/pom.xml
================================================
spring-annotation
com.xcs.spring
0.0.1-SNAPSHOT
4.0.0
spring-annotation-import
================================================
FILE: spring-annotation/spring-annotation-import/src/main/java/com/xcs/spring/ImportApplication.java
================================================
package com.xcs.spring;
import com.xcs.spring.config.MyConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 2023年08月07日 16时21分
**/
public class ImportApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
for (String beanDefinitionName : context.getBeanDefinitionNames()) {
System.out.println("beanName = " + beanDefinitionName);
}
}
}
================================================
FILE: spring-annotation/spring-annotation-import/src/main/java/com/xcs/spring/bean/MyBean.java
================================================
package com.xcs.spring.bean;
/**
* @author xcs
* @date 2023年09月11日 11时13分
**/
public class MyBean {
}
================================================
FILE: spring-annotation/spring-annotation-import/src/main/java/com/xcs/spring/bean/MyBeanA.java
================================================
package com.xcs.spring.bean;
/**
* @author xcs
* @date 2023年08月28日 11时13分
**/
public class MyBeanA {
}
================================================
FILE: spring-annotation/spring-annotation-import/src/main/java/com/xcs/spring/bean/MyBeanB.java
================================================
package com.xcs.spring.bean;
/**
* @author xcs
* @date 2023年08月28日 11时13分
**/
public class MyBeanB {
}
================================================
FILE: spring-annotation/spring-annotation-import/src/main/java/com/xcs/spring/bean/MyBeanC.java
================================================
package com.xcs.spring.bean;
/**
* @author xcs
* @date 2023年08月28日 11时13分
**/
public class MyBeanC {
}
================================================
FILE: spring-annotation/spring-annotation-import/src/main/java/com/xcs/spring/config/MyConfiguration.java
================================================
package com.xcs.spring.config;
import com.xcs.spring.bean.MyBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* @author xcs
* @date 2023年08月07日 16时25分
**/
@Configuration
@Import({MyBean.class, MyImportSelector.class, MyDeferredImportSelector.class, MyImportBeanDefinitionRegistrar.class})
public class MyConfiguration {
}
================================================
FILE: spring-annotation/spring-annotation-import/src/main/java/com/xcs/spring/config/MyDeferredImportSelector.java
================================================
package com.xcs.spring.config;
import com.xcs.spring.bean.MyBeanB;
import org.springframework.context.annotation.DeferredImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author xcs
* @date 2023年08月29日 11时08分
**/
public class MyDeferredImportSelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MyBeanB.class.getName()};
}
}
================================================
FILE: spring-annotation/spring-annotation-import/src/main/java/com/xcs/spring/config/MyImportBeanDefinitionRegistrar.java
================================================
package com.xcs.spring.config;
import com.xcs.spring.bean.MyBeanC;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author xcs
* @date 2023年08月28日 11时17分
**/
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
RootBeanDefinition beanDefinition = new RootBeanDefinition(MyBeanC.class);
registry.registerBeanDefinition(MyBeanC.class.getName(), beanDefinition);
}
}
================================================
FILE: spring-annotation/spring-annotation-import/src/main/java/com/xcs/spring/config/MyImportSelector.java
================================================
package com.xcs.spring.config;
import com.xcs.spring.bean.MyBeanA;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
/**
* @author xcs
* @date 2023年08月28日 11时12分
**/
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{MyBeanA.class.getName()};
}
}
================================================
FILE: spring-annotation/spring-annotation-lazy/README.md
================================================
## @Lazy
- [@Lazy](#lazy)
- [一、基本信息](#一基本信息)
- [二、注解描述](#二注解描述)
- [三、注解源码](#三注解源码)
- [四、主要功能](#四主要功能)
- [五、最佳实践](#五最佳实践)
- [六、时序图](#六时序图)
- [Bean注册时序图](#bean注册时序图)
- [Bean延迟创建时序图](#bean延迟创建时序图)
- [Bean延迟注入时序图](#bean延迟注入时序图)
- [七、源码分析](#七源码分析)
- [延迟初始化](#延迟初始化)
- [延迟注入](#延迟注入)
- [八、注意事项](#八注意事项)
- [九、总结](#九总结)
- [最佳实践总结](#最佳实践总结)
- [源码分析总结](#源码分析总结)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [我的CSDN](https://blog.csdn.net/duzhuang2399/article/details/133800805) 📚 **文章目录** - [所有文章](https://github.com/xuchengsheng/spring-reading) 🔗 **源码地址** - [@Lazy源码](https://github.com/xuchengsheng/spring-reading/tree/master/spring-annotation/spring-annotation-lazy)
### 二、注解描述
`@Lazy`注解,它的主要用途是延迟依赖注入的初始化。通常情况下,当 ApplicationContext 被启动和刷新时,所有的单例 bean 会被立即初始化。但有时,可能希望某些 bean 在首次使用时才被初始化。
### 三、注解源码
`@Lazy`注解是 Spring 框架自 3.0 版本开始引入的一个核心注解,用于控制 bean 的懒加载行为。默认情况下,当 `@Lazy` 被应用,bean 不会在 Spring 容器启动时立即初始化,而是在首次被引用或请求时。这适用于通过 `@Component` 或 `@Bean` 定义的 bean。此外,`@Lazy` 还可以用于注入点,如 `@Autowired`,创建一个懒解析代理,从而实现延迟注入。当用在 `@Configuration` 类上时,它影响该配置中所有的 `@Bean` 定义。
```java
/**
* 指示一个bean是否应懒加载初始化。
*
* 可直接或间接地用于带 org.springframework.stereotype.Component @Component 注解的类,
* 或者用于带有 Bean @Bean 注解的方法。
*
* 如果此注解不在 @Component 或 @Bean 定义上,将会立即初始化。
* 如果存在并且设置为 true,除非被另一个bean引用或从包围的 org.springframework.beans.factory.BeanFactory BeanFactory 中显式检索,
* 否则 @Bean 或 @Component 不会初始化。如果存在并设置为 false,那么执行积极初始化单例的bean工厂将在启动时实例化bean。
*
* 如果Lazy存在于 Configuration @Configuration 类上,表示该 @Configuration 中的所有 @Bean 方法都应懒加载。
* 如果在一个带有 @Lazy 注解的 @Configuration 类的 @Bean 方法上 @Lazy 设置为false,则表示覆盖了“默认懒惰”行为,该bean应立即初始化。
*
* 除了其在组件初始化中的作用外,此注解也可用于带有 org.springframework.beans.factory.annotation.Autowired 或 javax.inject.Inject 的注入点:
* 在这种情况下,它会导致为所有受影响的依赖关系创建一个懒解析代理,作为使用 org.springframework.beans.factory.ObjectFactory 或 javax.inject.Provider 的替代方法。
*
* @author Chris Beams
* @author Juergen Hoeller
* @since 3.0
* @see Primary
* @see Bean
* @see Configuration
* @see org.springframework.stereotype.Component
*/
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
/**
* 是否应进行懒加载初始化。
*/
boolean value() default true;
}
```
### 四、主要功能
1. **延迟初始化**
+ 当 `@Lazy` 注解应用于一个 `@Bean` 或 `@Component` 上时,该 bean 不会在 Spring 容器启动时立即初始化。而是直到首次被引用或请求时才进行初始化。
2. **延迟注入**
+ 当 `@Lazy` 注解应用于 `@Autowired` 或其他注入点上时,它导致为所注入的依赖关系创建一个懒解析代理,实现首次访问时的延迟注入。
### 五、最佳实践
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,然后从中获取一个 `MyService` bean 并调用其 `show` 方法。
```java
public class LazyApplication {
public static void main(String[] args) {
System.out.println("启动 Spring ApplicationContext...");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
System.out.println("启动完成 Spring ApplicationContext...");
System.out.println("获取MyService...");
MyService myService = context.getBean(MyService.class);
System.out.println("调用show方法...");
myService.show();
}
}
```
这里使用`@Bean`注解,`MyBean` 被标注了 `@Lazy`,这意味着它只会在首次被请求时才会初始化。`MyService` 没有 `@Lazy` 注解,所以它会被立即初始化。
```java
@Configuration
public class MyConfiguration {
@Bean
@Lazy
public MyBean myBean(){
System.out.println("MyBean 初始化了!");
return new MyBean();
}
@Bean
public MyService myService(){
return new MyService();
}
}
```
`MyBean` 类定义很简单。它有一个构造函数,在构造函数中打印了 "MyBean 的构造函数被调用了!",并有一个 `show` 方法,该方法打印 "hello world!"。
```java
public class MyBean {
public MyBean() {
System.out.println("MyBean 的构造函数被调用了!");
}
public void show() {
System.out.println("hello world!");
}
}
```
`MyService` 类有一个对 `MyBean` 的引用,而该引用通过 `@Autowired` 进行依赖注入。由于我们还在这个注入点上添加了 `@Lazy` 注解,这意味着 `myBean` 的实际注入将被延迟,直到我们首次尝试访问它时。
```java
public class MyService {
@Autowired
@Lazy
private MyBean myBean;
public void show() {
System.out.println("MyBean Class = " + myBean.getClass());
myBean.show();
}
}
```
运行结果发现
1. **延迟初始化**:
- 尽管 `MyService` 在应用上下文启动后可用,但由于 `MyBean` 上的 `@Lazy` 注解,`MyBean` 在启动时并未被初始化。
- 只有在首次访问或使用 `MyBean` 时,它才真正被初始化。这在 "MyBean 初始化了!" 和 "MyBean 的构造函数被调用了!" 的输出中得到了体现。
2. **延迟注入**:
- 在 `MyService` 的 `show` 方法首次被调用时,由于 `@Autowired` 和 `@Lazy` 注解的组合,Spring 不是直接注入原始的 `MyBean` 实例,而是注入一个代理对象。
- 这个代理对象的类名为 `com.xcs.spring.bean.MyBean$$EnhancerBySpringCGLIB$$2a517c55`,表明它是由 Spring 的 CGLIB 动态生成的。
- 当尝试访问该代理对象上的方法(如 `show` 方法)时,代理会确保真正的 `MyBean` 被初始化并代理该方法调用。
```java
启动 Spring ApplicationContext...
启动完成 Spring ApplicationContext...
获取MyService...
调用show方法...
MyBean Class = class com.xcs.spring.bean.MyBean$$EnhancerBySpringCGLIB$$2a517c55
MyBean 初始化了!
MyBean 的构造函数被调用了!
hello world!
```
### 六、时序图
#### Bean注册时序图
~~~mermaid
sequenceDiagram
LazyApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)
创建应用上下文
AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()
刷新应用上下文
AbstractApplicationContext->>AbstractApplicationContext:invokeBeanFactoryPostProcessors(beanFactory)
调用BFPP方法
AbstractApplicationContext->>PostProcessorRegistrationDelegate:invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors)
委托BFPP处理
PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate:invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup)
调用BDRPP方法
PostProcessorRegistrationDelegate->>ConfigurationClassPostProcessor:postProcessBeanDefinitionRegistry(registry)
处理Bean定义
ConfigurationClassPostProcessor->>ConfigurationClassPostProcessor:processConfigBeanDefinitions(registry)
处理配置类Bean
ConfigurationClassPostProcessor->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitions(configurationModel)
加载Bean定义
ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitionsForConfigurationClass(configClass,trackedConditionEvaluator)
为配置类加载
ConfigurationClassBeanDefinitionReader->>ConfigurationClassBeanDefinitionReader:loadBeanDefinitionsForBeanMethod(beanMethod)
为@Bean方法加载
ConfigurationClassBeanDefinitionReader->>AnnotationConfigUtils:processCommonDefinitionAnnotations(abd,metadata)
处理注解定义
AnnotationConfigUtils->>AnnotationConfigUtils:attributesFor(metadata, Lazy.class)
获取注解属性
AnnotationConfigUtils->>AbstractBeanDefinition:setLazyInit(lazyInit)
设置懒加载属性
~~~
#### Bean延迟创建时序图
~~~mermaid
sequenceDiagram
DependsOnApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext(componentClasses)
创建应用上下文
AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh()
刷新应用上下文
AbstractApplicationContext->>AbstractApplicationContext:finishBeanFactoryInitialization(beanFactory)
完成bean工厂初始化
AbstractApplicationContext->>DefaultListableBeanFactory:preInstantiateSingletons()
预实例化单例beans
DefaultListableBeanFactory->>AbstractBeanDefinition:isLazyInit()
AbstractBeanDefinition->>DefaultListableBeanFactory:返回Bean是否懒加载
DefaultListableBeanFactory->>AbstractBeanFactory:getBean(beanName)
alt Bean是懒加载
AbstractBeanFactory->>DefaultListableBeanFactory: 执行初始化Bean对象
else Bean不是懒加载
AbstractBeanFactory->>DefaultListableBeanFactory: 跳过Bean初始化,等待真正使用时在初始化
end
~~~
#### Bean延迟注入时序图
~~~mermaid
sequenceDiagram
Title: InstantiationAwareBeanPostProcessor时序图
InstantiationAwareBeanPostProcessorApplication->>AnnotationConfigApplicationContext:AnnotationConfigApplicationContext
开始初始化
AnnotationConfigApplicationContext->>AbstractApplicationContext:refresh
刷新上下文
AbstractApplicationContext->>AbstractApplicationContext:finishBeanFactoryInitialization
实例化非懒加载的单列Bean
AbstractApplicationContext->>DefaultListableBeanFactory:preInstantiateSingletons
预实例化Singleton
DefaultListableBeanFactory->>AbstractBeanFactory:getBean
根据beanName获取对象
AbstractBeanFactory->>AbstractBeanFactory:doGetBean
返回指定bean的实例
AbstractBeanFactory->>DefaultSingletonBeanRegistry:getSingleton
获取单例对象
DefaultSingletonBeanRegistry->>AbstractBeanFactory:getObject
ObjectFactory接口的工厂方法
AbstractBeanFactory->>AbstractAutowireCapableBeanFactory:createBean
创建一个bean实例,填充bean实例,应用后处理器
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:doCreateBean(beanName,mbd,args)
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:populateBean(beanName,mbd,bw)
AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessProperties(pvs,bean,beanName)
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs)
AutowiredAnnotationBeanPostProcessor->>InjectionMetadata:inject(target,beanName,pvs)
InjectionMetadata->>AutowiredFieldElement:inject(bean,beanName,pvs)
AutowiredFieldElement->>AutowiredFieldElement:resolveFieldValue(field,bean,beanName)
AutowiredFieldElement->>DefaultListableBeanFactory:resolveDependency(descriptor,requestingBeanName,autowiredBeanNames,typeConverter)
DefaultListableBeanFactory->>DefaultListableBeanFactory:getAutowireCandidateResolver()
DefaultListableBeanFactory->>ContextAnnotationAutowireCandidateResolver:getLazyResolutionProxyIfNecessary(descriptor,beanName)
ContextAnnotationAutowireCandidateResolver->>ContextAnnotationAutowireCandidateResolver:isLazy(descriptor)
ContextAnnotationAutowireCandidateResolver->>ContextAnnotationAutowireCandidateResolver:buildLazyResolutionProxy(descriptor, beanName)
ContextAnnotationAutowireCandidateResolver->>ProxyFactory:getProxy(classLoader)
ProxyFactory->>ContextAnnotationAutowireCandidateResolver:返回被代理的对象
ContextAnnotationAutowireCandidateResolver->>DefaultListableBeanFactory:返回被代理的对象
DefaultListableBeanFactory->>AutowiredFieldElement:返回被代理的对象
AutowiredFieldElement->>Field:field.set(bean, value)注入被代理的对象
~~~
### 七、源码分析
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类,然后从中获取一个 `MyService` bean 并调用其 `show` 方法。
```java
public class LazyApplication {
public static void main(String[] args) {
System.out.println("启动 Spring ApplicationContext...");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
System.out.println("启动完成 Spring ApplicationContext...");
System.out.println("获取MyService...");
MyService myService = context.getBean(MyService.class);
System.out.println("调用show方法...");
myService.show();
}
}
```
在`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中,执行了三个步骤。
```java
public AnnotationConfigApplicationContext(Class>... componentClasses) {
this();
register(componentClasses);
refresh();
}
```
在`org.springframework.context.support.AbstractApplicationContext#refresh`方法中,我们重点关注一下`finishBeanFactoryInitialization(beanFactory)`这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。
```java
@Override
public void refresh() throws BeansException, IllegalStateException {
// ... [代码部分省略以简化]
// 实例化所有剩余非懒加载的单列Bean对象
finishBeanFactoryInitialization(beanFactory);
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization`方法中,会继续调用`DefaultListableBeanFactory`类中的`preInstantiateSingletons`方法来完成所有剩余非懒加载的单列Bean对象。
```java
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// ... [代码部分省略以简化]
// 完成所有剩余非懒加载的单列Bean对象。
beanFactory.preInstantiateSingletons();
}
```
#### 延迟初始化
在`org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons`方法中,确保所有非懒加载的单例 beans 在容器启动时都被初始化,除非它们显式地标记为懒加载。这也是为什么 `@Lazy` 注解对于那些想要推迟 bean 初始化的场景非常有用。
```java
public void preInstantiateSingletons() throws BeansException {
// ... [代码部分省略以简化]
// 复制Bean名称列表
List beanNames = new ArrayList<>(this.beanDefinitionNames);
// 初始化非懒加载单例
for (String beanName : beanNames) {
RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
// Bean属性检查
if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
// 是否为工厂Bean
if (isFactoryBean(beanName)) {
Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
// 是否为FactoryBean实例
if (bean instanceof FactoryBean) {
FactoryBean> factory = (FactoryBean>) bean;
boolean isEagerInit;
// 安全权限检查
if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
isEagerInit = AccessController.doPrivileged(
(PrivilegedAction) ((SmartFactoryBean>) factory)::isEagerInit,
getAccessControlContext());
}
else {
// 是否立即初始化
isEagerInit = (factory instanceof SmartFactoryBean &&
((SmartFactoryBean>) factory).isEagerInit());
}
// 立即初始化Bean
if (isEagerInit) {
getBean(beanName);
}
}
}
else {
// 获取/创建Bean
getBean(beanName);
}
}
}
// ... [代码部分省略以简化]
}
```
#### 延迟注入
在`org.springframework.beans.factory.support.AbstractBeanFactory#getBean()`方法中,又调用了`doGetBean`方法来实际执行创建Bean的过程,传递给它bean的名称和一些其他默认的参数值。此处,`doGetBean`负责大部分工作,如查找bean定义、创建bean(如果尚未创建)、处理依赖关系等。
```java
@Override
public Object getBean(String name) throws BeansException {
return doGetBean(name, null, null, false);
}
```
在`org.springframework.beans.factory.support.AbstractBeanFactory#doGetBean`方法中,首先检查所请求的bean是否是一个单例并且已经创建。如果尚未创建,它将创建一个新的实例。在这个过程中,它处理可能的异常情况,如循环引用,并确保返回的bean是正确的类型。这是Spring容器bean生命周期管理的核心部分。
```java
protected T doGetBean(
String name, @Nullable Class requiredType, @Nullable Object[] args, boolean typeCheckOnly)
throws BeansException {
// ... [代码部分省略以简化]
// 开始创建bean实例
if (mbd.isSingleton()) {
// 如果bean是单例的,我们会尝试从单例缓存中获取它
// 如果不存在,则使用lambda创建一个新的实例
sharedInstance = getSingleton(beanName, () -> {
try {
// 尝试创建bean实例
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
// ... [代码部分省略以简化]
}
});
// 对于某些bean(例如FactoryBeans),可能需要进一步处理以获取真正的bean实例
beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
}
// ... [代码部分省略以简化]
// 确保返回的bean实例与请求的类型匹配
return adaptBeanInstance(name, beanInstance, requiredType);
}
```
在`org.springframework.beans.factory.support.DefaultSingletonBeanRegistry#getSingleton()`方法中,主要负责从单例缓存中获取一个已存在的bean实例,或者使用提供的`ObjectFactory`创建一个新的实例。这是确保bean在Spring容器中作为单例存在的关键部分。
```java
public Object getSingleton(String beanName, ObjectFactory> singletonFactory) {
// ... [代码部分省略以简化]
// 同步访问单例对象缓存,确保线程安全
synchronized (this.singletonObjects) {
// 从缓存中获取单例对象
Object singletonObject = this.singletonObjects.get(beanName);
// 如果缓存中没有找到
if (singletonObject == null) {
// ... [代码部分省略以简化]
try {
// 使用工厂创建新的单例实例
singletonObject = singletonFactory.getObject();
newSingleton = true;
}
catch (IllegalStateException ex) {
// ... [代码部分省略以简化]
}
catch (BeanCreationException ex) {
// ... [代码部分省略以简化]
}
finally {
// ... [代码部分省略以简化]
}
// ... [代码部分省略以简化]
}
// 返回单例对象
return singletonObject;
}
}
```
在`org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBean()`方法中,主要的逻辑是调用 `doCreateBean`,这是真正进行 bean 实例化、属性填充和初始化的地方。这个方法会返回新创建的 bean 实例。
```java
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// ... [代码部分省略以简化]
try {
// 正常的bean实例化、属性注入和初始化。
// 这里是真正进行bean创建的部分。
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
// 记录bean成功创建的日志
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// ... [代码部分省略以简化]
}
catch (Throwable ex) {
// ... [代码部分省略以简化]
}
}
```
在`org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean`方法中,对 bean 的属性进行注入。
```java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
throws BeanCreationException {
// ... [代码部分省略以简化]
try {
// 属性注入:这一步将配置中的属性值注入到bean实例中。例如,XML中定义的属性或使用@Autowired和@Value注解的属性都会在这里被注入
populateBean(beanName, mbd, instanceWrapper);
// ... [代码部分省略以简化]
}
catch (Throwable ex) {
// ... [代码部分省略以简化]
}
// ... [代码部分省略以简化]
}
```
在`org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#populateBean`方法中,如果一个属性被标记为 `@Autowired`,并且与 `@Lazy` 结合使用,那么实际的懒加载逻辑会在这个步骤中的其他部分被处理(特别是通过 `AutowiredAnnotationBeanPostProcessor`)。
```java
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
// ... [代码部分省略以简化]
// 对每一个InstantiationAwareBeanPostProcessor进行处理,这些处理器可能会修改Bean的属性值。
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
// 尝试使用新版本的方法 postProcessProperties
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
// ... [代码部分省略以简化]
}
// ... [代码部分省略以简化]
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties`方法中,用于处理 `@Autowired` 注解的依赖注入。
```java
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 根据bean的名称和类查找@Autowired注解元数据
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
// 执行实际的依赖注入
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
// 抛出bean创建异常
throw ex;
}
catch (Throwable ex) {
// 处理其他类型的异常,转换为Bean创建异常
throw new BeanCreationException(beanName, "依赖自动注入失败", ex);
}
// 返回属性值
return pvs;
}
```
在`org.springframework.beans.factory.annotation.InjectionMetadata#inject`方法中,遍历所有这些元素并调用其 `inject` 方法,这样实现了对带有注解的字段或方法的实际注入。
```java
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// 获取已校验的注入元素
Collection checkedElements = this.checkedElements;
// 如果没有已校验的元素,则使用所有注入元素
Collection elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
// 遍历所有待注入的元素(字段或方法)
for (InjectedElement element : elementsToIterate) {
// 执行实际的注入操作
element.inject(target, beanName, pvs);
}
}
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject`方法中,这个 `inject` 方法的核心逻辑是解析 `@Autowired` 字段的值(即找到匹配的 bean 依赖)并注入到目标 bean 中。在这个过程中,使用缓存以提高性能。
```java
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// 获取带有@Autowired注解的字段
Field field = (Field) this.member;
Object value;
// 如果字段的值已经被缓存,则直接从缓存中获取
if (this.cached) {
try {
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// 如果缓存中的bean意外被移除 -> 重新解析
value = resolveFieldValue(field, bean, beanName);
}
}
else {
// 解析字段的值(即找到要注入的bean)
value = resolveFieldValue(field, bean, beanName);
}
if (value != null) {
// 使字段可访问(例如,如果它是私有的)
ReflectionUtils.makeAccessible(field);
// 将解析出的值(bean)注入到目标bean的字段中
field.set(bean, value);
}
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue`方法中,主要用于解析 `@Autowired` 注解的字段值。它确定了应该为目标字段注入哪个 bean。
```java
@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
// ... [代码部分省略以简化]
Object value;
try {
// 解析依赖:这里的核心逻辑是使用bean工厂去解析字段的依赖。它会查找合适的bean来注入到此字段。
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
// 当无法解析依赖时,抛出异常
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
// ... [代码部分省略以简化]
// 返回解析到的值(bean)
return value;
}
```
在`org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency`方法中,这里的关键逻辑是 `getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary`,该方法尝试为描述的依赖关系获取一个懒加载代理。如果该依赖关系标记为懒加载 (`@Lazy`),并且被 `@Autowired` 引用,那么它将返回一个懒加载代理,而不是实际的 bean。这样,只有当应用程序真正访问该依赖关系时,实际的 bean 才会被初始化。
```java
@Override
@Nullable
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
// ... [代码部分省略以简化]
// 尝试为描述的依赖关系获取一个懒加载代理。如果依赖是懒加载的,这将返回一个代理对象。
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
descriptor, requestingBeanName);
// 如果没有为懒加载的依赖关系获取到代理,则尝试直接解析依赖关系
if (result == null) {
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
return result; // 返回解析得到的bean或者懒加载代理
}
```
在`org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#getLazyResolutionProxyIfNecessary`方法中,如果是懒加载,会调用 `buildLazyResolutionProxy` 来创建一个代理,当首次访问该代理时,代理会触发实际的 bean 初始化。
```java
@Override
@Nullable
public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
// 判断依赖描述是否被标记为懒加载
// 如果是懒加载,为其构建一个懒加载代理
// 如果不是懒加载,则返回null
return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
}
```
在`org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#buildLazyResolutionProxy`方法中,核心部分是 `TargetSource` 和 `ProxyFactory`。当我们访问这个懒加载代理时,`TargetSource` 的 `getTarget` 方法会被调用,它会解析和返回真正的 bean(或其他依赖项)。使用 `ProxyFactory`,可以为给定的 `TargetSource` 创建一个新的代理。这是 `@Lazy` 注解在字段注入时的实际实现,确保在首次访问字段时才触发依赖的解析和bean的初始化。
```java
protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
// 获取当前的BeanFactory
BeanFactory beanFactory = getBeanFactory();
// 确认BeanFactory是DefaultListableBeanFactory的实例
Assert.state(beanFactory instanceof DefaultListableBeanFactory,
"BeanFactory needs to be a DefaultListableBeanFactory");
final DefaultListableBeanFactory dlbf = (DefaultListableBeanFactory) beanFactory;
// 创建一个目标源(TargetSource)用于懒加载代理
TargetSource ts = new TargetSource() {
// 获取依赖的类型
@Override
public Class> getTargetClass() {
return descriptor.getDependencyType();
}
@Override
public boolean isStatic() {
return false;
}
// 当访问代理时,该方法被调用来解析实际的依赖关系
@Override
public Object getTarget() {
// ... [代码部分省略以简化]
return target; // 返回解析得到的bean
}
@Override
public void releaseTarget(Object target) {
}
};
// 使用Spring的ProxyFactory创建一个新的代理
ProxyFactory pf = new ProxyFactory();
pf.setTargetSource(ts);
Class> dependencyType = descriptor.getDependencyType();
if (dependencyType.isInterface()) {
pf.addInterface(dependencyType);
}
// 返回懒加载代理
return pf.getProxy(dlbf.getBeanClassLoader());
}
```
### 八、注意事项
1. **默认行为**
+ 如果没有使用 `@Lazy` 注解,Spring 容器会在启动时立即实例化单例 bean。通过使用 `@Lazy`,我们可以改变这个行为,使得 bean 只在首次请求时被初始化。
2. **构造函数注入**
+ 如果一个懒加载的 bean 依赖于另一个非懒加载的 bean,那么该懒加载的 bean 会在容器启动时被初始化,因为它的依赖需要它。这种情况常常在构造函数注入时出现。
3. **上下文的生命周期**
+ `@Lazy` 对于应用上下文的启动速度可能有益,因为少了一些即时初始化的工作。但请注意,延迟初始化可能会导致我们在首次访问 bean 时遇到延迟。
4. **与 `@Autowired` 结合使用**
+ 如果我们在一个字段或 setter 方法上同时使用 `@Autowired` 和 `@Lazy`,Spring 会注入一个代理对象。这个代理对象会在我们首次访问它时触发真正的 bean 初始化。
5. **懒加载的代理**
+ 当与 `@Autowired` 结合使用时,要确保 bean 的类型与代理类型兼容。如果注入的 bean 类型是一个接口,Spring 会创建一个基于接口的代理。如果是一个类,Spring 会创建一个基于类的代理。
6. **错误处理**
+ 延迟初始化意味着某些错误可能在应用启动时不会被立即发现,例如 bean 配置错误。这样的错误只有在实际尝试初始化 bean 时才会被抛出。
7. **在组件类上使用**
+ 对于直接或间接使用 `@Component`、`@Service`、`@Repository` 或 `@Controller` 注解的类,可以使用 `@Lazy` 注解来使这些组件在首次被注入或查找时才被初始化。
### 九、总结
#### 最佳实践总结
1. **上下文初始化**:
- 使用 `AnnotationConfigApplicationContext` 初始化应用上下文是针对基于Java的配置的推荐做法。
- 提供一个配置类(如 `MyConfiguration`)作为参数可以帮助上下文知道如何加载和配置beans。
2. **懒加载配置**:
- 通过在配置类的 `@Bean` 方法上添加 `@Lazy` 注解,可以确保特定的bean只在首次请求时被初始化,而不是在应用上下文启动时。
- 这可以提高应用启动速度,特别是对于资源密集型的beans或需要长时间初始化的beans。
3. **依赖注入**:
- 使用 `@Autowired` 注解是Spring的核心特性,它可以自动注入bean的依赖关系。
- 当与 `@Lazy` 注解组合使用时,Spring会注入一个代理对象而不是实际的bean实例。这个代理对象在首次访问时会触发真正的bean初始化。
4. **理解代理**:
- 由于延迟注入,通过 `@Autowired` 和 `@Lazy` 注解注入的对象是一个由CGLIB生成的代理对象。
- 这个代理对象负责在首次访问时初始化真正的bean。
5. **输出与验证**:
- 通过在bean的初始化和方法调用中添加日志或打印语句,可以验证和观察懒加载和代理的行为。
- 这对于确保应用的预期行为和性能调优非常有用。
#### 源码分析总结
1. **启动及初始化**:
- 使用`AnnotationConfigApplicationContext`初始化应用上下文。
- 在`AnnotationConfigApplicationContext`的构造函数中,执行了注册(`register`)和刷新(`refresh`)方法。
2. **Bean的实例化**:
- 在上下文刷新过程中,`finishBeanFactoryInitialization(beanFactory)`方法确保所有非懒加载的单例Beans被实例化。
- `DefaultListableBeanFactory#preInstantiateSingletons`方法确保所有非懒加载的单例Beans在容器启动时被初始化。
3. **延迟初始化**:
- 如果Bean被标记为`@Lazy`,它将不会在容器启动时被初始化,但只在首次请求时。
- `DefaultListableBeanFactory#preInstantiateSingletons`方法中,对于`isLazyInit`返回`true`的Beans,不会进行预初始化。
4. **Bean的获取与依赖注入**:
- 使用`AbstractBeanFactory#getBean`方法获取Bean实例。
- 如果Bean尚未创建,`doGetBean`方法将执行Bean的实际创建,包括解析依赖关系、处理循环引用等。
- 对于单例Beans,它们将被缓存,确保每次都返回相同的实例。
- 通过`AbstractAutowireCapableBeanFactory#createBean`来进行实际的Bean创建,并且将其属性通过`populateBean`方法注入。
5. **延迟注入**:
- 如果一个字段或属性被`@Autowired`注解,并与`@Lazy`结合使用,实际的懒加载逻辑会在属性填充阶段被处理。
- 使用`AutowiredAnnotationBeanPostProcessor`来处理带有`@Autowired`注解的属性的注入。
- 在`AutowiredFieldElement#inject`方法中,如果字段被标记为`@Lazy`,Spring不会直接注入真实的Bean,而是注入一个懒加载代理。
- 这个懒加载代理的实际行为是在首次访问时触发真正的Bean初始化。
6. **懒加载代理的创建**:
- 使用`ContextAnnotationAutowireCandidateResolver`来检查依赖关系是否需要懒加载。
- 如果需要懒加载,它将使用`buildLazyResolutionProxy`方法来为该依赖关系创建一个代理。
- 这个代理的行为是:在首次访问时,它会解析和返回真正的Bean或其他依赖项。
- 使用Spring的`ProxyFactory`来为给定的目标源创建新的代理。
================================================
FILE: spring-annotation/spring-annotation-lazy/pom.xml
================================================
spring-annotation
com.xcs.spring
0.0.1-SNAPSHOT
4.0.0
spring-annotation-lazy
================================================
FILE: spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/LazyApplication.java
================================================
package com.xcs.spring;
import com.xcs.spring.config.MyConfiguration;
import com.xcs.spring.service.MyService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 2023年08月07日 16时21分
**/
public class LazyApplication {
public static void main(String[] args) {
System.out.println("启动 Spring ApplicationContext...");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
System.out.println("启动完成 Spring ApplicationContext...");
System.out.println("获取MyService...");
MyService myService = context.getBean(MyService.class);
System.out.println("调用show方法...");
myService.show();
}
}
================================================
FILE: spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/bean/MyBean.java
================================================
package com.xcs.spring.bean;
public class MyBean {
public MyBean() {
System.out.println("MyBean 的构造函数被调用了!");
}
public void show() {
System.out.println("hello world!");
}
}
================================================
FILE: spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/config/MyConfiguration.java
================================================
package com.xcs.spring.config;
import com.xcs.spring.bean.MyBean;
import com.xcs.spring.service.MyService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@Configuration
public class MyConfiguration {
@Bean
@Lazy
public MyBean myBean(){
System.out.println("MyBean 初始化了!");
return new MyBean();
}
@Bean
public MyService myService(){
return new MyService();
}
}
================================================
FILE: spring-annotation/spring-annotation-lazy/src/main/java/com/xcs/spring/service/MyService.java
================================================
package com.xcs.spring.service;
import com.xcs.spring.bean.MyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
public class MyService {
@Autowired
@Lazy
private MyBean myBean;
public void show() {
System.out.println("MyBean Class = " + myBean.getClass());
myBean.show();
}
}
================================================
FILE: spring-annotation/spring-annotation-profile/README.md
================================================
## TODO
================================================
FILE: spring-annotation/spring-annotation-profile/pom.xml
================================================
spring-annotation
com.xcs.spring
0.0.1-SNAPSHOT
4.0.0
spring-annotation-profile
11
11
================================================
FILE: spring-annotation/spring-annotation-propertySource/README.md
================================================
## @PropertySource
- [@PropertySource](#propertysource)
- [一、基本信息](#一基本信息)
- [二、注解描述](#二注解描述)
- [三、注解源码](#三注解源码)
- [四、主要功能](#四主要功能)
- [五、最佳实践](#五最佳实践)
- [六、时序图](#六时序图)
- [七、源码分析](#七源码分析)
- [八、注意事项](#八注意事项)
- [九、总结](#九总结)
- [最佳实践总结](#最佳实践总结)
- [源码分析总结](#源码分析总结)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [我的CSDN](https://blog.csdn.net/duzhuang2399/article/details/133800438) 📚 **文章目录** - [所有文章](https://github.com/xuchengsheng/spring-reading) 🔗 **源码地址** - [@PropertySource源码](https://github.com/xuchengsheng/spring-reading/tree/master/spring-annotation/spring-annotation-propertySource)
### 二、注解描述
`@PropertySource` 注解,用于指定外部的属性文件,从而将该文件中的键值对加载到 Spring 的 `Environment` 中。这样,我们就可以在应用程序中轻松地访问和使用这些属性值。
### 三、注解源码
`@Configuration`注解是 Spring 框架自 3.1 版本开始引入的一个核心注解,`@PropertySource` 是用于在 Spring 框架中声明属性源的注解。它允许我们指定外部属性文件(例如 `.properties` 或 `.xml` 文件),并将这些文件中的键值对加载到 Spring 的 `Environment` 中,从而使得应用程序可以轻松访问和使用这些属性值。
```java
/**
* @PropertySource 是一个用于在 Spring 框架中声明属性源的注解。
* 使用此注解,我们可以指定外部的属性文件(如 .properties 或 .xml 文件),
* 从而将该文件中的键值对加载到 Spring 的 Environment 中。
* 这样,应用程序就可以轻松地访问和使用这些属性值。
*
* 通常,此注解与 @Configuration 注解类一起使用,为整个 Spring 环境提供配置属性。
*
* 示例:
* Configuration
* PropertySource("classpath:/com/myco/app.properties")
* public class AppConfig {
* // ...
* }
*
* 通过上面的配置,应用程序可以确保 `app.properties` 文件中的属性都可以在 Spring Environment 中使用。
*
* @author Chris Beams
* @author Juergen Hoeller
* @author Phillip Webb
* @author Sam Brannen
* @since 3.1
* @see PropertySources
* @see Configuration
* @see org.springframework.core.env.PropertySource
* @see org.springframework.core.env.ConfigurableEnvironment#getPropertySources()
* @see org.springframework.core.env.MutablePropertySources
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(PropertySources.class)
public @interface PropertySource {
/**
* 指定此属性源的名称。如果省略,工厂将根据底层资源生成一个名称。
*/
String name() default "";
/**
* 指定要加载的属性文件的资源位置。
* 支持传统的属性文件和XML格式,例如:"classpath:/com/myco/app.properties" 或 "file:/path/to/file.xml"。
* 不支持资源位置的通配符。
* ${...} 占位符将根据已在 Environment 中注册的所有属性源进行解析。
* 每个位置将按声明的顺序添加到封闭的 Environment 作为它自己的属性源。
*/
String[] value();
/**
* 指示是否应忽略找不到的属性资源。
* 如果属性文件是完全可选的,则 true 是合适的。
* 默认值为 false。
*/
boolean ignoreResourceNotFound() default false;
/**
* 为给定的资源指定一个特定的字符编码,例如 "UTF-8"。
*/
String encoding() default "";
/**
* 指定一个自定义的 PropertySourceFactory。
* 默认情况下,将使用标准资源文件的默认工厂。
*/
Class extends PropertySourceFactory> factory() default PropertySourceFactory.class;
}
```
### 四、主要功能
1. **加载属性文件**
+ 使我们能够指定一个或多个外部属性文件(如 `.properties` 或 `.xml` 文件),并将其加载到 Spring 的上下文中。
2. **声明资源位置**
+ `@PropertySource` 提供一个 `value` 属性,用于指定外部属性文件的位置,如 `classpath:/com/myco/app.properties` 或 `file:/path/to/file.xml`。
3. **属性覆盖**
+ 当使用多个 `@PropertySource` 注解或 `PropertySources` 注解(该注解允许我们指定多个 `@PropertySource`)时,后声明的属性源会覆盖先声明的属性源中的同名属性。
4. **处理资源找不到的情况**
+ 通过 `ignoreResourceNotFound` 属性,我们可以指定当资源找不到时是否抛出异常。这在某些属性文件是可选的情况下特别有用。
5. **指定文件编码**
+ 通过 `encoding` 属性,我们可以为资源文件指定特定的字符编码,如 "UTF-8"。
6. **自定义属性源工厂**
+ 使用 `factory` 属性,我们可以为属性源指定一个自定义的 `PropertySourceFactory`,用于解析和加载属性。
7. **支持占位符解析**
+ 在 `@PropertySource` 指定的文件中的属性值可以使用 `${...}` 占位符,这些占位符将被解析为在 `Environment` 中已经加载的其他属性的值。
8. **与 `Environment` 交互**
+ 一旦属性文件被加载到 `Environment` 中,可以通过 `@Autowired` 注入 `Environment` 来查询和使用这些属性。
### 五、最佳实践
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类。然后从中获取两个属性:`apiVersion` 和 `kind`。
```java
public class PropertySourceApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
System.out.println("apiVersion = " + context.getEnvironment().getProperty("apiVersion"));
System.out.println("kind = " + context.getEnvironment().getProperty("kind"));
}
}
```
`MyConfiguration` 类标注了 `@Configuration`,这意味着它是一个基于Java的Spring配置类。同时,它还使用 `@PropertySource` 注解来加载名为 `my-application.yml` 的外部属性文件。
```java
@Configuration
@PropertySource("classpath:my-application.yml")
public class MyConfiguration {
}
```
`my-application.yml` 文件包含以下内容
```yaml
apiVersion: v1
kind: ConfigMap
```
运行结果发现,我们从Spring应用成功地从一个YAML文件中加载了属性,并能够在应用中使用这些属性值。
```java
apiVersion = v1
kind = ConfigMap
```
### 六、时序图
~~~mermaid
sequenceDiagram
Title: @PropertySource注解时序图
ComponentScanApplication->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext(componentClasses)
创建上下文
AnnotationConfigApplicationContext->>AbstractApplicationContext: refresh()
刷新上下文
AbstractApplicationContext->>AbstractApplicationContext: invokeBeanFactoryPostProcessors(beanFactory)
调用Bean工厂后置处理器
AbstractApplicationContext->>PostProcessorRegistrationDelegate: invokeBeanFactoryPostProcessors(beanFactory,beanFactoryPostProcessors)
委托处理
PostProcessorRegistrationDelegate->>PostProcessorRegistrationDelegate: invokeBeanDefinitionRegistryPostProcessors(postProcessors,registry,applicationStartup)
注册后置处理器
PostProcessorRegistrationDelegate->>ConfigurationClassPostProcessor: postProcessBeanDefinitionRegistry(registry)
配置类后处理
ConfigurationClassPostProcessor->>ConfigurationClassPostProcessor: processConfigBeanDefinitions(registry)
处理Bean定义
ConfigurationClassPostProcessor->>ConfigurationClassParser: ConfigurationClassParser(...)
创建解析器
ConfigurationClassParser-->>ConfigurationClassPostProcessor: 返回解析器
ConfigurationClassPostProcessor->>ConfigurationClassParser: parser.parse(candidates)
解析候选项
ConfigurationClassParser->>ConfigurationClassParser: parse(metadata, String beanName)
解析元数据
ConfigurationClassParser->>ConfigurationClassParser: processConfigurationClass(configClass,filter)
处理配置类
ConfigurationClassParser->>ConfigurationClassParser: doProcessConfigurationClass(configClass,sourceClass,filter)
执行处理操作
ConfigurationClassParser->>ConfigurationClassParser: processPropertySource(propertySource)
处理属性源
ConfigurationClassParser->>DefaultPropertySourceFactory:createPropertySource(name,resource)
创建属性源
DefaultPropertySourceFactory->>ConfigurationClassParser:返回PropertySource
ConfigurationClassParser->>StandardEnvironment:getPropertySources()
获取属性源
StandardEnvironment->>ConfigurationClassParser:返回MutablePropertySources
ConfigurationClassParser->>ConfigurationClassParser: addPropertySource(propertySource)
添加属性源
ConfigurationClassParser->>MutablePropertySources:addLast(propertySource)
添加最低优先级的属性源
~~~
### 七、源码分析
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类。然后从中获取两个属性:`apiVersion` 和 `kind`。
```java
public class PropertySourceApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
System.out.println("apiVersion = " + context.getEnvironment().getProperty("apiVersion"));
System.out.println("kind = " + context.getEnvironment().getProperty("kind"));
}
}
```
在`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中,执行了三个步骤,我们重点关注`refresh()`方法。
```java
public AnnotationConfigApplicationContext(Class>... componentClasses) {
this();
register(componentClasses);
refresh();
}
```
在`org.springframework.context.support.AbstractApplicationContext#refresh`方法中我们重点关注一下`finishBeanFactoryInitialization(beanFactory)`这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。
```java
@Override
public void refresh() throws BeansException, IllegalStateException {
// ... [代码部分省略以简化]
// 调用在上下文中注册为bean的工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.AbstractApplicationContext#invokeBeanFactoryPostProcessors`方法中,又委托了`PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors()`进行调用。
```java
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors`方法中,首先调用了 `BeanDefinitionRegistryPostProcessor`(这是 `BeanFactoryPostProcessor` 的子接口)。它专门用来在所有其他 bean 定义加载之前修改默认的 bean 定义。
```java
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List beanFactoryPostProcessors) {
// ... [代码部分省略以简化]
invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry, beanFactory.getApplicationStartup());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.support.PostProcessorRegistrationDelegate#invokeBeanDefinitionRegistryPostProcessors`方法中,循环调用了实现`BeanDefinitionRegistryPostProcessor`接口中的`postProcessBeanDefinitionRegistry(registry)`方法
```java
private static void invokeBeanDefinitionRegistryPostProcessors(
Collection extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry, ApplicationStartup applicationStartup) {
for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
StartupStep postProcessBeanDefRegistry = applicationStartup.start("spring.context.beandef-registry.post-process")
.tag("postProcessor", postProcessor::toString);
postProcessor.postProcessBeanDefinitionRegistry(registry);
postProcessBeanDefRegistry.end();
}
}
```
在`org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry`方法中,调用了`processConfigBeanDefinitions`方法,该方法的主要目的是处理和注册配置类中定义的beans。
```java
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
// ... [代码部分省略以简化]
processConfigBeanDefinitions(registry);
}
```
在`org.springframework.context.annotation.ConfigurationClassPostProcessor#processConfigBeanDefinitions`方法中,这个方法主要处理了配置类的解析和验证,并确保了所有在配置类中定义的beans都被正确地注册到Spring的bean定义注册表中。
```java
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// ... [代码部分省略以简化]
// 步骤1:创建一个用于解析配置类的解析器
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// 步骤2:初始化候选配置类集合以及已解析配置类集合
Set candidates = new LinkedHashSet<>(configCandidates);
Set alreadyParsed = new HashSet<>(configCandidates.size());
// 步骤3:循环处理所有候选配置类,直至没有候选类为止
do {
// 步骤3.1 解析配置类
parser.parse(candidates);
// 步骤3.2 验证配置类
parser.validate();
// ... [代码部分省略以简化]
} while (!candidates.isEmpty());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#parse`方法中,主要是遍历所有的配置类候选者,并对每一个带有注解的Bean定义进行解析。这通常涉及到查找该配置类中的@Bean方法、组件扫描指令等,并将这些信息注册到Spring容器中。
```java
public void parse(Set configCandidates) {
// ... [代码部分省略以简化]
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
// ... [代码部分省略以简化]
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#parse(metadata, beanName)`方法中,将注解元数据和Bean名称转化为一个配置类,然后对其进行处理。处理配置类是Spring配置驱动的核心,它涉及到许多关键操作,如处理`@ComponentScan`注解等等。
```java
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#processConfigurationClass`方法中,处理一个给定的配置类。它首先递归地处理配置类及其父类,以确保所有相关的配置都被正确地读取并解析。在递归处理完所有相关配置后,它将配置类添加到已解析的配置类的映射中。
```java
protected void processConfigurationClass(ConfigurationClass configClass, Predicate filter) throws IOException {
// ... [代码部分省略以简化]
// 步骤1:递归地处理配置类及其超类层次结构
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
} while (sourceClass != null);
// 步骤2:将处理后的配置类放入映射中
this.configurationClasses.put(configClass, configClass);
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#doProcessConfigurationClass`方法中,主要任务是处理 `ConfigurationClass` 中定义的 `@PropertySource` 注解,并确保它们被正确地添加到 `ConfigurableEnvironment`。如果环境不可配置(即不是 `ConfigurableEnvironment` 的实例),则会忽略该注解并记录相关信息。
```java
protected final SourceClass doProcessConfigurationClass(
ConfigurationClass configClass, SourceClass sourceClass, Predicate filter)
throws IOException {
// 处理任何 @PropertySource 注解
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
// 检查当前环境是否为ConfigurableEnvironment的实例
if (this.environment instanceof ConfigurableEnvironment) {
// 如果是,则处理该属性源
processPropertySource(propertySource);
}
else {
// 否则,记录一条信息,说明正在忽略@PropertySource注解
logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
"]. Reason: Environment must implement ConfigurableEnvironment");
}
}
// 没有超类 -> 处理完成
return null;
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#processPropertySource`方法中,首先解析 `@PropertySource` 注解的各个属性,如 `name`、`encoding`、`value` 和 `factory`。然后,它尝试加载每个指定的属性文件,并根据文件的内容和其他相关属性创建一个属性源,最后将这个属性源添加到Spring环境中。
```java
/**
* 处理给定的@PropertySource注解属性。
*
* @param propertySource @PropertySource注解的属性
* @throws IOException 如果处理时发生IO异常
*/
private void processPropertySource(AnnotationAttributes propertySource) throws IOException {
// 获取"name"属性,如果没有指定,设置为null
String name = propertySource.getString("name");
if (!StringUtils.hasLength(name)) {
name = null;
}
// 获取"encoding"属性,如果没有指定,设置为null
String encoding = propertySource.getString("encoding");
if (!StringUtils.hasLength(encoding)) {
encoding = null;
}
// 获取"value"属性,它表示属性文件的位置
String[] locations = propertySource.getStringArray("value");
// 确保至少指定了一个位置
Assert.isTrue(locations.length > 0, "At least one @PropertySource(value) location is required");
// 获取"ignoreResourceNotFound"属性,表示如果资源找不到是否应该忽略
boolean ignoreResourceNotFound = propertySource.getBoolean("ignoreResourceNotFound");
// 获取并实例化PropertySourceFactory,用于创建属性源
Class extends PropertySourceFactory> factoryClass = propertySource.getClass("factory");
PropertySourceFactory factory = (factoryClass == PropertySourceFactory.class ?
DEFAULT_PROPERTY_SOURCE_FACTORY : BeanUtils.instantiateClass(factoryClass));
// 对于每一个指定的位置:
for (String location : locations) {
try {
// 解析位置中的占位符
String resolvedLocation = this.environment.resolveRequiredPlaceholders(location);
// 获取资源
Resource resource = this.resourceLoader.getResource(resolvedLocation);
// 添加属性源到环境中
addPropertySource(factory.createPropertySource(name, new EncodedResource(resource, encoding)));
}
catch (IllegalArgumentException | FileNotFoundException | UnknownHostException | SocketException ex) {
// 当尝试打开资源时,占位符不可解析或资源找不到
if (ignoreResourceNotFound) {
// 如果忽略资源找不到,则记录一条信息
if (logger.isInfoEnabled()) {
logger.info("Properties location [" + location + "] not resolvable: " + ex.getMessage());
}
}
else {
// 否则,抛出异常
throw ex;
}
}
}
}
```
在`org.springframework.core.io.support.DefaultPropertySourceFactory#createPropertySource`方法中,如果提供了名称,它将为该属性源使用该名称。如果没有提供名称,它将只基于资源创建一个属性源,属性源的名称可能会基于资源自己的描述或其他默认方式来确定。
```java
@Override
public PropertySource> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
return (name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource));
}
```
在`org.springframework.context.annotation.ConfigurationClassParser#addPropertySource`方法中,首先检查是否已经添加了具有相同名称的属性源。如果是,则它会将新的属性源与现有的属性源合并。如果没有,它将按照适当的顺序添加新的属性源。
```java
private void addPropertySource(PropertySource> propertySource) {
// 获取属性源的名称
String name = propertySource.getName();
// 获取当前环境的所有属性源
MutablePropertySources propertySources = ((ConfigurableEnvironment) this.environment).getPropertySources();
// 如果此名称的属性源已经被添加过
if (this.propertySourceNames.contains(name)) {
// 我们已经添加了一个版本,需要扩展它
PropertySource> existing = propertySources.get(name);
if (existing != null) {
PropertySource> newSource = (propertySource instanceof ResourcePropertySource ?
((ResourcePropertySource) propertySource).withResourceName() : propertySource);
// 如果现有的属性源是CompositePropertySource
if (existing instanceof CompositePropertySource) {
((CompositePropertySource) existing).addFirstPropertySource(newSource);
}
else {
// 创建一个新的CompositePropertySource并将新的和现有的属性源添加进去
if (existing instanceof ResourcePropertySource) {
existing = ((ResourcePropertySource) existing).withResourceName();
}
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(newSource);
composite.addPropertySource(existing);
propertySources.replace(name, composite);
}
return;
}
}
// 如果还没有添加任何属性源
if (this.propertySourceNames.isEmpty()) {
propertySources.addLast(propertySource);
}
else {
// 添加属性源到上次处理的属性源之前
String firstProcessed = this.propertySourceNames.get(this.propertySourceNames.size() - 1);
propertySources.addBefore(firstProcessed, propertySource);
}
// 将属性源名称添加到跟踪的名称列表中
this.propertySourceNames.add(name);
}
```
### 八、注意事项
1. **文件位置**
+ 确保你提供的文件路径是正确的。例如,`classpath:` 前缀表示文件应该在类路径中,而 `file:` 前缀则表示文件应该在文件系统的特定位置。
2. **占位符**
+ 在 `@PropertySource` 的 `value` 属性中,你可以使用 `${...}` 占位符,它们将会被已注册的任何属性源解析。
3. **处理重复的属性源名称**
+ 如果你有多个 `@PropertySource` 注解(或使用 `@PropertySources` 注解)且它们具有相同的名称,那么它们会合并。后声明的 `@PropertySource` 将覆盖先前声明的同名 `@PropertySource`。
4. **属性源的顺序**
+ 属性源的顺序很重要,因为在多个属性源中定义的同名属性将使用先找到的值。你可以使用 `PropertySource` 的 `name` 属性来明确指定属性源的名称,以控制其在环境中的顺序。
5. **忽略找不到的资源**
+ 你可以使用 `ignoreResourceNotFound` 属性来指定当属性文件找不到时是否应该抛出异常。默认情况下,这是 `false`,意味着如果属性文件找不到,会抛出异常。设置为 `true` 可以让Spring在找不到文件时安静地继续运行。
6. **字符编码**
+ 从Spring 4.3开始,`@PropertySource` 注解有一个 `encoding` 属性,允许你为给定的资源指定特定的字符编码。
7. **自定义属性源工厂**
+ 如果你需要特殊的逻辑来创建属性源,可以使用 `factory` 属性来指定一个自定义的 `PropertySourceFactory`。
8. **激活属性占位符解析**
+ 仅仅使用 `@PropertySource` 并不会激活属性占位符解析。为了替换你的bean定义中的 `${...}` 占位符,你还需要添加 `@Bean` 定义为 `PropertySourcesPlaceholderConfigurer`。
9. **与Profiles结合**
+ 你可以与Spring的Profile功能结合使用 `@PropertySource`,以根据不同的环境加载不同的属性文件。
### 九、总结
#### 最佳实践总结
1. **初始化上下文**
+ 使用 `AnnotationConfigApplicationContext` 作为Spring容器的初始化方式,它允许从Java注解中配置Spring容器。
2. **定义配置类**
+ 创建一个名为 `MyConfiguration` 的类,并使用 `@Configuration` 注解来标记它,表示这是一个基于Java的Spring配置类。
3. **指定属性源**
+ 在 `MyConfiguration` 类中,使用 `@PropertySource` 注解指定了一个外部属性文件的位置,文件名为 `my-application.yml`。
4. **定义属性内容**
+ 在 `my-application.yml` 文件中定义了两个属性:`apiVersion` 和 `kind`。
5. **加载并访问属性**
+ 运行应用程序后,使用Spring的 `Environment` API来访问这些属性,并将其输出到控制台。
6. **运行结果**
+ 从YAML文件成功加载的属性在控制台上显示为 `apiVersion = v1` 和 `kind = ConfigMap`。
#### 源码分析总结
1. **上下文初始化**
+ 通过使用 `AnnotationConfigApplicationContext`, Spring 上下文被初始化。该类允许Spring容器从Java注解中进行配置。传递的配置类是 `MyConfiguration`,该类定义了要从中加载的属性源。
2. **处理@PropertySource注解**
+ 在配置类处理期间,Spring 检查每个配置类以查找 `@PropertySource` 注解。如果找到,则属性源的相关数据(例如其位置和其他属性)被提取出来。
3. **资源位置解析**
+ 对于 `@PropertySource` 注解中定义的每个属性文件位置,Spring 尝试加载和解析该文件。它首先解析任何占位符,然后尝试加载资源。
4. **创建属性源**
+ 使用 `DefaultPropertySourceFactory`, Spring 创建一个属性源。这个工厂可以从给定的资源(例如 .properties 或 .yml 文件)创建一个属性源。
5. **向环境中添加属性源**
+ 一旦属性源被创建,它就被添加到Spring的运行时环境中。如果已经存在具有相同名称的属性源,新的属性源可能会与旧的合并,或者会以适当的方式进行处理。
6. **处理完成**
+ 在完成所有配置类和属性源的处理之后,Spring上下文继续其正常的启动过程,最终在应用程序运行时提供这些属性。
================================================
FILE: spring-annotation/spring-annotation-propertySource/pom.xml
================================================
spring-annotation
com.xcs.spring
0.0.1-SNAPSHOT
4.0.0
spring-annotation-propertySource
================================================
FILE: spring-annotation/spring-annotation-propertySource/src/main/java/com/xcs/spring/PropertySourceApplication.java
================================================
package com.xcs.spring;
import com.xcs.spring.config.MyConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 2023年08月07日 16时21分
**/
public class PropertySourceApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
System.out.println("apiVersion = " + context.getEnvironment().getProperty("apiVersion"));
System.out.println("kind = " + context.getEnvironment().getProperty("kind"));
}
}
================================================
FILE: spring-annotation/spring-annotation-propertySource/src/main/java/com/xcs/spring/config/MyConfiguration.java
================================================
package com.xcs.spring.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* @author xcs
* @date 2023年08月07日 16时25分
**/
@Configuration
@PropertySource("classpath:my-application.yml")
public class MyConfiguration {
}
================================================
FILE: spring-annotation/spring-annotation-propertySource/src/main/resources/my-application.yml
================================================
apiVersion: v1
kind: ConfigMap
================================================
FILE: spring-annotation/spring-annotation-value/README.md
================================================
## @Value
- [@Value](#value)
- [一、基本信息](#一基本信息)
- [二、注解描述](#二注解描述)
- [三、注解源码](#三注解源码)
- [四、主要功能](#四主要功能)
- [五、最佳实践](#五最佳实践)
- [六、时序图](#六时序图)
- [七、源码分析](#七源码分析)
- [前置条件](#前置条件)
- [收集阶段](#收集阶段)
- [注入阶段](#注入阶段)
- [八、注意事项](#八注意事项)
- [九、总结](#九总结)
- [最佳实践总结](#最佳实践总结)
- [源码分析总结](#源码分析总结)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [我的CSDN](https://blog.csdn.net/duzhuang2399/article/details/133815435) 📚 **文章目录** - [所有文章](https://github.com/xuchengsheng/spring-reading) 🔗 **源码地址** - [@Value源码](https://github.com/xuchengsheng/spring-reading/tree/master/spring-annotation/spring-annotation-value)
### 二、注解描述
`@Value` 注解,是一个非常有用的功能,它允许我们从配置文件中注入属性值到Java类的字段或方法参数中。这样,我们可以将配置和代码分离,使应用更容易配置和维护。
### 三、注解源码
`@Value`注解是 Spring 框架自 3.1 版本开始引入的一个核心注解,主要目的是允许我们在Spring管理的bean中直接注入来自各种源(如属性文件、系统属性等)的值,而不需要显式地编写代码来解析这些值。
```java
/**
* 用于字段或方法/构造函数参数级别的注解,
* 表示被注解元素的默认值表达式。
*
* 通常用于基于表达式或属性的依赖注入。
* 也支持动态解析处理器方法参数,例如在Spring MVC中。
*
* 常见的使用场景是使用 `#{systemProperties.myProp}` 这样的SpEL(Spring表达式语言)表达式来注入值。
* 或者,使用 `${my.app.myProp}` 这样的属性占位符来注入值。
*
* 注意,@Value 注解的实际处理是由 BeanPostProcessor 执行的,
* 这意味着我们**不能**在 BeanPostProcessor 或 BeanFactoryPostProcessor 类型中使用 @Value。
* 请查阅 AutowiredAnnotationBeanPostProcessor 类的javadoc(默认检查此注解的存在)。
*
* @author Juergen Hoeller
* @since 3.0
* @see AutowiredAnnotationBeanPostProcessor
* @see Autowired
* @see org.springframework.beans.factory.config.BeanExpressionResolver
* @see org.springframework.beans.factory.support.AutowireCandidateResolver#getSuggestedValue
*/
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Value {
/**
* 实际的值表达式,如 `#{systemProperties.myProp}`,
* 或属性占位符,如 `${my.app.myProp}`。
*/
String value();
}
```
### 四、主要功能
1. **提供属性注入**
+ 允许从不同的配置源(如属性文件、系统属性等)直接向 Spring 管理的 beans 中注入值。
2. **支持表达式**:
- SpEL (Spring Expression Language) 表达式:例如,`#{systemProperties.myProp}` 可以从系统属性中获取名为 `myProp` 的值。
- 属性占位符:例如,`${my.app.myProp}` 可以从预定义的配置源,如 `application.properties` 或 `application.yml` 文件,获取名为 `my.app.myProp` 的属性值。
3. **动态值解析**
+ 与只能在启动时设置静态值相比,`@Value` 注解可以解析动态表达式,从而为字段或构造函数参数提供动态值。
4. **用于字段、方法参数、构造函数参数和注解**
+ 它可以被应用到这些元素上,以提供必要的值。
5. **与其他注解协同工作**
+ 尽管 `@Value` 本身是用于注入值的,但它经常与其他如 `@Component`、`@Service` 和 `@Controller` 之类的 Spring 注解一起使用,以创建完全由 Spring 管理和配置的 beans。
6. **与属性解析器配合**
+ 为了正确解析 `@Value` 中的表达式,Spring 应用上下文中需要有一个属性解析器,例如 `PropertySourcesPlaceholderConfigurer`。在 Spring Boot 项目中,这已经默认配置好了。
### 五、最佳实践
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类。
```java
public class ValueApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
}
}
```
这里使用`@Bean`注解,定义了一个Bean,是为了确保 `MyService` 被 Spring 容器执行,另外使用`@PropertySource`注解从类路径下的`application.properties`文件中加载属性。这意味着我们可以在这个文件中定义属性,然后在应用中使用`Environment`对象来访问它们。
```java
@Configuration
@PropertySource("classpath:application.properties")
public class MyConfiguration {
@Bean
public MyService myService(){
return new MyService();
}
}
```
`application.properties`文件在`src/main/resources`目录中,并添加以下内容。
```properties
app.name=My Spring Application
app.servers=server1,server2,server3
app.val1=10
app.val2=20
```
`MyService`类,展示了如何使用`@Value`注解的五种不同方式进行属性注入。
```java
public class MyService implements InitializingBean {
/**
* 直接注入值
*/
@Value("Some String Value")
private String someString;
/**
* 从属性文件中注入值方式
*/
@Value("${app.name}")
private String appName;
/**
* 使用默认值方式
*/
@Value("${app.description:我是默认值}")
private String appDescription;
/**
* 注入列表和属性
*/
@Value("#{'${app.servers}'.split(',')}")
private List servers;
/**
* 使用Spring的SpEL
*/
@Value("#{${app.val1} + ${app.val2}}")
private int sumOfValues;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("直接注入值: " + someString);
System.out.println("从属性文件中注入值: " + appName);
System.out.println("使用默认值: " + appDescription);
System.out.println("注入列表和属性: " + servers);
System.out.println("使用Spring的SpEL: " + sumOfValues);
}
}
```
运行结果发现, `@Value` 注解都被正确地解析并注入了预期的值。
```java
直接注入值: Some String Value
从属性文件中注入值: My Spring Application
使用默认值: 我是默认值
注入列表和属性: [server1, server2, server3]
使用Spring的SpEL: 30
```
### 六、时序图
~~~mermaid
sequenceDiagram
Title: @Value注解时序图
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:applyMergedBeanDefinitionPostProcessors(mbd,beanType,beanName)
应用Bean定义的后置处理器
AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessMergedBeanDefinition(beanDefinition,beanType,beanName)
处理已合并的Bean定义
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs)
查找自动注入的元数据
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:buildAutowiringMetadata(clazz)
构建自动注入的元数据
AutowiredAnnotationBeanPostProcessor->>ReflectionUtils:doWithLocalFields(clazz,fc)
处理类的本地字段
ReflectionUtils->>AutowiredAnnotationBeanPostProcessor:解析有@Value注解的字段
AutowiredAnnotationBeanPostProcessor->>ReflectionUtils:doWithLocalMethods(clazz,fc)
处理类的本地方法
ReflectionUtils->>AutowiredAnnotationBeanPostProcessor:解析有@Value注解的方法
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:injectionMetadataCache.put(cacheKey, metadata)
将元数据存入缓存
AbstractAutowireCapableBeanFactory->>AbstractAutowireCapableBeanFactory:populateBean(beanName,mbd,bw)
填充Bean的属性值
AbstractAutowireCapableBeanFactory->>AutowiredAnnotationBeanPostProcessor:postProcessProperties(pvs,bean,beanName)
后处理Bean的属性
AutowiredAnnotationBeanPostProcessor->>AutowiredAnnotationBeanPostProcessor:findAutowiringMetadata(beanName,clazz,pvs)
再次查找自动注入的元数据
Note right of AutowiredAnnotationBeanPostProcessor:
从缓存中获取注入的元数据
AutowiredAnnotationBeanPostProcessor->>InjectionMetadata:inject(bean, beanName, pvs)
执行实际的属性注入
InjectionMetadata->>AutowiredFieldElement:inject(target, beanName, pvs)
注入特定的字段元素
AutowiredFieldElement->>AutowiredFieldElement:resolveFieldValue(field,bean,beanName)
解析字段的值
AutowiredFieldElement->>DefaultListableBeanFactory:resolveDependency(desc, beanName, autowiredBeanNames, typeConverter)
解析字段的依赖
DefaultListableBeanFactory->>AutowiredFieldElement:返回解析后的value值
返回解析后的属性值
AutowiredFieldElement->>Field:field.set(bean, value)
设置Bean字段的值
~~~
### 七、源码分析
#### 前置条件
在Spring中,`AutowiredAnnotationBeanPostProcessor`是处理`@Value`等注解的关键类,它实现了下述两个接口。因此,为了深入理解`@Value`的工作方式,研究这个类是非常有用的。简而言之,为了完全理解`@Value`的工作机制,了解下述接口确实是必要的。这两个接口提供了对bean生命周期中关键阶段的干预,从而允许进行属性注入和其他相关的操作。
1. **`MergedBeanDefinitionPostProcessor`接口**
+ 此接口提供的`postProcessMergedBeanDefinition`方法允许后处理器修改合并后的bean定义。合并后的bean定义是一个已经考虑了所有父bean定义属性的bean定义。对于`@Value`注解的处理,这一步通常涉及到收集需要被解析的`@Value`注解信息并准备对其进行后续处理。
+ 🔗 [MergedBeanDefinitionPostProcessor接口传送门](https://github.com/xuchengsheng/spring-reading/tree/master/spring-interface/spring-interface-mergedBeanDefinitionPostProcessor)
2. **`InstantiationAwareBeanPostProcessor`接口**
+ 此接口提供了几个回调方法,允许后处理器在bean实例化之前和实例化之后介入bean的创建过程。特别是,`postProcessProperties`方法允许后处理器对bean的属性进行操作。对于`@Value`注解,这通常涉及到实际地解析注解中的表达式或属性占位符,并将解析得到的值注入到bean中。
+ 🔗 [InstantiationAwareBeanPostProcessor接口传送门](https://github.com/xuchengsheng/spring-reading/tree/master/spring-interface/spring-interface-instantiationAwareBeanPostProcessor)
#### 收集阶段
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition`方法中,主要确保给定的bean定义与其预期的自动装配元数据一致。
```java
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class> beanType, String beanName) {
// 对于给定的bean名称和类型,它首先尝试查找相关的InjectionMetadata,这可能包含了该bean的字段和方法的注入信息
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
// 使用找到的InjectionMetadata来验证bean定义中的配置成员是否与预期的注入元数据匹配。
metadata.checkConfigMembers(beanDefinition);
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata`方法中,确保了始终为给定的bean名称和类获取最新和相关的`InjectionMetadata`,并利用缓存机制优化性能。
```java
private InjectionMetadata findAutowiringMetadata(String beanName, Class> clazz, @Nullable PropertyValues pvs) {
// 如果beanName为空,则使用类名作为缓存键。
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// 首先尝试从并发缓存中获取InjectionMetadata。
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
// 检查获取到的元数据是否需要刷新。
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
// 使用双重检查锁定确保线程安全。
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
// 如果有旧的元数据,清除它。
if (metadata != null) {
metadata.clear(pvs);
}
// 为给定的类构建新的InjectionMetadata。
metadata = buildAutowiringMetadata(clazz);
// 将新构建的元数据更新到缓存中。
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
// 返回找到的或新构建的元数据。
return metadata;
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata`方法中,查找类及其所有父类中的字段和方法,以找出所有带有自动装配注解的字段和方法,并为它们创建一个统一的`InjectionMetadata`对象。
```java
private InjectionMetadata buildAutowiringMetadata(final Class> clazz) {
// 检查类是否含有自动装配注解,若无则直接返回空的InjectionMetadata。
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
// 初始化存放注入元素的列表。
List elements = new ArrayList<>();
Class> targetClass = clazz;
do {
// 当前类中要注入的元素列表。
final List currElements = new ArrayList<>();
// 处理类中的所有字段。
ReflectionUtils.doWithLocalFields(targetClass, field -> {
// 查找字段上的自动装配注解。
MergedAnnotation> ann = findAutowiredAnnotation(field);
if (ann != null) {
// 忽略静态字段。
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static fields: " + field);
}
return;
}
boolean required = determineRequiredStatus(ann);
// 创建一个新的AutowiredFieldElement并加入到列表。
currElements.add(new AutowiredFieldElement(field, required));
}
});
// 处理类中的所有方法。
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
// 查找方法上的自动装配注解。
MergedAnnotation> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
// 忽略静态方法。
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static methods: " + method);
}
return;
}
// 只处理带参数的方法。
if (method.getParameterCount() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation should only be used on methods with parameters: " +
method);
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
// 创建一个新的AutowiredMethodElement并加入到列表。
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
// 将当前类的注入元素加入到总的注入元素列表的开头。
elements.addAll(0, currElements);
// 处理父类。
targetClass = targetClass.getSuperclass();
}
// 循环直至Object类。
while (targetClass != null && targetClass != Object.class);
// 返回为元素列表创建的新的InjectionMetadata。
return InjectionMetadata.forElements(elements, clazz);
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#autowiredAnnotationTypes`字段中,主要的用途是告诉`AutowiredAnnotationBeanPostProcessor`哪些注解它应该处理。当Spring容器解析bean定义并创建bean实例时,如果这个bean的字段、方法或构造函数上的注解被包含在这个`autowiredAnnotationTypes`集合中,那么`AutowiredAnnotationBeanPostProcessor`就会对它进行处理。
```java
public AutowiredAnnotationBeanPostProcessor() {
this.autowiredAnnotationTypes.add(Value.class);
// ... [代码部分省略以简化]
}
```
#### 注入阶段
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties`方法中,用于处理bean属性的后处理,特别是通过`@Value`等注解进行的属性注入。
```java
@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
// 获取与bean名称和类相关的InjectionMetadata。
// 这包括该bean需要进行注入的所有字段和方法。
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
// 使用获取到的InjectionMetadata,实际进行属性的注入。
metadata.inject(bean, beanName, pvs);
}
// 如果在注入过程中出现BeanCreationException,直接抛出。
catch (BeanCreationException ex) {
throw ex;
}
// 捕获其他异常,并以BeanCreationException的形式抛出,提供详细的错误信息。
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
// 返回原始的PropertyValues,因为这个方法主要关注依赖注入而不是修改属性。
return pvs;
}
```
在`org.springframework.beans.factory.annotation.InjectionMetadata#inject`方法中,主要目的是将所有需要注入的元素(例如带有`@Value`等注解的字段或方法)注入到目标bean中。
```java
public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// 获取已经检查的元素。通常,在初始化阶段,所有的元素都会被检查一次。
Collection checkedElements = this.checkedElements;
// 如果已经有检查过的元素,则使用它们,否则使用所有注入的元素。
Collection elementsToIterate =
(checkedElements != null ? checkedElements : this.injectedElements);
// 如果有需要注入的元素...
if (!elementsToIterate.isEmpty()) {
// 遍历每个元素并注入到目标bean中。
for (InjectedElement element : elementsToIterate) {
// 对每个元素(字段或方法)执行注入操作。
element.inject(target, beanName, pvs);
}
}
}
```
在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject`方法中,首先检查字段的值是否已经被缓存。如果已缓存,则从缓存中获取,否则重新解析。然后,它确保字段是可访问的(特别是对于私有字段),并将解析的值设置到目标bean的相应字段中。
```java
@Override
protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
// 步骤1. 获取代表带有@Autowired注解的字段的Field对象。
Field field = (Field) this.member;
Object value;
// 步骤2. 如果字段的值已经被缓存(即先前已解析过),则尝试从缓存中获取。
if (this.cached) {
try {
// 从缓存中获取已解析的字段值。
value = resolvedCachedArgument(beanName, this.cachedFieldValue);
}
catch (NoSuchBeanDefinitionException ex) {
// 如果缓存中的bean已被意外删除 -> 重新解析。
value = resolveFieldValue(field, bean, beanName);
}
}
else {
// 步骤3. 如果字段值未被缓存,直接解析。
value = resolveFieldValue(field, bean, beanName);
}
// 步骤4. 如果解析到的值不为null...
if (value != null) {
// 步骤4.1. 使字段可访问,这是必要的,特别是当字段是private时。
ReflectionUtils.makeAccessible(field);
// 步骤4.2. 实际将解析的值注入到目标bean的字段中。
field.set(bean, value);
}
}
```
首先来到`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject`方法中的步骤3。在`org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#resolveFieldValue`方法中,通过`beanFactory.resolveDependency`方法从Spring的bean工厂中解析字段的值。
```java
@Nullable
private Object resolveFieldValue(Field field, Object bean, @Nullable String beanName) {
// ... [代码部分省略以简化]
Object value;
try {
// 通过`beanFactory.resolveDependency`方法从Spring的bean工厂中解析字段的值
value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
}
catch (BeansException ex) {
throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
}
// ... [代码部分省略以简化]
return value;
}
```
在`org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency`方法中,首先尝试获取一个延迟解析代理。如果无法获得,它会进一步尝试解析依赖。`doResolveDependency` 是实际进行解析工作的方法。
```java
public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
// ... [代码部分省略以简化]
Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
descriptor, requestingBeanName);
if (result == null) {
result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
}
return result;
}
```
在`org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency`方法中,首先从`DependencyDescriptor`获取注解值,然后处理其中的字符串属性占位符和SpEL表达式。最后,确保值根据目标字段或参数类型进行正确的类型转换,并将其注入相应的位置。
```java
@Nullable
public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
@Nullable Set autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
// ... [其他代码部分省略以简化]
try {
// 尝试快速解析依赖
Object shortcut = descriptor.resolveShortcut(this);
if (shortcut != null) {
return shortcut;
}
// 获取依赖的类型
Class> type = descriptor.getDependencyType();
// 步骤1. 获取依赖的建议值,例如@Value注解的值
Object value = getAutowireCandidateResolver().getSuggestedValue(descriptor);
// 如果建议的值是字符串类型
if (value instanceof String) {
// 步骤2. 解析嵌入的值,如处理属性占位符
String strVal = resolveEmbeddedValue((String) value);
// 获取与bean名称相关的BeanDefinition
BeanDefinition bd = (beanName != null && containsBean(beanName) ?
getMergedBeanDefinition(beanName) : null);
// 步骤3. 对Bean定义字符串进行评估,如处理SpEL表达式
value = evaluateBeanDefinitionString(strVal, bd);
}
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
try {
// 步骤4. 获取类型转换器并进行必要的类型转换
return converter.convertIfNecessary(value, type, descriptor.getTypeDescriptor());
}
catch (UnsupportedOperationException ex) {
// ... [其他代码部分省略以简化]
}
// ... [其他代码部分省略以简化]
}
// ... [其他代码部分省略以简化]
}
```
首先来到在`org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency`方法中的步骤1。在`org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#getSuggestedValue`方法中,主要是用于解析与`DependencyDescriptor`相关的注解值,特别是`@Value`注解。如果字段或方法参数上有`@Value`注解,它会从注解中提取相应的值或表达式。
```java
@Override
@Nullable
public Object getSuggestedValue(DependencyDescriptor descriptor) {
// 从描述符的注解中查找@Value注解提供的值
Object value = findValue(descriptor.getAnnotations());
// 如果在描述符的注解中没有找到,检查是否存在与此描述符关联的方法参数
if (value == null) {
MethodParameter methodParam = descriptor.getMethodParameter();
if (methodParam != null) {
// 如果存在方法参数,再从方法参数的注解中查找@Value提供的值
value = findValue(methodParam.getMethodAnnotations());
}
}
// 返回找到的值,如果没有找到则返回null
return value;
}
```
在`org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#findValue`方法中,目标是在提供的注解集合中找到并返回`@Value`注解的值。如果没有找到,它会返回null。
```java
protected Object findValue(Annotation[] annotationsToSearch) {
if (annotationsToSearch.length > 0) { // qualifier annotations have to be local
AnnotationAttributes attr = AnnotatedElementUtils.getMergedAnnotationAttributes(
AnnotatedElementUtils.forAnnotations(annotationsToSearch), this.valueAnnotationType);
if (attr != null) {
return extractValue(attr);
}
}
return null;
}
```
在`org.springframework.beans.factory.annotation.QualifierAnnotationAutowireCandidateResolver#extractValue`方法中,目的是从`AnnotationAttributes`对象中直接提取`@Value`注解的值。如果没有提供值,它会抛出异常。
```java
protected Object extractValue(AnnotationAttributes attr) {
Object value = attr.get(AnnotationUtils.VALUE);
if (value == null) {
throw new IllegalStateException("Value annotation must have a value attribute");
}
return value;
}
```
当我们使用 `@Value("${app.description:我是默认值}")` 在你的字段上时,Spring 会在运行时尝试解析这个属性占位符。当 Spring 容器处理这个字段的注入时,它会使用 `QualifierAnnotationAutowireCandidateResolver`(或其他相关的后处理器)来获取并解析这个属性值。在我们的最佳实践下,`extractValue` 方法就是从注解属性中提取该属性占位符的逻辑,即返回值为 `"${app.description:我是默认值}"`。这个值随后会被 Spring 的属性解析器进一步处理,解析真实的值或使用默认值,并最终注入到 `appDescription` 字段中。
```java
${app.description:我是默认值}
```
然后我们来到在`org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency`方法中的步骤2。在`org.springframework.beans.factory.support.AbstractBeanFactory#resolveEmbeddedValue`方法中,用于解析给定字符串中的内嵌值。它遍历所有注册的`StringValueResolver`解析器,对给定的字符串值进行连续解析,以处理可能存在的多重内嵌值或引用。例如,如果字符串中有一个`${property}`形式的属性,它可以通过注册的解析器进行处理和解析为实际的属性值。
```java
@Override
@Nullable
public String resolveEmbeddedValue(@Nullable String value) {
// 初始检查:如果提供的值为null,则直接返回null
if (value == null) {
return null;
}
String result = value;
// 遍历所有的内嵌值解析器
for (StringValueResolver resolver : this.embeddedValueResolvers) {
// 使用当前解析器解析result中的值
result = resolver.resolveStringValue(result);
// 如果解析后的值为null,则直接返回null
if (result == null) {
return null;
}
}
// 返回所有解析器处理后的最终值
return result;
}
```
然后我们来到在`org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency`方法中的步骤3。在`org.springframework.beans.factory.support.AbstractBeanFactory#evaluateBeanDefinitionString`方法中,用于评估给定的字符串值,特别是处理可能包含Spring表达式语言 (SpEL) 表达式的字符串。首先,它检查是否有一个`beanExpressionResolver`可用来解析SpEL。如果有,它可能会获取bean定义的作用域(如果提供了bean定义),然后使用`beanExpressionResolver`对字符串值进行评估,并考虑到相关的作用域上下文。
```java
@Nullable
protected Object evaluateBeanDefinitionString(@Nullable String value, @Nullable BeanDefinition beanDefinition) {
// 如果没有设置bean表达式解析器,直接返回原始值
if (this.beanExpressionResolver == null) {
return value;
}
Scope scope = null;
// 如果提供了bean定义
if (beanDefinition != null) {
// 获取bean的作用域
String scopeName = beanDefinition.getScope();
// 如果作用域名称不为空,则尝试从已注册的作用域中获取对应的作用域
if (scopeName != null) {
scope = getRegisteredScope(scopeName);
}
}
// 使用bean表达式解析器解析提供的值,并返回结果
// 这可以处理例如使用Spring EL的情况
return this.beanExpressionResolver.evaluate(value, new BeanExpressionContext(this, scope));
}
```
在`org.springframework.context.expression.StandardBeanExpressionResolver#evaluate`方法中,主要目的是解析并评估给定的值(可能是一个Spring EL表达式)。为了提高性能,它使用缓存来存储先前解析的表达式和评估上下文。此方法首先从缓存中检索或解析表达式,然后准备一个评估上下文,并使用它评估表达式。这个评估上下文被配置为能够访问与Spring容器相关的各种内容,如beans、环境属性等。
```java
@Override
@Nullable
public Object evaluate(@Nullable String value, BeanExpressionContext evalContext) throws BeansException {
// 如果提供的值为空或没有内容,直接返回该值
if (!StringUtils.hasLength(value)) {
return value;
}
try {
// 从缓存中尝试获取表达式
Expression expr = this.expressionCache.get(value);
// 如果缓存中没有表达式,则使用表达式解析器解析该值,并将其放入缓存中
if (expr == null) {
expr = this.expressionParser.parseExpression(value, this.beanExpressionParserContext);
this.expressionCache.put(value, expr);
}
// 尝试从缓存中获取评估上下文
StandardEvaluationContext sec = this.evaluationCache.get(evalContext);
// 如果缓存中没有评估上下文,则创建一个新的,并进行一些初始化配置
if (sec == null) {
sec = new StandardEvaluationContext(evalContext);
// 添加各种属性访问器以支持对特定类型的属性的访问
sec.addPropertyAccessor(new BeanExpressionContextAccessor());
sec.addPropertyAccessor(new BeanFactoryAccessor());
sec.addPropertyAccessor(new MapAccessor());
sec.addPropertyAccessor(new EnvironmentAccessor());
// 设置bean解析器和类型定位器
sec.setBeanResolver(new BeanFactoryResolver(evalContext.getBeanFactory()));
sec.setTypeLocator(new StandardTypeLocator(evalContext.getBeanFactory().getBeanClassLoader()));
// 如果有可用的转换服务,则设置类型转换器
ConversionService conversionService = evalContext.getBeanFactory().getConversionService();
if (conversionService != null) {
sec.setTypeConverter(new StandardTypeConverter(conversionService));
}
// 自定义评估上下文,允许子类提供额外的配置
customizeEvaluationContext(sec);
// 将创建的评估上下文放入缓存
this.evaluationCache.put(evalContext, sec);
}
// 使用已准备好的评估上下文评估表达式并返回结果
return expr.getValue(sec);
}
catch (Throwable ex) {
// 如果在解析或评估过程中出现任何异常,抛出BeanExpressionException
throw new BeanExpressionException("Expression parsing failed", ex);
}
}
```
然后我们来到在`org.springframework.beans.factory.support.DefaultListableBeanFactory#doResolveDependency`方法中的步骤4。在`org.springframework.beans.TypeConverterSupport#convertIfNecessary(value,requiredType,typeDescriptor)`方法中,又重新委托给`typeConverterDelegate`进行实际的转换工作
```java
@Nullable
@Override
public T convertIfNecessary(@Nullable Object value, @Nullable Class requiredType,
@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
try {
// 委托给typeConverterDelegate进行实际的转换工作
return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);
}
catch (ConverterNotFoundException | IllegalStateException ex) {
throw new ConversionNotSupportedException(value, requiredType, ex);
}
catch (ConversionException | IllegalArgumentException ex) {
throw new TypeMismatchException(value, requiredType, ex);
}
}
```
在`org.springframework.beans.TypeConverterDelegate#convertIfNecessary(propertyName,oldValue,newValue,requiredType,typeDescriptor)`方法中,负责将一个值转换为必需的类型。首先,它会尝试查找对应的自定义编辑器。如果没有找到编辑器但设置了自定义的转换服务,它会尝试使用此服务进行转换。如果上述两步都失败,该方法还会尝试执行一些标准的转换规则,例如从字符串到枚举或从数字到其他数字类型的转换。如果所有尝试都失败,该方法会抛出相应的异常,指出不能执行所需的转换。
```java
public T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// 查找此类型的自定义编辑器
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
// 用于捕获ConversionService尝试失败的异常
ConversionFailedException conversionAttemptEx = null;
// 没有自定义编辑器,但是否指定了自定义ConversionService?
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
conversionAttemptEx = ex; // 记录转换尝试失败
}
}
}
Object convertedValue = newValue;
// 如果值不是所需类型,进行转换
if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
convertedValue instanceof String) {
TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
if (elementTypeDesc != null) {
Class> elementType = elementTypeDesc.getType();
if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
}
}
if (editor == null) {
editor = findDefaultEditor(requiredType); // 如果没有自定义编辑器,找默认的
}
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor); // 进行转换
}
// 对于特定情况尝试标准类型转换,如字符串到枚举、数字转换等
boolean standardConversion = false;
if (requiredType != null) {
if (convertedValue != null) {
// 若目标类型为Object,则直接返回转换值
if (Object.class == requiredType) {
return (T) convertedValue;
}
// 若目标类型为数组,进行数组转换
else if (requiredType.isArray()) {
if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
}
return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
}
// 如果是Collection或Map,则尝试转换集合或映射的内容
else if (convertedValue instanceof Collection) {
convertedValue = convertToTypedCollection((Collection>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
else if (convertedValue instanceof Map) {
convertedValue = convertToTypedMap((Map, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
standardConversion = true;
}
if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
convertedValue = Array.get(convertedValue, 0);
standardConversion = true;
}
if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
return (T) convertedValue.toString();
}
else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
String trimmedValue = ((String) convertedValue).trim();
if (requiredType.isEnum() && trimmedValue.isEmpty()) {
return null;
}
convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
standardConversion = true;
}
else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
convertedValue = NumberUtils.convertNumberToTargetClass((Number) convertedValue, (Class) requiredType);
standardConversion = true;
}
}
else if (requiredType == Optional.class) {
convertedValue = Optional.empty();
}
// 如果经过上述所有转换后,值仍不匹配目标类型,则抛出异常
if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
if (conversionAttemptEx != null) {
throw conversionAttemptEx;
}
else if (conversionService != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
}
throw new IllegalStateException("转换失败"); // 实际异常消息会更详细,这里简化了
}
}
if (conversionAttemptEx != null) {
throw conversionAttemptEx;
}
return (T) convertedValue;
}
```
### 八、注意事项
1. **SpEL表达式**
+ `@Value`可以用来解析Spring Expression Language (SpEL) 表达式。确保我们的表达式是正确的,以防止运行时错误。
2. **默认值**
+ 我们可以为`@Value`注解提供默认值,以防止某个属性在属性文件中未被定义。例如:`@Value("${some.property:default}")`。
3. **类型转换**
+ 确保`@Value`提供的值可以被转换为字段或方法参数的类型。Spring会尝试自动进行这种转换,但不一定总是成功。
4. **不适用于复杂类型**
+ 尽管`@Value`可以用于简单的类型(如字符串、整数、枚举等),但不应用于复杂的bean注入,这时应该使用`@Autowired`或`@Inject`。
5. **不可用于`BeanPostProcessor`或`BeanFactoryPostProcessor`**
+ `@Value`注解在`BeanPostProcessor`或`BeanFactoryPostProcessor`实现中是不起作用的,因为它们在Spring容器生命周期中的处理时机早于`@Value`的处理。
6. **占位符解析器的配置**
+ 要使用属性占位符(如`${property.name}`),需要确保已配置了`PropertySourcesPlaceholderConfigurer`或`PropertyPlaceholderConfigurer`。
7. **环境变量与系统属性**
+ 我们可以使用`@Value`来引用环境变量或系统属性,例如:`@Value("${JAVA_HOME}")`。
8. **防止注入敏感信息**
+ 不要使用`@Value`来注入敏感信息,如密码,除非它们是适当加密的。考虑使用专门的解决方案,如Spring Cloud Config的Vault集成。
9. **循环依赖**
+ 尽管与`@Autowired`不同,但需要注意的是,使用`@Value`可能间接导致循环依赖,尤其是当注入的值是其他bean的属性时。
10. **性能考虑**
+ 大量使用SpEL表达式可能对性能产生轻微的影响,因为这些表达式需要在运行时进行解析。
### 九、总结
#### 最佳实践总结
1. **启动类入口**:
- 使用`AnnotationConfigApplicationContext`来启动Spring上下文,该上下文支持基于Java注解的配置。
- 在创建上下文时,为其提供了`MyConfiguration`作为配置类。
2. **配置类**:
- `MyConfiguration`类标记为`@Configuration`,表示它提供了bean定义的配置信息。
- 使用`@PropertySource`指定一个属性文件`application.properties`来为上下文加载属性。
- 定义了一个bean:`MyService`,确保其在Spring容器中被创建和初始化。
3. **属性文件**:
- 在`application.properties`文件中定义了几个属性,这些属性可以在应用程序中使用。
4. **属性注入**:
- 在`MyService`类中,展示了如何使用`@Value`注解进行五种不同方式的属性注入,从直接注入字符串值到使用SpEL表达式。
5. **注入结果的验证**:
- 实现`InitializingBean`接口并重写`afterPropertiesSet`方法来验证注入的属性值。
- 运行应用后,该方法会打印出所有注入属性的值,从而验证`@Value`注解正确地解析并注入了预期的值。
#### 源码分析总结
1. **核心后处理器**:
+ `AutowiredAnnotationBeanPostProcessor`是处理`@Value`等注解的主要后处理器。它实现了两个关键的接口,`MergedBeanDefinitionPostProcessor`和`InstantiationAwareBeanPostProcessor`,这两个接口允许在bean的生命周期中的关键阶段进行干预,为属性注入提供了机制。
2. **收集阶段**:
- 在`postProcessMergedBeanDefinition`方法中,`AutowiredAnnotationBeanPostProcessor`确保bean的定义与预期的自动装配元数据匹配。
- `findAutowiringMetadata`方法确保为给定的bean名称和类获取相关的`InjectionMetadata`,并利用缓存机制优化性能。
- `buildAutowiringMetadata`方法检查类及其所有父类,确定带有`@Autowired`、`@Value`等注解的字段和方法,并为这些元素创建一个统一的`InjectionMetadata`对象。
3. **注入阶段**:
+ `postProcessProperties`方法用于处理bean的属性的后处理,特别是注入由`@Value`等注解标记的属性。
+ `InjectionMetadata#inject`方法用于将所有需要注入的元素(例如带有`@Value`的字段)注入到目标bean中。
+ `AutowiredFieldElement#inject`方法处理具体的字段注入,包括解析`@Value`注解中的值。
+ `DefaultListableBeanFactory#resolveDependency`方法从Spring的bean工厂中解析字段的值。
+ `DefaultListableBeanFactory#doResolveDependency`方法是实际解析工作的主要场所,涉及到处理`@Value`注解中的字符串属性占位符和SpEL表达式,并确保值经过正确的类型转换。
================================================
FILE: spring-annotation/spring-annotation-value/pom.xml
================================================
spring-annotation
com.xcs.spring
0.0.1-SNAPSHOT
4.0.0
spring-annotation-value
11
11
================================================
FILE: spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/ValueApplication.java
================================================
package com.xcs.spring;
import com.xcs.spring.config.MyConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @author xcs
* @date 2023年08月07日 16时21分
**/
public class ValueApplication {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfiguration.class);
}
}
================================================
FILE: spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/config/MyConfiguration.java
================================================
package com.xcs.spring.config;
import com.xcs.spring.service.MyService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* @author xcs
* @date 2023年10月13日 14时54分
**/
@Configuration
@PropertySource("classpath:application.properties")
public class MyConfiguration {
@Bean
public MyService myService(){
return new MyService();
}
}
================================================
FILE: spring-annotation/spring-annotation-value/src/main/java/com/xcs/spring/service/MyService.java
================================================
package com.xcs.spring.service;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import java.util.List;
/**
* @author xcs
* @date 2023年10月13日 14时55分
**/
public class MyService implements InitializingBean {
/**
* 直接注入值
*/
@Value("Some String Value")
private String someString;
/**
* 从属性文件中注入值方式
*/
@Value("${app.name}")
private String appName;
/**
* 使用默认值方式
*/
@Value("${app.description:我是默认值}")
private String appDescription;
/**
* 注入列表和属性
*/
@Value("#{'${app.servers}'.split(',')}")
private List servers;
/**
* 使用Spring的SpEL
*/
@Value("#{${app.val1} + ${app.val2}}")
private int sumOfValues;
@Value("${myapp.names[0]}")
private String firstName;
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("直接注入值: " + someString);
System.out.println("从属性文件中注入值: " + appName);
System.out.println("使用默认值: " + appDescription);
System.out.println("注入列表和属性: " + servers);
System.out.println("使用Spring的SpEL: " + sumOfValues);
System.out.println("firstName: " + firstName);
}
}
================================================
FILE: spring-annotation/spring-annotation-value/src/main/resources/application.properties
================================================
app.name=My Spring Application
app.servers=server1,server2,server3
app.val1=10
app.val2=20
myapp.names[0]=Alice
myapp.names[1]=Bob
myapp.names[2]=Charlie
================================================
FILE: spring-aop/pom.xml
================================================
spring-reading
com.xcs.spring
0.0.1-SNAPSHOT
pom
spring-aop-advisor
spring-aop-pointcut
spring-aop-advisorAdapter
spring-aop-targetSource
spring-aop-aopProxy
spring-aop-classFilter
spring-aop-methodMatcher
spring-aop-jdkProxy
spring-aop-cglibProxy
spring-aop-enableAspectJAutoProxy
spring-aop-enableLoadTimeWeaving
spring-aop-advice-methodInterceptor
spring-aop-advice-methodBeforeAdvice
spring-aop-advice-afterReturningAdvice
spring-aop-advice-throwsAdvice
spring-aop-advice-introductionInterceptor
spring-aop-aopProxyFactory
spring-aop-annotationAwareAspectJAutoProxyCreator
spring-aop-aspectInstanceFactory
spring-aop-metadataAwareAspectInstanceFactory
spring-aop-aspectJAdvisorFactory
spring-aop-beanFactoryAspectJAdvisorsBuilder
spring-aop-beanFactoryAdvisorRetrievalHelper
spring-aop-proxyFactory
spring-aop-advisorChainFactory
spring-aop-advisorAdapterRegistry
spring-aop-advised
spring-aop-aopContext
spring-aop-targetSourceCreator
spring-aop-exposeInvocationInterceptor
spring-aop-advice
spring-aop-proxyMethodInvocation
4.0.0
spring-aop
================================================
FILE: spring-aop/spring-aop-advice/README.md
================================================
## Advice
- [Advice](#advice)
- [一、基本信息](#一基本信息)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、接口源码](#四接口源码)
- [五、子接口](#五子接口)
- [六、类关系图](#六类关系图)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、基本描述
`Advice`接口是Spring AOP中的核心接口之一,用于定义在切面逻辑中要执行的操作。它允许我们在目标方法执行前、执行后、抛出异常时等不同的连接点上添加自定义的行为。`Advice`接口的实现类可以通过方法拦截器(MethodInterceptor)、前置通知(BeforeAdvice)、后置通知(AfterReturningAdvice)、异常通知(ThrowsAdvice)等方式来实现不同类型的通知逻辑。
### 三、主要功能
1. **定义通知逻辑**
+ 允许我们定义在目标方法执行前、执行后、抛出异常时等不同连接点上执行的操作。
2. **支持不同类型的通知**
+ `Advice`接口的实现类可以实现不同类型的通知逻辑,如前置通知、后置通知、环绕通知、异常通知等。
3. **与切点结合**
+ `Advice`通常与切点(Pointcut)结合使用,以确定通知应该在哪些连接点上执行。
4. **应用于Advisor**
+ `Advice`通常作为`Advisor`的一部分,与切点结合,以实现切面的逻辑。
### 四、接口源码
`Advice`接口是Spring AOP中的一个标签接口,用于定义各种类型的通知,例如拦截器。通过实现该接口,我们可以定义在方法执行前、执行后、抛出异常时等不同连接点上执行的操作,从而实现对应用程序行为的干预和控制。
```java
/**
* Advice的标签接口。实现可以是任何类型的通知,例如拦截器。
*
* 该接口用于定义通知。通知可以是在方法执行前、执行后、抛出异常时等不同连接点上执行的操作。
* 实现该接口的类可以是拦截器(Interceptors)等任何类型的通知。
*
* @author Rod Johnson
* @version $Id: Advice.java,v 1.1 2004/03/19 17:02:16 johnsonr Exp $
*/
public interface Advice {
}
```
### 五、子接口
1. **AfterAdvice(后置通知)**
+ 是一个标记接口,用于表示后置通知的类型。
2. **AfterReturningAdvice(返回后通知)**
+ 用于在目标方法成功执行并返回结果后执行自定义逻辑。
3. **BeforeAdvice(前置通知)**
+ 用于在目标方法执行前执行自定义逻辑。
4. **ConstructorInterceptor(构造器拦截器)**
+ 实现该接口的类可以在目标对象的构造器被调用时执行自定义逻辑。
5. **Interceptor(拦截器)**
+ 是一个标记接口,表示通用的拦截器类型,通常用于包装方法调用。
6. **IntroductionInterceptor(引介拦截器)**
+ 实现该接口的类可以在目标对象上添加新的方法和属性,用于实现AOP引介功能。
7. **MethodBeforeAdvice(方法前置通知)**
+ 用于在目标方法执行前执行自定义逻辑。
8. **MethodInterceptor(方法拦截器)**
+ 实现该接口的类可以在目标方法执行前、执行后以及抛出异常时进行拦截,并执行自定义的逻辑。
9. **ThrowsAdvice(异常通知)**
+ 用于在目标方法抛出异常时执行自定义逻辑。
### 六、类关系图
~~~mermaid
classDiagram
direction BT
class Advice {
<>
}
class AfterAdvice {
<>
}
class AfterReturningAdvice {
<>
}
class BeforeAdvice {
<>
}
class ConstructorInterceptor {
<>
}
class Interceptor {
<>
}
class IntroductionInterceptor {
<>
}
class MethodBeforeAdvice {
<>
}
class MethodInterceptor {
<>
}
class ThrowsAdvice {
<>
}
AfterAdvice --> Advice
AfterReturningAdvice --> AfterAdvice
BeforeAdvice --> Advice
ConstructorInterceptor --> Interceptor
Interceptor --> Advice
IntroductionInterceptor --> Advice
IntroductionInterceptor --> MethodInterceptor
MethodBeforeAdvice --> BeforeAdvice
MethodInterceptor --> Interceptor
ThrowsAdvice --> AfterAdvice
~~~
================================================
FILE: spring-aop/spring-aop-advice/pom.xml
================================================
com.xcs.spring
spring-aop
0.0.1-SNAPSHOT
4.0.0
spring-aop-advice
================================================
FILE: spring-aop/spring-aop-advice-afterReturningAdvice/README.md
================================================
## AfterReturningAdvice
- [AfterReturningAdvice](#afterreturningadvice)
- [一、基本信息](#一基本信息)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、接口源码](#四接口源码)
- [五、主要实现](#五主要实现)
- [六、类关系图](#六类关系图)
- [七、最佳实践](#七最佳实践)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、基本描述
`AfterReturningAdvice`接口是Spring AOP框架中的一个核心接口,用于在目标方法执行后拦截并执行自定义逻辑。通过实现该接口的`afterReturning`方法,可以在目标方法成功返回结果后进行一些操作。
### 三、主要功能
1. **日志记录**
+ 记录目标方法的执行情况,如方法名称、参数值、返回结果等,以便跟踪应用程序的运行状态。
2. **性能监控**
+ 统计目标方法的执行时间,分析应用程序的性能瓶颈,优化程序性能。
3. **缓存处理**
+ 在方法返回结果后,将结果缓存起来,以提高后续相同请求的响应速度。
4. **统一处理**
+ 执行一些与业务逻辑无关的统一处理,如资源释放、清理等。
### 四、接口源码
`AfterReturningAdvice`接口主要用于在方法正常返回时执行后置通知。后置通知可以查看方法的返回值,但不能修改它,并且仅在方法正常返回时被调用,不会在抛出异常时被调用。该接口提供了一个`afterReturning`方法,在方法成功返回后进行回调,其中包含了返回值、被调用的方法、方法的参数以及方法调用的目标对象等信息。
```java
/**
* 后置返回通知仅在方法正常返回时被调用,如果抛出异常则不会被调用。这样的通知可以查看方法的返回值,但不能修改它。
*
* 作者:Rod Johnson
* @see MethodBeforeAdvice
* @see ThrowsAdvice
*/
public interface AfterReturningAdvice extends AfterAdvice {
/**
* 在给定方法成功返回后的回调。
* @param returnValue 方法返回的值,如果有的话
* @param method 被调用的方法
* @param args 方法的参数
* @param target 方法调用的目标对象。可能为{@code null}。
* @throws Throwable 如果此对象希望中止调用。如果方法签名允许,将返回任何抛出的异常给调用者。否则,异常将被包装为运行时异常。
*/
void afterReturning(@Nullable Object returnValue, Method method, Object[] args, @Nullable Object target) throws Throwable;
}
```
### 五、主要实现
1. **AspectJAfterReturningAdvice**
+ 实现了返回后通知,使用 AspectJ 风格定义的通知,用于在目标方法成功执行并返回结果后执行额外的逻辑。
### 六、类关系图
~~~mermaid
classDiagram
direction BT
class Advice {
<>
}
class AfterAdvice {
<>
}
class AfterReturningAdvice {
<>
}
class AspectJAfterReturningAdvice
AfterAdvice --> Advice
AfterReturningAdvice --> AfterAdvice
AspectJAfterReturningAdvice ..> Advice
AspectJAfterReturningAdvice ..> AfterAdvice
AspectJAfterReturningAdvice ..> AfterReturningAdvice
~~~
### 七、最佳实践
使用Spring AOP中的后置返回通知(AfterReturningAdvice)。首先,创建了一个代理工厂(ProxyFactory)并指定目标对象(MyService)。然后,创建了一个后置返回通知(MyAfterReturningAdvice)并添加到代理工厂中。接着,通过代理工厂获取代理对象,并调用代理对象的方法。
```java
public class AfterReturningAdviceDemo {
public static void main(String[] args) {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 创建通知
proxyFactory.addAdvice(new MyAfterReturningAdvice());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.foo();
}
}
```
`MyAfterReturningAdvice`的类,它实现了Spring AOP框架中的`AfterReturningAdvice`接口。在`afterReturning`方法中,当目标方法成功返回结果时,它将打印出目标方法的名称以及返回的值。
```java
public class MyAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After Method " + method.getName());
}
}
```
`MyService` 类是一个简单的服务类,其中包含了一个名为 `foo()` 的方法。在上下文中,`MyService` 类被用作目标对象,即需要被拦截和增强的对象。
```java
public class MyService {
public void foo() {
System.out.println("foo...");
}
}
```
运行结果,成功地执行了代理对象的`foo()`方法,并在方法执行完成后,后置返回通知`MyAfterReturningAdvice`被触发。
```java
foo...
After Method foo
```
================================================
FILE: spring-aop/spring-aop-advice-afterReturningAdvice/pom.xml
================================================
com.xcs.spring
spring-aop
0.0.1-SNAPSHOT
4.0.0
spring-aop-advice-afterReturningAdvice
================================================
FILE: spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/AfterReturningAdviceDemo.java
================================================
package com.xcs.spring;
import org.springframework.aop.framework.ProxyFactory;
public class AfterReturningAdviceDemo {
public static void main(String[] args) {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 创建通知
proxyFactory.addAdvice(new MyAfterReturningAdvice());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.foo();
}
}
================================================
FILE: spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/MyAfterReturningAdvice.java
================================================
package com.xcs.spring;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class MyAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("After Method " + method.getName());
}
}
================================================
FILE: spring-aop/spring-aop-advice-afterReturningAdvice/src/main/java/com/xcs/spring/MyService.java
================================================
package com.xcs.spring;
public class MyService {
public void foo() {
System.out.println("foo...");
}
}
================================================
FILE: spring-aop/spring-aop-advice-introductionInterceptor/README.md
================================================
## IntroductionInterceptor
- [IntroductionInterceptor](#introductioninterceptor)
- [一、基本信息](#一基本信息)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、接口源码](#四接口源码)
- [五、主要实现](#五主要实现)
- [六、类关系图](#六类关系图)
- [七、最佳实践](#七最佳实践)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、基本描述
`IntroductionInterceptor` 接口是 Spring AOP 中的一个关键接口,用于实现引介(Introduction)功能,允许向目标对象动态地添加新的方法或属性,而无需修改目标类的代码,从而实现横切关注点的功能,如日志记录、事务管理等。
### 三、主要功能
1. **引介新的接口或类**
+ 通过实现 `implementsInterface()` 方法,在目标对象的方法调用之前,向目标对象引介新的接口或类,从而使目标对象具有额外的功能或属性。
### 四、接口源码
`IntroductionInterceptor` 接口,它是 AOP 联盟 `MethodInterceptor` 的子接口,允许拦截器实现额外的接口,并通过使用该拦截器的代理对象来使用这些接口。`IntroductionInterceptor` 接口体现了 AOP 中的引介(Introduction)概念,通过引介,可以将额外的功能添加到目标对象中,类似于混合(mixins)的概念,使得可以构建复合对象,实现类似于 Java 中多继承的目标。
```java
/**
* AOP联盟 MethodInterceptor 的子接口,允许拦截器实现额外的接口,并通过使用该拦截器的代理对象来使用这些接口。这是一个基本的AOP概念,称为引介。
*
* 引介通常是混合,允许构建能够实现多继承目标的复合对象。
*
* @author Rod Johnson
* @see DynamicIntroductionAdvice
*/
public interface IntroductionInterceptor extends MethodInterceptor, DynamicIntroductionAdvice {
}
```
### 五、主要实现
1. **DelegatingIntroductionInterceptor**
+ `DelegatingIntroductionInterceptor` 是 Spring AOP 提供的通用引介拦截器,允许我们定义自定义的引介逻辑,并在需要时将其应用于目标对象。通过配置,可以动态地向目标对象添加新的方法或属性,而不必修改目标类的代码,提高了代码的可维护性和灵活性。
2. **DelegatePerTargetObjectIntroductionInterceptor**
+ `DelegatePerTargetObjectIntroductionInterceptor` 是 `DelegatingIntroductionInterceptor` 的子类,为每个目标对象创建一个独立的引介代理对象。这意味着每个目标对象都可以拥有自己独立的引介逻辑,而不会受到其他目标对象的影响。这种灵活性特别适用于需要为不同的目标对象动态添加不同功能或属性的场景,提供了更高级的定制能力。
### 六、类关系图
~~~mermaid
classDiagram
direction BT
class Advice {
<>
}
class DelegatePerTargetObjectIntroductionInterceptor
class DelegatingIntroductionInterceptor
class DynamicIntroductionAdvice {
<>
}
class Interceptor {
<>
}
class IntroductionInterceptor {
<>
}
class MethodInterceptor {
<>
}
DelegatePerTargetObjectIntroductionInterceptor ..> IntroductionInterceptor
DelegatingIntroductionInterceptor ..> IntroductionInterceptor
DynamicIntroductionAdvice --> Advice
Interceptor --> Advice
IntroductionInterceptor --> DynamicIntroductionAdvice
IntroductionInterceptor --> MethodInterceptor
MethodInterceptor --> Interceptor
~~~
### 七、最佳实践
使用 Spring AOP 中的引介功能。它创建了一个代理工厂,并通过设置强制使用 CGLIB 代理来创建代理对象。然后,它添加了一个通知器,将自定义的引介通知(`MyMonitoringIntroductionAdvice`)应用于目标对象(`MyService` 类),使得目标对象实现了 `MyMonitoringCapable` 接口。最后,它调用了代理对象的方法,并在必要时启用了监控功能,展示了如何在运行时动态地向目标对象引入新的功能。
```java
public class IntroductionInterceptorDemo {
public static void main(String[] args) {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 强制私用CGLIB
proxyFactory.setProxyTargetClass(true);
// 创建通知
proxyFactory.addAdvisor(new DefaultIntroductionAdvisor(new MyMonitoringIntroductionAdvice(), MyMonitoringCapable.class));
// 创建代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.foo();
// 开始监控
((MyMonitoringCapable) proxy).toggleMonitoring();
// 再次调用代理对象的方法
proxy.foo();
}
}
```
`MyMonitoringIntroductionAdvice` 类是一个实现了 `DelegatingIntroductionInterceptor` 接口和 `MyMonitoringCapable` 接口的自定义引介通知类。它具有一个 `active` 属性来表示监控是否处于激活状态,并提供了一个方法 `toggleMonitoring()` 来启用监控功能。在被监控的方法被调用时,如果监控处于激活状态,该类会输出相应的日志信息,包括方法执行时间等。通过继承 `doProceed()` 方法,它能够在方法执行前后添加自定义逻辑,实现了监控功能的动态引入。
```java
public class MyMonitoringIntroductionAdvice extends DelegatingIntroductionInterceptor implements MyMonitoringCapable {
private boolean active = false;
public void setActive(boolean active) {
this.active = active;
}
@Override
public void toggleMonitoring() {
setActive(true);
}
// 当被监控的方法被调用时,如果监控处于激活状态,则输出日志
@Override
protected Object doProceed(MethodInvocation mi) throws Throwable {
if (this.active) {
System.out.println("[开启监控" + mi.getMethod().getName() + "]");
long startTime = System.currentTimeMillis();
Object result = super.doProceed(mi);
long endTime = System.currentTimeMillis();
System.out.println("[结束监控" + mi.getMethod().getName() + "] 耗费时间:" + (endTime - startTime) + " 毫秒");
return result;
}
return super.doProceed(mi);
}
}
```
`MyMonitoringCapable` 接口定义了一个 `toggleMonitoring()` 方法,用于启用或禁用监控功能。
```java
public interface MyMonitoringCapable {
void toggleMonitoring();
}
```
`MyService` 类是一个简单的服务类,其中包含了一个名为 `foo()` 的方法。在上下文中,`MyService` 类被用作目标对象,即需要被拦截和增强的对象。
```java
public class MyService {
public void foo() {
System.out.println("foo...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
```
运行结果,这个运行结果说明了引介通知成功地增强了目标方法,实现了在目标方法执行前后动态地添加额外的逻辑。
```java
foo...
[开启监控foo]
foo...
[结束监控foo] 耗费时间:1008 毫秒
```
================================================
FILE: spring-aop/spring-aop-advice-introductionInterceptor/pom.xml
================================================
com.xcs.spring
spring-aop
0.0.1-SNAPSHOT
4.0.0
spring-aop-advice-introductionInterceptor
================================================
FILE: spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/IntroductionInterceptorDemo.java
================================================
package com.xcs.spring;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultIntroductionAdvisor;
public class IntroductionInterceptorDemo {
public static void main(String[] args) {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 强制私用CGLIB
proxyFactory.setProxyTargetClass(true);
// 创建通知
proxyFactory.addAdvisor(new DefaultIntroductionAdvisor(new MyMonitoringIntroductionAdvice(), MyMonitoringCapable.class));
// 创建代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.foo();
// 开始监控
((MyMonitoringCapable) proxy).toggleMonitoring();
// 再次调用代理对象的方法
proxy.foo();
}
}
================================================
FILE: spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyMonitoringCapable.java
================================================
package com.xcs.spring;
public interface MyMonitoringCapable {
void toggleMonitoring();
}
================================================
FILE: spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyMonitoringIntroductionAdvice.java
================================================
package com.xcs.spring;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
public class MyMonitoringIntroductionAdvice extends DelegatingIntroductionInterceptor implements MyMonitoringCapable {
private boolean active = false;
public void setActive(boolean active) {
this.active = active;
}
@Override
public void toggleMonitoring() {
setActive(true);
}
// 当被监控的方法被调用时,如果监控处于激活状态,则输出日志
@Override
protected Object doProceed(MethodInvocation mi) throws Throwable {
if (this.active) {
System.out.println("[开启监控" + mi.getMethod().getName() + "]");
long startTime = System.currentTimeMillis();
Object result = super.doProceed(mi);
long endTime = System.currentTimeMillis();
System.out.println("[结束监控" + mi.getMethod().getName() + "] 耗费时间:" + (endTime - startTime) + " 毫秒");
return result;
}
return super.doProceed(mi);
}
}
================================================
FILE: spring-aop/spring-aop-advice-introductionInterceptor/src/main/java/com/xcs/spring/MyService.java
================================================
package com.xcs.spring;
public class MyService {
public void foo() {
System.out.println("foo...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
================================================
FILE: spring-aop/spring-aop-advice-methodBeforeAdvice/README.md
================================================
## MethodBeforeAdvice
- [MethodBeforeAdvice](#methodbeforeadvice)
- [一、基本信息](#一基本信息)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、接口源码](#四接口源码)
- [五、主要实现](#五主要实现)
- [六、类关系图](#六类关系图)
- [七、最佳实践](#七最佳实践)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、基本描述
`MethodBeforeAdvice`接口是Spring AOP中的一个核心接口,允许我们在目标方法执行之前插入自定义的逻辑,例如参数验证、日志记录等,从而实现面向切面编程的前置通知功能。
### 三、主要功能
1. **前置通知**
+ 允许我们在目标方法执行之前执行额外的逻辑操作。
2. **横切关注点的分离**
+ 将与业务逻辑无关的横切关注点(如日志记录、性能监控、安全验证等)与核心业务逻辑分离开来,提高代码的模块化和可维护性。
3. **参数验证**
+ 在目标方法执行前对方法参数进行验证,确保参数的合法性。
4. **权限控制**
+ 在方法执行前进行权限检查,确保只有具有足够权限的用户能够执行该方法。
### 四、接口源码
`MethodBeforeAdvice`接口,用于在方法调用之前执行通知。通知方法`before`接收被调用的方法、方法参数以及方法调用的目标对象作为参数,并可以抛出Throwable以中止方法调用。这样的通知无法阻止方法调用的继续进行,除非它们抛出了Throwable。
```java
/**
* 在方法被调用之前调用的通知。这样的通知不能阻止方法调用的继续进行,除非它们抛出了一个Throwable。
*
* @author Rod Johnson
* @see AfterReturningAdvice
* @see ThrowsAdvice
*/
public interface MethodBeforeAdvice extends BeforeAdvice {
/**
* 在给定方法被调用之前的回调。
* @param method 被调用的方法
* @param args 方法的参数
* @param target 方法调用的目标对象。可能为 {@code null}。
* @throws Throwable 如果此对象希望中止调用。任何抛出的异常如果方法签名允许,将返回给调用者。否则异常将作为运行时异常进行包装。
*/
void before(Method method, Object[] args, @Nullable Object target) throws Throwable;
}
```
### 五、主要实现
1. **AspectJMethodBeforeAdvice**
- 实现了前置通知,使用 AspectJ 风格定义的通知,用于在目标方法执行前执行额外的逻辑。
### 六、类关系图
~~~mermaid
classDiagram
direction BT
class Advice {
<>
}
class AspectJMethodBeforeAdvice
class BeforeAdvice {
<>
}
class MethodBeforeAdvice {
<>
}
AspectJMethodBeforeAdvice ..> Advice
AspectJMethodBeforeAdvice ..> MethodBeforeAdvice
BeforeAdvice --> Advice
MethodBeforeAdvice --> BeforeAdvice
~~~
### 七、最佳实践
使用`MethodBeforeAdvice`接口。首先,通过创建代理工厂和目标对象,然后创建自定义的前置通知`MyMethodBeforeAdvice`,将其添加到代理工厂中。接着,通过代理工厂获取代理对象,并调用代理对象的方法。在方法调用之前,前置通知会被触发执行,执行自定义的逻辑。
```java
public class MethodBeforeAdviceDemo {
public static void main(String[] args) {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 创建通知
proxyFactory.addAdvice(new MyMethodBeforeAdvice());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.doSomething();
}
}
```
`MyMethodBeforeAdvice`类实现了`MethodBeforeAdvice`接口,在其`before`方法中,打印出目标方法被调用之前的信息,包括方法名。这个类可以用作Spring AOP中的前置通知,在目标方法执行之前执行一些额外的逻辑。
```java
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before Method " + method.getName());
}
}
```
`MyService` 类是一个简单的服务类,其中包含了一个名为 `doSomething()` 的方法。在上下文中,`MyService` 类被用作目标对象,即需要被拦截和增强的对象。
```java
public class MyService {
public void foo() {
System.out.println("foo...");
}
}
```
运行结果,调用目标方法`foo`之前,`MyMethodBeforeAdvice`中的前置通知被成功触发,并打印了相应的信息。
```java
Before Method foo
foo...
```
================================================
FILE: spring-aop/spring-aop-advice-methodBeforeAdvice/pom.xml
================================================
com.xcs.spring
spring-aop
0.0.1-SNAPSHOT
4.0.0
spring-aop-advice-methodBeforeAdvice
================================================
FILE: spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MethodBeforeAdviceDemo.java
================================================
package com.xcs.spring;
import org.springframework.aop.framework.ProxyFactory;
public class MethodBeforeAdviceDemo {
public static void main(String[] args) {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 创建通知
proxyFactory.addAdvice(new MyMethodBeforeAdvice());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.foo();
}
}
================================================
FILE: spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MyMethodBeforeAdvice.java
================================================
package com.xcs.spring;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Before Method " + method.getName());
}
}
================================================
FILE: spring-aop/spring-aop-advice-methodBeforeAdvice/src/main/java/com/xcs/spring/MyService.java
================================================
package com.xcs.spring;
public class MyService {
public void foo() {
System.out.println("foo...");
}
}
================================================
FILE: spring-aop/spring-aop-advice-methodInterceptor/README.md
================================================
## MethodInterceptor
- [MethodInterceptor](#methodinterceptor)
- [一、基本信息](#一基本信息)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、接口源码](#四接口源码)
- [五、主要实现](#五主要实现)
- [六、类关系图](#六类关系图)
- [七、最佳实践](#七最佳实践)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、基本描述
`MethodInterceptor`接口是Spring框架中的一个核心接口,用于实现面向切面编程(AOP)。通过实现该接口,可以在目标方法执行前后、异常抛出时等关键点对方法进行拦截和增强,从而实现横切关注点的集中管理,提高代码的可维护性和灵活性。
### 三、主要功能
1. **方法拦截和增强**
+ 可以在目标方法执行前后、异常抛出时等关键点对方法进行拦截和增强,从而实现横切关注点的代码集中管理。
### 四、接口源码
`MethodInterceptor`接口是用于拦截接口方法调用并在目标方法之前和之后执行额外处理的核心接口。我们需要实现其中的`invoke`方法来定义拦截器的具体行为,例如,可以实现一个跟踪拦截器来追踪被拦截方法的调用情况。在`invoke`方法中,通常会调用`proceed()`方法来继续执行目标方法,并在必要时对返回值或异常进行处理。
```java
/**
* 拦截器,用于拦截接口方法调用并在目标方法之前和之后执行额外处理。
* 这些拦截器被嵌套在目标方法之上。
*
* 用户应该实现 {@link #invoke(MethodInvocation)} 方法来修改原始行为。例如,以下类实现了一个跟踪拦截器(跟踪所有被拦截方法的调用)
*
* @author Rod Johnson
*/
@FunctionalInterface
public interface MethodInterceptor extends Interceptor {
/**
* 实现此方法以在调用之前和之后执行额外处理。礼貌的实现应该肯定地调用 {@link Joinpoint#proceed()}。
* @param invocation 方法调用连接点
* @return 调用 {@link Joinpoint#proceed()} 的结果;可能会被拦截器拦截
* @throws Throwable 如果拦截器或目标对象抛出异常
*/
@Nullable
Object invoke(@Nonnull MethodInvocation invocation) throws Throwable;
}
```
### 五、主要实现
1. **MethodBeforeAdviceInterceptor**
+ 实现了前置通知的拦截器。前置通知在目标方法执行之前执行,允许我们在方法执行前插入额外的逻辑。
2. **AfterReturningAdviceInterceptor**
+ 实现了返回后通知的拦截器。返回后通知在目标方法成功执行并返回结果后执行,允许我们在方法返回后插入额外的逻辑。
3. **ThrowsAdviceInterceptor**
+ 实现了异常抛出后通知的拦截器。异常抛出后通知在目标方法抛出异常后执行,允许我们在方法抛出异常后插入额外的逻辑。
4. **AspectJAfterAdvice**
+ 实现了后置通知(After Advice),在目标方法执行后执行额外逻辑,不影响目标方法的执行结果。
5. **AspectJAfterThrowingAdvice**
+ 实现了异常抛出后通知(After Throwing Advice),在目标方法抛出异常后执行额外逻辑,允许处理异常或执行一些清理操作。
6. **AspectJAroundAdvice**
+ 实现了环绕通知(Around Advice),是最强大的一种通知类型,允许在目标方法执行前后添加额外逻辑,并完全控制目标方法的执行过程,包括是否执行目标方法和如何处理返回值。
### 六、类关系图
~~~mermaid
classDiagram
direction BT
class AbstractAspectJAdvice
class Advice {
<>
}
class AspectJAfterAdvice
class AspectJAfterThrowingAdvice
class AspectJAroundAdvice
class Interceptor {
<>
}
class MethodBeforeAdviceInterceptor
class AfterReturningAdviceInterceptor
class MethodInterceptor {
<>
}
class ThrowsAdviceInterceptor
AbstractAspectJAdvice ..> Advice
AfterReturningAdviceInterceptor ..> Advice
AfterReturningAdviceInterceptor ..> MethodInterceptor
AspectJAfterAdvice --> AbstractAspectJAdvice
AspectJAfterAdvice ..> Advice
AspectJAfterAdvice ..> MethodInterceptor
AspectJAfterThrowingAdvice --> AbstractAspectJAdvice
AspectJAfterThrowingAdvice ..> Advice
AspectJAfterThrowingAdvice ..> MethodInterceptor
AspectJAroundAdvice --> AbstractAspectJAdvice
AspectJAroundAdvice ..> MethodInterceptor
Interceptor --> Advice
MethodBeforeAdviceInterceptor ..> Advice
MethodBeforeAdviceInterceptor ..> MethodInterceptor
MethodInterceptor --> Interceptor
ThrowsAdviceInterceptor ..> Advice
ThrowsAdviceInterceptor ..> MethodInterceptor
~~~
### 七、最佳实践
创建了一个代理工厂 `ProxyFactory`,并传入了目标对象 `MyService`。然后通过 `proxyFactory.addAdvice()` 方法添加了一个自定义的方法拦截器 `MyMethodInterceptor` 作为通知。接着,通过 `proxyFactory.getProxy()` 方法获取代理对象 `MyService` 的实例。最后,调用代理对象的方法 `doSomething()`。
```java
public class MethodInterceptorDemo {
public static void main(String[] args) {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 创建通知
proxyFactory.addAdvice(new MyMethodInterceptor());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.foo();
}
}
```
`MyMethodInterceptor` 类用于实现方法拦截和增强的功能。在 `invoke()` 方法中,首先通过 `MethodInvocation` 对象获取被调用方法的信息,例如方法名等,并在方法调用之前输出方法被调用的信息。然后调用 `invocation.proceed()` 方法来执行原始方法,获取方法执行结果。最后并将其返回。
```java
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 在方法调用之前执行的逻辑
System.out.println("Before Method " + invocation.getMethod().getName());
// 调用原始方法
Object result = invocation.proceed();
// 在方法调用之后执行的逻辑
System.out.println("After Method " + invocation.getMethod().getName());
return result;
}
}
```
`MyService` 类是一个简单的服务类,其中包含了一个名为 `foo()` 的方法。在上下文中,`MyService` 类被用作目标对象,即需要被拦截和增强的对象。
```java
public class MyService {
public void foo() {
System.out.println("foo...");
}
}
```
运行结果,在调用 `MyService` 实例的 `foo()` 方法时,`MyMethodInterceptor` 拦截器成功地拦截了方法的执行,并在方法执行前后添加了额外的逻辑处理。
```java
Before Method foo
foo...
After Method foo
```
================================================
FILE: spring-aop/spring-aop-advice-methodInterceptor/pom.xml
================================================
com.xcs.spring
spring-aop
0.0.1-SNAPSHOT
4.0.0
spring-aop-advice-methodInterceptor
================================================
FILE: spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MethodInterceptorDemo.java
================================================
package com.xcs.spring;
import org.springframework.aop.framework.ProxyFactory;
public class MethodInterceptorDemo {
public static void main(String[] args) {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 创建通知
proxyFactory.addAdvice(new MyMethodInterceptor());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.foo();
}
}
================================================
FILE: spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MyMethodInterceptor.java
================================================
package com.xcs.spring;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 在方法调用之前执行的逻辑
System.out.println("Before Method " + invocation.getMethod().getName());
// 调用原始方法
Object result = invocation.proceed();
// 在方法调用之后执行的逻辑
System.out.println("After Method " + invocation.getMethod().getName());
return result;
}
}
================================================
FILE: spring-aop/spring-aop-advice-methodInterceptor/src/main/java/com/xcs/spring/MyService.java
================================================
package com.xcs.spring;
public class MyService {
public void foo() {
System.out.println("foo...");
}
}
================================================
FILE: spring-aop/spring-aop-advice-throwsAdvice/README.md
================================================
## ThrowsAdvice
- [ThrowsAdvice](#throwsadvice)
- [一、基本信息](#一基本信息)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、接口源码](#四接口源码)
- [五、主要实现](#五主要实现)
- [六、类关系图](#六类关系图)
- [七、最佳实践](#七最佳实践)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、基本描述
`ThrowsAdvice`接口是Spring AOP中的一种通知类型,用于在方法抛出异常时执行额外的逻辑。实现该接口的类可以捕获方法抛出的异常并执行自定义的异常处理逻辑。
### 三、主要功能
1. **捕获异常**
+ 允许在目标方法抛出异常时捕获这些异常。
2. **执行额外逻辑**
+ 提供了`afterThrowing()`方法,允许实现类在方法抛出异常时执行额外的逻辑,比如记录日志、发送通知等。
3. **参数传递**
+ `afterThrowing()`方法提供了抛出异常的方法对象、参数数组、目标对象和异常对象作为参数,方便实现类在处理异常时获取相关信息。
4. **定制化处理**
+ 可以根据业务需求定制化异常处理逻辑,使应用程序更加灵活和健壮。
### 四、接口源码
`ThrowsAdvice`接口,用作异常通知的标签接口。它没有任何方法,方法是通过反射调用的。实现类必须实现`afterThrowing()`方法,以处理方法抛出的异常。该方法的参数形式为`void afterThrowing([Method, args, target], ThrowableSubclass)`,前三个参数可选,用于提供关于连接点的更多信息,
```java
/**
* 用于异常通知的标签接口。
*
* 该接口没有任何方法,因为方法是通过反射调用的。实现类必须实现以下形式的方法:
*
*
void afterThrowing([Method, args, target], ThrowableSubclass);
*
* 以下是一些有效的方法示例:
*
*
public void afterThrowing(Exception ex)
* public void afterThrowing(RemoteException)
* public void afterThrowing(Method method, Object[] args, Object target, Exception ex)
* public void afterThrowing(Method method, Object[] args, Object target, ServletException ex)
*
* 前三个参数是可选的,只有在我们需要有关连接点更多信息时才有用,如AspectJ中的after-throwing通知。
*
* 注意: 如果throws-advice方法本身抛出异常,它将覆盖原始异常(即将异常更改为用户)。
* 覆盖的异常通常是RuntimeException; 这与任何方法签名兼容。但是,如果throws-advice方法抛出一个已检查的异常,
* 它将必须匹配目标方法的声明异常,并且在某种程度上与特定目标方法签名耦合。
* 不要抛出与目标方法签名不兼容的未声明的检查异常!
*
* @author Rod Johnson
* @author Juergen Hoeller
* @see AfterReturningAdvice
* @see MethodBeforeAdvice
*/
public interface ThrowsAdvice extends AfterAdvice {
}
```
### 五、主要实现
1. **ThrowsAdviceInterceptor**
+ 用于拦截方法抛出的异常,并触发相应的异常通知(`ThrowsAdvice`)。它负责捕获方法执行过程中抛出的异常,并调用相关的异常通知来处理异常情况。
### 六、类关系图
~~~mermaid
classDiagram
direction BT
class Advice {
<>
}
class AfterAdvice {
<>
}
class ThrowsAdvice {
<>
}
AfterAdvice --> Advice
ThrowsAdvice --> AfterAdvice
~~~
### 七、最佳实践
使用`ThrowsAdvice`接口来处理方法抛出的异常。它创建了一个代理工厂,并将目标对象(`MyService`)和异常通知(`MyThrowsAdvice`)传递给代理工厂。然后,它通过代理工厂获取代理对象,并调用代理对象的方法`foo()`。
```java
public class ThrowsAdviceDemo {
public static void main(String[] args) {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 创建通知
proxyFactory.addAdvice(new MyThrowsAdvice());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.foo();
}
}
```
`MyThrowsAdvice`类实现了`ThrowsAdvice`接口,并定义了`afterThrowing()`方法,用于处理方法抛出的异常。当目标方法抛出异常时,该方法将被调用,并打印出异常信息。
```java
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("Exception thrown: " + ex.getMessage());
}
}
```
`MyService`类包含了一个名为`foo()`的方法,该方法执行某些操作,并故意引发了一个异常(通过除以零)。
```java
public class MyService {
public void foo() {
System.out.println("foo...");
int i = 1 / 0;
}
}
```
运行结果,当调用了`MyService`类的`foo()`方法,但在该方法中发生了除以零的错误,导致了`java.lang.ArithmeticException: / by zero`异常的抛出。
```java
Doing something exception...
Exception thrown: / by zero
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.xcs.spring.MyService.doSomethingException(MyService.java:7)
at com.xcs.spring.MyService$$FastClassBySpringCGLIB$$c768e93b.invoke()
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.adapter.ThrowsAdviceInterceptor.invoke(ThrowsAdviceInterceptor.java:113)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)
at com.xcs.spring.MyService$$EnhancerBySpringCGLIB$$abe9fbc2.doSomethingException()
at com.xcs.spring.ThrowsAdviceDemo.main(ThrowsAdviceDemo.java:15)
```
================================================
FILE: spring-aop/spring-aop-advice-throwsAdvice/pom.xml
================================================
com.xcs.spring
spring-aop
0.0.1-SNAPSHOT
4.0.0
spring-aop-advice-throwsAdvice
================================================
FILE: spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/MyService.java
================================================
package com.xcs.spring;
public class MyService {
public void foo() {
System.out.println("foo...");
int i = 1 / 0;
}
}
================================================
FILE: spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/MyThrowsAdvice.java
================================================
package com.xcs.spring;
import org.springframework.aop.ThrowsAdvice;
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception ex) throws Throwable {
System.out.println("Exception thrown: " + ex.getMessage());
}
}
================================================
FILE: spring-aop/spring-aop-advice-throwsAdvice/src/main/java/com/xcs/spring/ThrowsAdviceDemo.java
================================================
package com.xcs.spring;
import org.springframework.aop.framework.ProxyFactory;
public class ThrowsAdviceDemo {
public static void main(String[] args) {
// 创建代理工厂&创建目标对象
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 创建通知
proxyFactory.addAdvice(new MyThrowsAdvice());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用代理对象的方法
proxy.foo();
}
}
================================================
FILE: spring-aop/spring-aop-advised/README.md
================================================
## Advised
- [Advised](#advised)
- [一、基本信息](#一基本信息)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、接口源码](#四接口源码)
- [五、主要实现](#五主要实现)
- [六、类关系图](#六类关系图)
- [七、最佳实践](#七最佳实践)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、基本描述
`Advised` 接口是 Spring AOP 中的核心接口之一,代表了被 Spring AOP 支持的被通知对象,它提供了对被通知对象的通用管理方法,包括检查是否被冻结、是否代理目标类、获取被代理接口、添加、移除和获取通知等,通过这些方法可以实现对被通知对象的动态通知管理。
### 三、主要功能
1. **冻结状态检查(isFrozen)**
+ 可以检查被通知对象是否处于冻结状态,即是否可以修改其 AOP 配置。
2. **代理类型检查(isProxyTargetClass)**
+ 可以检查是否代理了目标类,而不是目标接口。
3. **获取被代理接口(getProxiedInterfaces)**
+ 可以获取被代理对象所实现的接口数组。
4. **接口代理检查(isInterfaceProxied)**
+ 可以检查给定的接口是否被代理。
5. **添加通知(addAdvice)**
+ 可以向被通知对象添加通知,实现对目标方法的增强。
6. **指定位置添加通知(addAdvice)**
+ 可以在指定位置添加通知,控制通知的执行顺序。
7. **获取所有通知(getAdvices)**
+ 可以获取所有添加到被通知对象的通知。
8. **移除通知(removeAdvice)**
+ 可以移除指定的通知,动态调整通知的配置。
### 四、接口源码
`Advised`接口 ,规定了代理工厂配置的结构和行为。这个接口包含了许多方法,用于管理AOP代理的配置,包括拦截器、通知器、被代理的接口等。通过这个接口,可以获取和操作AOP代理的配置信息,如是否冻结、是否代理目标类、获取被代理的接口、添加和移除通知器等。
```java
/**
* 用于实现持有 AOP 代理工厂配置的类的接口。该配置包括拦截器和其他通知器、通知器以及被代理的接口。
*
* 从 Spring 获取的任何 AOP 代理都可以转换为此接口,以允许对其 AOP 通知进行操作。
*
* @author Rod Johnson
* @author Juergen Hoeller
* @since 13.03.2003
* @see org.springframework.aop.framework.AdvisedSupport
*/
public interface Advised extends TargetClassAware {
/**
* 返回 Advised 配置是否已冻结,如果已冻结,则无法进行通知更改。
*/
boolean isFrozen();
/**
* 是否代理了完整的目标类,而不是指定的接口?
*/
boolean isProxyTargetClass();
/**
* 返回 AOP 代理所代理的接口。
* 不会包括目标类,目标类可能也会被代理。
*/
Class>[] getProxiedInterfaces();
/**
* 确定给定的接口是否被代理。
* @param intf 要检查的接口
*/
boolean isInterfaceProxied(Class> intf);
/**
* 更改此 Advised 对象使用的 TargetSource。
* 仅在配置未被冻结时有效。
* @param targetSource 要使用的新 TargetSource
*/
void setTargetSource(TargetSource targetSource);
/**
* 返回此 Advised 对象使用的 TargetSource。
*/
TargetSource getTargetSource();
/**
* 设置代理是否应该被 AOP 框架公开为 {@link ThreadLocal},以便通过 {@link AopContext} 类进行检索。
* 如果需要在通知对象上调用自身的方法并应用通知,则可能需要公开代理。否则,如果通知对象调用 {@code this} 的方法,将不会应用任何通知。
* 默认为 {@code false},以获得最佳性能。
*/
void setExposeProxy(boolean exposeProxy);
/**
* 返回工厂是否应将代理公开为 {@link ThreadLocal}。
* 如果需要在通知对象上调用自身的方法并应用通知,则可能需要公开代理。否则,如果通知对象调用 {@code this} 的方法,将不会应用任何通知。
* 获取代理类似于 EJB 调用 {@code getEJBObject()}。
* @see AopContext
*/
boolean isExposeProxy();
/**
* 设置此代理配置是否经过预过滤,以便仅包含适用的通知器(与此代理的目标类匹配)。
* 默认为 "false"。如果通知器已经被预过滤,即可以跳过构建实际的代理调用链时的 ClassFilter 检查,则将其设置为 "true"。
* @see org.springframework.aop.ClassFilter
*/
void setPreFiltered(boolean preFiltered);
/**
* 返回此代理配置是否经过预过滤,以便仅包含适用的通知器(与此代理的目标类匹配)。
*/
boolean isPreFiltered();
/**
* 返回应用于此代理的通知器。
* @return 应用于此代理的通知器列表(永远不会为 {@code null})
*/
Advisor[] getAdvisors();
/**
* 返回应用于此代理的通知器数量。
* 默认实现委托给 {@code getAdvisors().length}。
* @since 5.3.1
*/
default int getAdvisorCount() {
return getAdvisors().length;
}
/**
* 在通知器链的末尾添加一个通知器。
* 通知器可以是 {@link org.springframework.aop.IntroductionAdvisor},
* 在从相关工厂下次获取代理时将提供新的接口。
* @param advisor 要添加到链的末尾的通知器
* @throws AopConfigException 如果通知器无效
*/
void addAdvisor(Advisor advisor) throws AopConfigException;
/**
* 在链中的指定位置添加一个通知器。
* @param advisor 要在链中指定位置添加的通知器
* @param pos 链中的位置(0 是头)。必须有效。
* @throws AopConfigException 如果通知器无效
*/
void addAdvisor(int pos, Advisor advisor) throws AopConfigException;
/**
* 删除给定的通知器。
* @param advisor 要移除的通知器
* @return 如果已移除通知器,则返回 {@code true};如果未找到该通知器,因此无法移除,则返回 {@code false}
*/
boolean removeAdvisor(Advisor advisor);
/**
* 移除给定索引处的通知器。
* @param index 要移除的通知器的索引
* @throws AopConfigException 如果索引无效
*/
void removeAdvisor(int index) throws AopConfigException;
/**
* 返回给定通知器的索引(从 0 开始),
* 如果没有这样的通知器适用于此代理,则返回 -1。
* 此方法的返回值可用于索引到通知器数组中。
* @param advisor 要搜索的通知器
* @return 此通知器的从 0 开始的索引,如果没有这样的通知器,则返回 -1
*/
int indexOf(Advisor advisor);
/**
* 替换给定的通知器。
* 注意如果通知器是 {@link org.springframework.aop.IntroductionAdvisor},
* 并且替换项不是或实现了不同的接口,则需要重新获取代理,否则旧接口将不被支持,新接口也将不被实现。
* @param a 要替换的通知器
* @param b 要替换的新通知器
* @return 是否已替换。如果通知器未在通知器列表中找到,则此方法返回 {@code false},并且不执行任何操作。
* @throws AopConfigException 如果通知器无效
*/
boolean replaceAdvisor(Advisor a, Advisor b) throws AopConfigException;
/**
* 将给定的 AOP Alliance 通知添加到通知(拦截器)链的末尾。
* 这将被包装在一个始终适用的 DefaultPointcutAdvisor 中,并从 {@code getAdvisors()} 方法以此包装形式返回。
* 请注意,给定的通知将应用于代理的所有调用,甚至是 {@code toString()} 方法!使用适当的通知实现或指定适当的切入点以适用于更窄范围的方法。
* @param advice 要添加到链末尾的通知
* @throws AopConfigException 如果通知无效
* @see #addAdvice(int, Advice)
* @see org.springframework.aop.support.DefaultPointcutAdvisor
*/
void addAdvice(Advice advice) throws AopConfigException;
/**
* 将给定的 AOP Alliance 通知添加到通知链的指定位置。
* 这将被包装在一个 {@link org.springframework.aop.support.DefaultPointcutAdvisor} 中,
* 并以此包装形式从 {@link #getAdvisors()} 方法返回。
* 注意给定的通知将应用于代理的所有调用,甚至是 {@code toString()} 方法!使用适当的通知实现或指定适当的切入点以适用于更窄范围的方法。
* @param pos 从 0 开始的索引(头部)
* @param advice 要在通知链的指定位置添加的通知
* @throws AopConfigException 如果通知无效
*/
void addAdvice(int pos, Advice advice) throws AopConfigException;
/**
* 移除包含给定通知的通知器。
* @param advice 要移除的通知
* @return 如果找到并移除了通知,则返回 {@code true};如果没有找到该通知,则返回 {@code false}
*/
boolean removeAdvice(Advice advice);
/**
* 返回给定 AOP Alliance 通知的索引(从 0 开始),
* 如果没有这样的通知是此代理的通知,则返回 -1。
* 此方法的返回值可用于索引到通知器数组中。
* @param advice 要搜索的 AOP Alliance 通知
* @return 此通知的从 0 开始的索引,如果没有这样的通知,则返回 -1
*/
int indexOf(Advice advice);
/**
* 由于通常将 {@code toString()} 委托给目标,因此此方法返回 AOP 代理的等效描述。
* @return 代理配置的字符串描述
*/
String toProxyConfigString();
}
```
### 五、主要实现
+ **AdvisedSupport**
+ 负责管理 AOP 代理配置信息的核心类,它包含了通知器、目标对象、目标源等关键属性,能够灵活地配置和管理 AOP 代理的创建过程,并提供了各种方法来处理代理配置的冻结状态、代理暴露等功能。
### 六、类关系图
~~~mermaid
classDiagram
direction BT
class Advised {
<>
}
class AdvisedSupport
class TargetClassAware {
<>
}
Advised --> TargetClassAware
AdvisedSupport ..> Advised
~~~
### 七、最佳实践
使用 `AdvisedSupport` 类来配置代理对象的相关属性,包括设置目标对象、接口、通知、通知器、是否暴露代理对象、是否使用 CGLIB 代理以及冻结对象,并通过 `toProxyConfigString()` 方法打印代理配置信息。
```java
public class AdvisedDemo {
public static void main(String[] args) {
// 创建 AdvisedSupport 对象
AdvisedSupport advisedSupport = new AdvisedSupport();
// 设置目标对象
advisedSupport.setTarget(new MyServiceImpl());
// 设置目标对象实现的接口
advisedSupport.setInterfaces(MyService.class);
// 添加通知
advisedSupport.addAdvice(new Advice() {});
// 添加通知器
advisedSupport.addAdvisor(new DefaultPointcutAdvisor());
// 暴露代理对象
advisedSupport.setExposeProxy(true);
// 设置CGLIB 代理
advisedSupport.setProxyTargetClass(true);
// 冻结对象
advisedSupport.setFrozen(true);
// 打印
System.out.println("AdvisedSupport = " + advisedSupport.toProxyConfigString());
}
}
```
定义了一个 `MyService` 接口
```java
public interface MyService {
void foo();
}
```
实现了 `MyService` 接口的 `MyServiceImpl` 类。
```java
public class MyServiceImpl implements MyService {
@Override
public void foo() {
System.out.println("foo...");
}
}
```
运行结果,显示了 `AdvisedSupport` 对象的配置信息,包括代理的接口、通知器、目标对象信息、代理类型以及其他相关设置。
```java
AdvisedSupport = org.springframework.aop.framework.AdvisedSupport: 1 interfaces [com.xcs.spring.MyService]; 2 advisors [org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [Pointcut.TRUE]; advice [com.xcs.spring.AdvisedDemo$1@32d992b2], org.springframework.aop.support.DefaultPointcutAdvisor: pointcut [Pointcut.TRUE]; advice [org.springframework.aop.Advisor$1@215be6bb]]; targetSource [SingletonTargetSource for target object [com.xcs.spring.MyServiceImpl@5d5eef3d]]; proxyTargetClass=true; optimize=false; opaque=false; exposeProxy=true; frozen=true
```
================================================
FILE: spring-aop/spring-aop-advised/pom.xml
================================================
com.xcs.spring
spring-aop
0.0.1-SNAPSHOT
4.0.0
spring-aop-advised
================================================
FILE: spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/AdvisedDemo.java
================================================
package com.xcs.spring;
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.AdvisedSupport;
import org.springframework.aop.support.DefaultPointcutAdvisor;
public class AdvisedDemo {
public static void main(String[] args) {
// 创建 AdvisedSupport 对象
AdvisedSupport advisedSupport = new AdvisedSupport();
// 设置目标对象
advisedSupport.setTarget(new MyServiceImpl());
// 设置目标对象实现的接口
advisedSupport.setInterfaces(MyService.class);
// 添加通知
advisedSupport.addAdvice(new Advice() {});
// 添加通知器
advisedSupport.addAdvisor(new DefaultPointcutAdvisor());
// 暴露代理对象
advisedSupport.setExposeProxy(true);
// 设置CGLIB 代理
advisedSupport.setProxyTargetClass(true);
// 冻结对象
advisedSupport.setFrozen(true);
// 打印
System.out.println("AdvisedSupport = " + advisedSupport.toProxyConfigString());
}
}
================================================
FILE: spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/MyService.java
================================================
package com.xcs.spring;
public interface MyService {
void foo();
}
================================================
FILE: spring-aop/spring-aop-advised/src/main/java/com/xcs/spring/MyServiceImpl.java
================================================
package com.xcs.spring;
public class MyServiceImpl implements MyService {
@Override
public void foo() {
System.out.println("foo...");
}
}
================================================
FILE: spring-aop/spring-aop-advisor/README.md
================================================
## Advisor
- [Advisor](#advisor)
- [一、基本信息](#一基本信息)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、接口源码](#四接口源码)
- [五、主要实现](#五主要实现)
- [六、类关系图](#六类关系图)
- [七、最佳实践](#七最佳实践)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、基本描述
`Advisor`接口是Spring框架中的一个关键接口,用于将切点(Pointcut)和通知(Advice)组合起来,以便在AOP(面向切面编程)中定义何时、何地以及如何应用横切关注点。
### 三、主要功能
1. **组合切点和通知**
+ Advisor接口允许将切点(Pointcut)和通知(Advice)组合在一起。切点确定何时应该应用通知,而通知定义了在连接点处执行的代码。
### 四、接口源码
`Advisor`接口是Spring框架中的一个基础接口,用于持有AOP通知(在连接点执行的操作)和确定通知适用性的过滤器(例如切点)。该接口定义了获取通知部分的方法`getAdvice()`,以及确定通知是否与特定实例相关联的方法`isPerInstance()`。同时,该接口还提供了一个常量`EMPTY_ADVICE`,用作当未配置适当通知时的占位符。在Spring AOP中,Advisor接口允许支持不同类型的通知,例如拦截器、前置通知、异常通知等,并且并非所有通知都需要使用拦截来实现。
```java
/**
* 基础接口,持有AOP 通知(在连接点执行的操作)和确定通知适用性的过滤器(例如切点)。
* 此接口不供Spring用户使用,而是为了在支持不同类型的通知时提供共性。
*
* Spring AOP基于通过方法拦截(interception)提供的环绕通知,符合AOP Alliance拦截API。
* Advisor接口允许支持不同类型的通知,例如前置和后置通知,它们不一定要使用拦截来实现。
*
* @author Rod Johnson
* @author Juergen Hoeller
*/
public interface Advisor {
/**
* 如果尚未配置适当的通知,则从{@link #getAdvice()}返回一个空的{@code Advice}的常用占位符。
* @since 5.0
*/
Advice EMPTY_ADVICE = new Advice() {};
/**
* 返回此方面的通知部分。通知可以是拦截器、前置通知、异常通知等。
* @return 如果切点匹配,则应应用的通知
* @see org.aopalliance.intercept.MethodInterceptor
* @see BeforeAdvice
* @see ThrowsAdvice
* @see AfterReturningAdvice
*/
Advice getAdvice();
/**
* 返回此通知是否与特定实例相关联(例如,创建混入),或与从同一Spring bean工厂获取的被通知类的所有实例共享。
*
请注意,框架当前不使用此方法。
* 典型的Advisor实现总是返回{@code true}。
* 使用singleton/prototype bean定义或适当的编程代理创建来确保Advisor具有正确的生命周期模型。
* @return 此通知是否与特定目标实例关联
*/
boolean isPerInstance();
}
```
`PointcutAdvisor`接口是所有由切点驱动的Advisor的超级接口。它覆盖了几乎所有的Advisor,但不包括引介Advisor,因为引介Advisor不适用于方法级别的匹配。该接口表示由切点驱动的Advisor,通过`getPointcut()`方法获取驱动该Advisor的切点。 PointcutAdvisor通常用于基于切点的切面,通过指定切点来确定通知逻辑应该应用于哪些连接点。
```java
/**
* 所有由切点驱动的Advisor的超级接口。
* 这几乎涵盖了所有的Advisor,除了引介Advisor,
* 因为方法级别的匹配不适用于引介Advisor。
*
* 该接口是Advisor的子接口,用于表示由切点驱动的Advisor。
* 切点驱动的Advisor通常用于基于切点的切面,通过指定切点来确定通知逻辑应该应用于哪些连接点。
*
* 作者:Rod Johnson
*/
public interface PointcutAdvisor extends Advisor {
/**
* 获取驱动该Advisor的切点。
*/
Pointcut getPointcut();
}
```
### 五、主要实现
1. **RegexpMethodPointcutAdvisor**
- 基于正则表达式来匹配方法名的切点。通过使用正则表达式,可以根据方法名模式匹配连接点,并将通知应用于匹配的连接点,从而实现基于方法名模式的切面逻辑。
2. **AspectJExpressionPointcutAdvisor**
- 基于AspectJ表达式来定义切点。通过使用AspectJ的语法,可以更灵活地定义切面,从而匹配连接点,并将通知应用于匹配的连接点,实现更复杂的切面逻辑。
3. **NameMatchMethodPointcutAdvisor**
- 基于方法名模式匹配来定义切点。通过使用方法名模式,可以轻松地匹配连接点,并将通知应用于匹配的连接点,从而实现基于方法名模式的切面逻辑。
4. **DefaultPointcutAdvisor**
- 一个通用的切点Advisor,用于将切点和通知组合在一起。它允许将任何类型的通知与任何类型的切点结合使用,并将通知应用于匹配的连接点,从而实现横切关注点的管理。
### 六、类关系图
~~~mermaid
classDiagram
direction BT
class Advisor {
<>
}
class AspectJPointcutAdvisor
class DefaultPointcutAdvisor
class NameMatchMethodPointcutAdvisor
class PointcutAdvisor {
<>
}
class RegexpMethodPointcutAdvisor
AspectJPointcutAdvisor ..> PointcutAdvisor
DefaultPointcutAdvisor ..> PointcutAdvisor
NameMatchMethodPointcutAdvisor ..> PointcutAdvisor
PointcutAdvisor --> Advisor
RegexpMethodPointcutAdvisor ..> PointcutAdvisor
~~~
### 七、最佳实践
使用Advisor来创建代理对象并应用切面逻辑。首先,通过创建代理工厂`ProxyFactory`,并将目标对象`MyService`传递给它。然后,通过`proxyFactory.addAdvisor(new MyCustomAdvisor())`添加了一个自定义的Advisor,其中包含了切点和通知的定义。接着,通过`proxyFactory.getProxy()`获取了代理对象`MyService`。最后,调用了代理对象的`foo()`方法,该方法触发了切面逻辑的执行。
```java
public class AdvisorDemo {
public static void main(String[] args) {
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 添加Advisor
proxyFactory.addAdvisor(new MyCustomAdvisor());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用方法
proxy.foo();
}
}
```
`MyCustomAdvisor`类是一个自定义的Advisor,它实现了`PointcutAdvisor`接口,并用于将通知应用于带有特定注解的方法。在该类中,我们定义了一个通知对象`advice`和一个切点对象`pointcut`,切点用于匹配带有自定义注解`MyCustomAnnotation`的方法。通过实现`getPointcut()`方法和`getAdvice()`方法,我们指定了切点和通知的逻辑。
```java
/**
* 自定义Advisor,用于将通知应用于带有特定注解的方法。
*/
public class MyCustomAdvisor implements PointcutAdvisor {
/**
* 通知对象
*/
private final Advice advice = new MyAdvice();
/**
* 切点对象,用于匹配带有自定义注解的方法
*/
private final Pointcut pointcut = new AnnotationMatchingPointcut(null, MyCustomAnnotation.class);
@Override
public Pointcut getPointcut() {
return pointcut;
}
@Override
public Advice getAdvice() {
return advice;
}
@Override
public boolean isPerInstance() {
return true;
}
}
```
`MyAdvice`类是一个通知类,它实现了`MethodInterceptor`接口,用于在方法执行前后添加额外的逻辑。在`invoke`方法中,我们首先输出了正在调用的方法名,然后调用了`invocation.proceed()`方法来执行原始方法,并获取了方法执行的结果。最后,我们在方法执行之后再次输出了方法名。
```java
public class MyAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 在方法调用之前执行的逻辑
System.out.println("Before Method " + invocation.getMethod().getName());
// 调用原始方法
Object result = invocation.proceed();
// 在方法调用之后执行的逻辑
System.out.println("After Method " + invocation.getMethod().getName());
return result;
}
}
```
`MyCustomAnnotation`是一个自定义的注解,通常用于标记需要特殊处理的方法。
```java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCustomAnnotation {
}
```
`MyCustomAnnotation`类其中包含一个名为`foo`的方法。该方法被`@MyCustomAnnotation`注解标记,表明需要特殊处理。
```java
public class MyService {
@MyCustomAnnotation
public void foo() {
System.out.println("foo...");
}
}
```
运行结果,切面逻辑成功应用于带有特定注解的方法。
```java
Before Method foo
foo...
After Method foo
```
================================================
FILE: spring-aop/spring-aop-advisor/pom.xml
================================================
com.xcs.spring
spring-aop
0.0.1-SNAPSHOT
4.0.0
spring-aop-advisor
================================================
FILE: spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/AdvisorDemo.java
================================================
package com.xcs.spring;
import org.springframework.aop.framework.ProxyFactory;
public class AdvisorDemo {
public static void main(String[] args) {
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 添加Advisor
proxyFactory.addAdvisor(new MyCustomAdvisor());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 调用方法
proxy.foo();
}
}
================================================
FILE: spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyAdvice.java
================================================
package com.xcs.spring;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
public class MyAdvice implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 在方法调用之前执行的逻辑
System.out.println("Before Method " + invocation.getMethod().getName());
// 调用原始方法
Object result = invocation.proceed();
// 在方法调用之后执行的逻辑
System.out.println("After Method " + invocation.getMethod().getName());
return result;
}
}
================================================
FILE: spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyCustomAdvisor.java
================================================
package com.xcs.spring;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Pointcut;
import org.springframework.aop.PointcutAdvisor;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
/**
* 自定义Advisor,用于将通知应用于带有特定注解的方法。
*/
public class MyCustomAdvisor implements PointcutAdvisor {
/**
* 通知对象
*/
private final Advice advice = new MyAdvice();
/**
* 切点对象,用于匹配带有自定义注解的方法
*/
private final Pointcut pointcut = new AnnotationMatchingPointcut(null, MyCustomAnnotation.class);
@Override
public Pointcut getPointcut() {
return pointcut;
}
@Override
public Advice getAdvice() {
return advice;
}
@Override
public boolean isPerInstance() {
return true;
}
}
================================================
FILE: spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyCustomAnnotation.java
================================================
package com.xcs.spring;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyCustomAnnotation {
}
================================================
FILE: spring-aop/spring-aop-advisor/src/main/java/com/xcs/spring/MyService.java
================================================
package com.xcs.spring;
public class MyService {
@MyCustomAnnotation
public void foo() {
System.out.println("foo...");
}
}
================================================
FILE: spring-aop/spring-aop-advisorAdapter/README.md
================================================
## AdvisorAdapter
- [AdvisorAdapter](#advisoradapter)
- [一、基本信息](#一基本信息)
- [二、基本描述](#二基本描述)
- [三、主要功能](#三主要功能)
- [四、接口源码](#四接口源码)
- [五、主要实现](#五主要实现)
- [六、类关系图](#六类关系图)
- [七、最佳实践](#七最佳实践)
- [八、源码分析](#八源码分析)
### 一、基本信息
✒️ **作者** - Lex 📝 **博客** - [掘金](https://juejin.cn/user/4251135018533068/posts) 📚 **源码地址** - [github](https://github.com/xuchengsheng/spring-reading)
### 二、基本描述
`AdvisorAdapter` 接口是 Spring AOP 中的一个重要接口,用于将不同类型的通知(Advice)适配到拦截器链中,以便将其应用于目标方法的执行。它允许我们自定义适配器来将自定义的通知与 Spring AOP 框架结合,从而实现对目标方法的前置、后置、环绕等类型的增强操作,为 AOP 的灵活性和可扩展性提供了支持。
### 三、主要功能
1. **通知适配**
+ 将不同类型的通知(Advice)适配到 Spring AOP 拦截器链中,以便将其应用于目标方法的执行。
2. **支持不同通知类型**
+ 支持适配各种类型的通知,包括前置通知(MethodBeforeAdvice)、后置通知(AfterReturningAdvice)、环绕通知(MethodInterceptor)、抛出异常通知(ThrowsAdvice)等。
3. **适配器注册和管理**
+ 允许我们注册和管理不同类型通知的适配器,以便在应用中使用不同类型的通知。
### 四、接口源码
这个接口定义了一种机制,允许向 Spring AOP 框架中引入新的 Advisor 和 Advice 类型。实现该接口的对象可以将自定义的 Advice 类型转换为 AOP Alliance 拦截器,使得这些自定义的 Advice 类型能够在 Spring AOP 框架中被使用。通常情况下,大多数 Spring 用户不需要直接实现这个接口;只有在需要引入新的 Advisor 或 Advice 类型时才需要这样做。
```java
/**
* 允许扩展 Spring AOP 框架的接口,以处理新的 Advisor 和 Advice 类型。
*
* 实现该接口的对象可以从自定义的 Advice 类型创建 AOP Alliance 拦截器,
* 从而使得这些 Advice 类型可以在 Spring AOP 框架中使用,该框架在底层使用拦截。
*
*
大多数 Spring 用户无需实现此接口;只有在需要向 Spring 引入更多的 Advisor 或 Advice 类型时才需要这样做。
*
* @author Rod Johnson
*/
public interface AdvisorAdapter {
/**
* 此适配器是否了解该通知对象?是否可以使用 Advisor 包含此通知作为参数调用 getInterceptors 方法?
* @param advice 一个 Advice,如 BeforeAdvice
* @return 此适配器是否了解给定的 Advice 对象
* @see #getInterceptor(org.springframework.aop.Advisor)
* @see org.springframework.aop.BeforeAdvice
*/
boolean supportsAdvice(Advice advice);
/**
* 返回一个 AOP Alliance MethodInterceptor,将给定 Advice 的行为暴露给基于拦截的 AOP 框架。
*
不必担心 Advisor 中包含的 Pointcut;AOP 框架将负责检查切点。
* @param advisor Advisor。supportsAdvice() 方法必须在此对象上返回 true
* @return 此 Advisor 的 AOP Alliance 拦截器。无需为效率缓存实例,因为 AOP 框架会缓存 Advice 链。
*/
MethodInterceptor getInterceptor(Advisor advisor);
}
```
### 五、主要实现
1. **MethodBeforeAdviceAdapter**
+ 用于将 `MethodBeforeAdvice` 类型的通知适配到 Spring AOP 拦截器链中。`MethodBeforeAdvice` 是一个在目标方法执行前执行的通知接口。
2. **ThrowsAdviceAdapter**
+ 用于将 `ThrowsAdvice` 类型的通知适配到 Spring AOP 拦截器链中。`ThrowsAdvice` 通知用于捕获目标方法抛出的异常。
3. **AfterReturningAdviceAdapter**
+ 用于将 `AfterReturningAdvice` 类型的通知适配到 Spring AOP 拦截器链中。`AfterReturningAdvice` 通知在目标方法正常返回后执行。
### 六、类关系图
~~~mermaid
classDiagram
direction BT
class AdvisorAdapter {
<>
}
class AfterReturningAdviceAdapter
class MethodBeforeAdviceAdapter
class ThrowsAdviceAdapter
AfterReturningAdviceAdapter ..> AdvisorAdapter
MethodBeforeAdviceAdapter ..> AdvisorAdapter
ThrowsAdviceAdapter ..> AdvisorAdapter
~~~
### 七、最佳实践
用自定义的 AdvisorAdapter 和 Advice 来实现对目标方法的增强。在示例中,首先注册了一个自定义的 AdvisorAdapter(NullReturningAdviceAdapter),然后创建了一个代理工厂(ProxyFactory)并向其添加了一个自定义的通知(MyNullReturningAdvice)。最后,通过代理工厂获取了代理对象,并调用了两个方法,其中一个方法会触发通知,另一个方法不会触发通知。
```java
public class AdvisorAdapterDemo {
public static void main(String[] args) {
// 注册自定义适配器
GlobalAdvisorAdapterRegistry.getInstance().registerAdvisorAdapter(new NullReturningAdviceAdapter());
// 创建代理工厂
ProxyFactory proxyFactory = new ProxyFactory(new MyService());
// 添加Advisor
proxyFactory.addAdvice(new MyNullReturningAdvice());
// 获取代理对象
MyService proxy = (MyService) proxyFactory.getProxy();
// 不会触发通知
System.out.println("foo return value : " + proxy.foo());
// 换行
System.out.println("==================================");
// 会触发通知
System.out.println("bar return value : " + proxy.bar());
}
}
```
一个空返回通知的适配器,用于将空返回通知(NullReturningAdvice)适配到拦截器链中。它实现了 AdvisorAdapter 接口,包含了支持给定通知和获取方法拦截器的功能,以便将特定类型的通知行为暴露给基于拦截的 AOP 框架。
```java
/**
* 空返回通知适配器,用于将空返回通知(NullReturningAdvice)适配到拦截器链中。
*/
public class NullReturningAdviceAdapter implements AdvisorAdapter {
/**
* 判断该适配器是否支持给定的通知。
* @param advice 一个通知,如空返回通知(NullReturningAdvice)
* @return 如果该适配器支持给定的通知,则返回 true;否则返回 false
*/
@Override
public boolean supportsAdvice(Advice advice) {
return (advice instanceof NullReturningAdvice);
}
/**
* 获取一个方法拦截器,将给定的通知行为暴露给基于拦截的 AOP 框架。
* @param advisor Advisor。supportsAdvice() 方法必须在此对象上返回 true
* @return 给定 Advisor 的方法拦截器
*/
@Override
public MethodInterceptor getInterceptor(Advisor advisor) {
NullReturningAdvice advice = (NullReturningAdvice) advisor.getAdvice();
return new NullReturningAdviceInterceptor(advice);
}
}
```
一个空返回通知拦截器,用于在方法执行后检查返回值是否为空,并根据情况执行空返回通知的逻辑。它实现了 MethodInterceptor 和 AfterAdvice 接口,通过拦截方法调用后的返回值来判断是否需要执行空返回通知,并在必要时调用空返回通知的逻辑。
```java
/**
* 空返回通知拦截器,用于在方法执行后检查返回值是否为空,并根据情况执行空返回通知的逻辑。
*/
public class NullReturningAdviceInterceptor implements MethodInterceptor, AfterAdvice {
/** 空返回通知 */
private final NullReturningAdvice advice;
/**
* 构造一个空返回通知拦截器。
* @param advice 空返回通知
*/
public NullReturningAdviceInterceptor(NullReturningAdvice advice) {
Assert.notNull(advice, "Advice must not be null");
this.advice = advice;
}
/**
* 在方法执行后拦截,检查返回值是否为空,并根据情况执行空返回通知的逻辑。
* @param mi 方法调用信息
* @return 方法执行结果,如果返回值为空,则根据空返回通知执行后的返回值
* @throws Throwable 如果方法调用过程中发生异常,则抛出异常
*/
@Override
public Object invoke(MethodInvocation mi) throws Throwable {
// 执行方法调用,获取返回值
Object retVal = mi.proceed();
// 如果返回值为空,则根据空返回通知执行后的返回值
if (retVal == null) {
retVal = this.advice.nullReturning(mi.getMethod(), mi.getArguments(), mi.getThis());
}
return retVal;
}
}
```
一个空返回通知的定义,继承了 AfterAdvice 接口。它包含了一个方法 nullReturning,用于在目标方法返回值为空时执行相应的逻辑,并返回一个新的返回值。
```java
/**
* 空返回通知接口,继承自 AfterAdvice。
*/
public interface NullReturningAdvice extends AfterAdvice {
/**
* 当目标方法返回值为空时调用的方法。
* @param method 目标方法
* @param args 方法参数
* @param target 目标对象
* @return 空返回通知执行后的返回值
* @throws Throwable 如果在执行空返回通知的过程中发生异常,则抛出异常
*/
Object nullReturning(Method method, Object[] args, @Nullable Object target) throws Throwable;
}
```
实现了`NullReturningAdvice`空返回通知接口,用于在目标方法返回值为空时执行特定逻辑。在 nullReturning 方法中返回一个默认的字符串值。
```java
public class MyNullReturningAdvice implements NullReturningAdvice {
@Override
public Object nullReturning(Method method, Object[] args, Object target) throws Throwable {
return "this is a defaultValue";
}
}
```
简单的服务类,包含了两个方法 foo 和 bar。foo 方法执行后返回字符串 "this is a foo",而 bar 方法执行后返回 null。
```java
public class MyService {
public String foo() {
System.out.println("foo...");
return "this is a foo";
}
public String bar() {
System.out.println("bar...");
return null;
}
}
```
运行结果,调用了 foo 方法,它返回 "this is a foo";然后调用了 bar 方法,由于其返回值为 null,因此触发了空返回通知,打印了相应的消息,并返回了默认值 "this is a defaultValue"。
```java
foo...
foo return value : this is a foo
==================================
bar...
bar return value : this is a defaultValue
```
### 八、源码分析
**注册适配器**
在`org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry#registerAdvisorAdapter`方法中,向适配器列表中注册一个新的 AdvisorAdapter 实例。
```java
/**
* 注册一个Advisor适配器。
* @param adapter 要注册的Advisor适配器
*/
@Override
public void registerAdvisorAdapter(AdvisorAdapter adapter) {
this.adapters.add(adapter);
}
```
在`org.springframework.aop.framework.adapter.DefaultAdvisorAdapterRegistry#adapters`字段中,用于存储 AdvisorAdapter 实例
```java
private final List adapters = new ArrayList<>(3);
```
**适配器转换拦截器**
在`org.springframework.aop.framework.JdkDynamicAopProxy#invoke`方法中,JDK动态代理入口中,获取指定方法的拦截链。
```java
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ... [代码部分省略以简化]
try {
// ... [代码部分省略以简化]
// Get the interception chain for this method.
List