From 8d4bf55bdbd68a4d84ed01ac7187add7c8eb4b04 Mon Sep 17 00:00:00 2001 From: xuchengsheng Date: Thu, 12 Oct 2023 21:15:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96@Configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../spring-annotation-configuration/README.md | 128 ++++++++++-------- 1 file changed, 69 insertions(+), 59 deletions(-) diff --git a/spring-annotation/spring-annotation-configuration/README.md b/spring-annotation/spring-annotation-configuration/README.md index 274fd61..88ae2f8 100644 --- a/spring-annotation/spring-annotation-configuration/README.md +++ b/spring-annotation/spring-annotation-configuration/README.md @@ -3,37 +3,37 @@ ## @Configuration - [@Configuration](#configuration) - - [一、注解描述](#一注解描述) - - [二、注解源码](#二注解源码) - - [三、主要功能](#三主要功能) - - [四、最佳实践](#四最佳实践) - - [4.1、proxyBeanMethods设置为true](#41proxybeanmethods设置为true) - - [4.2、proxyBeanMethods设置为false](#42proxybeanmethods设置为false) - - [五、时序图](#五时序图) - - [5.1、初始化流程](#51初始化流程) - - [5.2、注册流程](#52注册流程) - - [5.3、增强流程](#53增强流程) - - [六、源码分析](#六源码分析) - - [6.1、初始化流程](#61初始化流程) - - [6.2、注册流程](#62注册流程) - - [6.3、增强流程](#63增强流程) - - [七、注意事项](#七注意事项) - - [八、总结](#八总结) - - [8.1、最佳实践总结](#81最佳实践总结) - - [8.2、源码分析总结](#82源码分析总结) - - [九、常见问题](#九常见问题) - - [9.2 @Configuration中full模式与lite模式如何选择?](#92-configuration中full模式与lite模式如何选择) - - [9.2.1 Full 模式](#921-full-模式) - - [9.2.2 Lite 模式](#922-lite-模式) - - [9.2.3 如何选择](#923-如何选择) + - [一、基本信息](#一基本信息) + - [二、注解描述](#二注解描述) + - [三、注解源码](#三注解源码) + - [四、主要功能](#四主要功能) + - [五、最佳实践](#五最佳实践) + - [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 实例。 @@ -102,7 +102,7 @@ public @interface Configuration { } ``` -### 三、主要功能 +### 四、主要功能 1. **Bean定义方法** + `@Configuration` 类中可以包含一个或多个使用 `@Bean` 注解的方法,这些方法用于创建和配置应用程序上下文中的 beans。 @@ -122,9 +122,9 @@ public @interface Configuration { 1. **与其他注解结合**: + `@Configuration` 类通常与其他 Spring 注解(如 `@ComponentScan`、`@PropertySource` 等)结合使用,以提供全面的配置机制。 -### 四、最佳实践 +### 五、最佳实践 -#### 4.1、proxyBeanMethods设置为true +#### proxyBeanMethods设置为true 首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类。然后从Spring上下文中获取一个`MyConfiguration`类型的bean,最后调用了 `myBean` 方法两次,并将其结果打印到控制台。 @@ -170,7 +170,7 @@ com.xcs.spring.bean.MyBean@f736069 com.xcs.spring.bean.MyBean@f736069 ``` -#### 4.2、proxyBeanMethods设置为false +#### proxyBeanMethods设置为false 将 `proxyBeanMethods` 设置为 `false` 时,此代理行为被禁用。这意味着,如果我们在配置类内部多次调用同一个 `@Bean` 方法,每次都会创建一个新的实例。 @@ -192,11 +192,11 @@ com.xcs.spring.bean.MyBean@3b69e7d1 com.xcs.spring.bean.MyBean@815b41f ``` -### 五、时序图 +### 六、时序图 时序图主要分为三个关键步骤 -#### 5.1、初始化流程 +#### 初始化流程 - 当 `AnnotationConfigApplicationContext` 被实例化时,它开始初始化过程。 - 在这个过程中,`AnnotatedBeanDefinitionReader` 会被创建。这个读取器负责解析带注解的类,并将其转化为 Spring 可理解的 `BeanDefinition`。 @@ -215,7 +215,7 @@ AnnotationConfigUtils-->>AnnotationConfigUtils: registerPostProcessor(registry,d AnnotationConfigUtils-->>DefaultListableBeanFactory: registerBeanDefinition(beanName, beanDefinition)
注册Bean定义 ~~~ -#### 5.2、注册流程 +#### 注册流程 - 使用 `AnnotationConfigApplicationContext` 的 `register` 方法,配置类(带有 `@Bean` 方法的类)会被注册到Spring上下文中。 - `AnnotatedBeanDefinitionReader` 负责解析这些配置类,并创建相应的 `BeanDefinition`。 @@ -233,7 +233,7 @@ AnnotatedBeanDefinitionReader-->>BeanDefinitionReaderUtils: registerBeanDefiniti BeanDefinitionReaderUtils-->>DefaultListableBeanFactory: registerBeanDefinition(beanName,beanDefinition)
工厂存Bean定义 ~~~ -#### 5.3、增强流程 +#### 增强流程 - 当容器开始刷新(通过 `refresh` 方法),它会启动 beans 的创建和初始化过程。 - 在这个过程中,所有的 `BeanFactoryPostProcessor` 会被触发,特别是 `ConfigurationClassPostProcessor`。 @@ -254,7 +254,7 @@ ConfigurationClassEnhancer-->>ConfigurationClassEnhancer: createClass(enhancer)< ConfigurationClassEnhancer-->>ConfigurationClassPostProcessor: 增强后的Class类 ~~~ -### 六、源码分析 +### 七、源码分析 首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`(此类是使用Java注解来配置Spring容器的方式),构造参数我们给定了一个`MyConfiguration`组件类。然后从Spring上下文中获取一个`MyConfiguration`类型的bean,最后调用了 `myBean` 方法两次,并将其结果打印到控制台。 @@ -284,7 +284,7 @@ public AnnotationConfigApplicationContext(Class... componentClasses) { } ``` -#### 6.1、初始化流程 +#### 初始化流程 我们首先来到`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中步骤1。在`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`方法中,`AnnotationConfigApplicationContext` 的无参数构造函数中,初始化了 `AnnotatedBeanDefinitionReader` 和 `ClassPathBeanDefinitionScanner` 这两个核心组件。 @@ -347,7 +347,7 @@ public static Set registerAnnotationConfigProcessors( } ``` -#### 6.2、注册流程 +#### 注册流程 然后我们来到`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中步骤2。在org.springframework.context.annotation.AnnotationConfigApplicationContext#register方法中,主要是允许我们注册一个或多个组件类(例如,那些使用 `@Component`, `@Service`, `@Repository`, `@Controller`, `@Configuration` 等注解的类)到Spring容器。 @@ -422,7 +422,7 @@ public static void registerBeanDefinition( } ``` -#### 6.3、增强流程 +#### 增强流程 然后我们来到`org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext`构造函数中步骤3。在`org.springframework.context.support.AbstractApplicationContext#refresh`方法中我们重点关注一下`finishBeanFactoryInitialization(beanFactory)`这方法会对实例化所有剩余非懒加载的单列Bean对象,其他方法不是本次源码阅读的重点暂时忽略。 @@ -653,7 +653,7 @@ private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs, } ``` -### 七、注意事项 +### 八、注意事项 1. **单例保证**: - 在 `@Configuration` 类中,如果一个方法被标记为 `@Bean` 并被多次调用,它不会多次实例化一个 bean,而是返回同一个实例。这是因为 CGLIB 增强了 `@Configuration` 类,以确保 bean 的单例特性。 @@ -666,16 +666,16 @@ private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs, 5. **避免使用 `final`**: - 由于 `@Configuration` 类是通过 CGLIB 增强的,因此它们不能是 `final` 类型,同样,它们的方法也不能声明为 `final`。 -### 八、总结 +### 九、总结 -#### 8.1、最佳实践总结 +#### 最佳实践总结 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` 实例,这证明了两次调用返回了两个不同的对象。 -#### 8.2、源码分析总结 +#### 源码分析总结 1. **初始化流程**: - 当使用 `AnnotationConfigApplicationContext` 启动 Spring 应用时,会调用其构造函数,该函数执行三个主要步骤:初始化、注册和刷新。 @@ -691,30 +691,40 @@ private Object resolveBeanReference(Method beanMethod, Object[] beanMethodArgs, - 这个代理确保对配置类中的 `@Bean` 方法的每次调用都返回同一个 bean 实例,除非它是原型作用域的。 - 当应用上下文启动完成后,对于任何请求的 bean,代理的 `@Bean` 方法会从 Spring 容器中返回已存在的 bean 实例,而不是重新创建一个新的实例。 -### 九、常见问题 +### 十、常见问题 -#### 9.2 @Configuration中full模式与lite模式如何选择? +1. **@Configuration中full模式与lite模式如何选择?** -`@Configuration` 注解有两种模式:`full` 和 `lite`。它们在功能和性能上有所不同。了解它们的优缺点有助于为特定的场景做出合适的选择。 + `@Configuration` 注解有两种模式:`full` 和 `lite`。它们在功能和性能上有所不同。了解它们的优缺点有助于为特定的场景做出合适的选择。 -##### 9.2.1 Full 模式 + + Full 模式 -- 启用方式:在 `@Configuration` 注解中不设置 `proxyBeanMethods` 或将其设置为 `true`。 -- 功能:当在配置类中的 `@Bean` 方法内部调用另一个 `@Bean` 方法时,Spring 会确保返回的是容器中的单例bean,而不是一个新的实例。这是通过CGLIB代理实现的。 -- 优势:保持单例语义,确保容器中的单例Bean在配置类中的调用中始终是单例的。 -- 劣势:需要通过CGLIB创建配置类的子类,可能带来一些性能开销,增加了启动时间,可能与某些库不兼容,这些库期望操作实际类而不是其CGLIB代理。 + - 启用方式:在 `@Configuration` 注解中不设置 `proxyBeanMethods` 或将其设置为 `true`。 -##### 9.2.2 Lite 模式 + - 功能:当在配置类中的 `@Bean` 方法内部调用另一个 `@Bean` 方法时,Spring 会确保返回的是容器中的单例bean,而不是一个新的实例。这是通过CGLIB代理实现的。 -- 启用方式:在 `@Configuration` 注解中设置 `proxyBeanMethods` 为 `false`。 -- 功能:禁用CGLIB代理。`@Bean` 方法之间的调用就像普通的Java方法调用,每次都会创建一个新的实例。 -- 优势:更快的启动时间,因为不需要通过CGLIB增强配置类,对于简单的注入,这种模式可能更为简洁和直接。 -- 劣势:不保持单例语义。如果在一个 `@Bean` 方法内部调用另一个 `@Bean` 方法,会创建一个新的bean实例。 + - 优势:保持单例语义,确保容器中的单例Bean在配置类中的调用中始终是单例的。 -##### 9.2.3 如何选择 + - 劣势:需要通过CGLIB创建配置类的子类,可能带来一些性能开销,增加了启动时间,可能与某些库不兼容,这些库期望操作实际类而不是其CGLIB代理。 -- 如果我们的配置中需要确保在配置类中调用的bean始终是Spring容器中的单例bean,选择full模式。 -- 如果我们的配置类只是简单地定义beans并注入依赖,且不需要在配置类方法之间共享单例实例,选择lite模式。 -- 如果我们关心应用的启动性能,特别是在云环境或微服务中,使用lite模式可能更合适,因为它避免了额外的CGLIB处理。 + + +Lite 模式 -最终,根据项目的具体需求和场景选择合适的模式。如果没有特殊的单例需求,推荐使用lite模式,因为它更简单且启动性能更好。 \ No newline at end of file + - 启用方式:在 `@Configuration` 注解中设置 `proxyBeanMethods` 为 `false`。 + + - 功能:禁用CGLIB代理。`@Bean` 方法之间的调用就像普通的Java方法调用,每次都会创建一个新的实例。 + + - 优势:更快的启动时间,因为不需要通过CGLIB增强配置类,对于简单的注入,这种模式可能更为简洁和直接。 + + - 劣势:不保持单例语义。如果在一个 `@Bean` 方法内部调用另一个 `@Bean` 方法,会创建一个新的bean实例。 + + + + 如何选择 + + - 如果我们的配置中需要确保在配置类中调用的bean始终是Spring容器中的单例bean,选择full模式。 + + - 如果我们的配置类只是简单地定义beans并注入依赖,且不需要在配置类方法之间共享单例实例,选择lite模式。 + + - 如果我们关心应用的启动性能,特别是在云环境或微服务中,使用lite模式可能更合适,因为它避免了额外的CGLIB处理。 + + +​ 最终,根据项目的具体需求和场景选择合适的模式。如果没有特殊的单例需求,推荐使用lite模式,因为它更简单且启动性能更好。 \ No newline at end of file