spring-reading./spring-annotation-configura.../README.md

1319 lines
66 KiB
Markdown
Raw Normal View History

2023-09-27 09:50:26 +00:00
## @Configuration
[TOC]
### 1、注解说明
`@Configuration`是Spring框架中的一个核心注解主要用于类级别标识该类是一个配置类。配置类是Spring IoC容器的重要组成部分它包含了Spring容器如何初始化和配置应用中的bean的信息。在配置类中你可以定义一个或多个公共的`@Bean`注解方法这些方法会实例化、配置并初始化新的对象然后这些对象被Spring IoC容器管理
### 2、注解源码
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
boolean proxyBeanMethods() default true;
}
```
### 3、字段描述
#### 3.1、value
用于指定Bean的名称的。这个属性是`@Component`注解的一部分,因为`@Configuration`注解是元注解`@Component`的特化
#### 3.2、proxyBeanMethods
这是Spring 5.2新增的属性,用于控制`@Configuration`类的`@Bean`方法是否应该被CGLIB代理。如果`proxyBeanMethods`设置为true full模式那么@Bean方法会被代理每次调用都会检查Spring上下文确保返回的是同一个bean实例。如果设置为falselite模式那么`@Bean`方法会像普通方法一样执行每次调用都会返回一个新的实例。这种方式可能会使应用启动得更快因为不需要生成CGLIB代理类但是你必须自己处理@Bean方法之间的引用。
### 4、如何使用
首先来看看启动类入口,上下文环境使用`AnnotationConfigApplicationContext`此类是使用Java注解来配置Spring容器的方式构造参数我们给定了一个`MyConfiguration`组件类在最后我们调用两次获取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());
}
}
```
创建MyBean类作为IOC容器的Bean对象
```java
public class MyBean {
private String beanId;
public String getBeanId() {
return beanId;
}
public void setBeanId(String beanId) {
this.beanId = beanId;
}
}
```
#### 4.1、测试proxyBeanMethods为true情况
创建`MyConfiguration`类作为spring的启动配置引导类由于`@Configuration`中的proxyBeanMethods字段默认为true此处使用缺省值
```java
@Configuration
public class MyConfiguration {
@Bean
public MyBean myBean(){
return new MyBean();
}
}
```
通过运行main方法我们发现打印出来的2个对象是一致的<img src="https://img-blog.csdnimg.cn/466e60aca5c8471e8da94dd2586013dc.png#pic_center" alt="image-20230808100238267" style="zoom:150%;" />
#### 4.2、测试proxyBeanMethods为false情况
创建`MyConfiguration`类作为spring的启动配置引导类此处proxyBeanMethods设置为false
```java
@Configuration(proxyBeanMethods = false)
public class MyConfiguration {
@Bean
public MyBean myBean(){
return new MyBean();
}
}
```
通过再次运行main方法我们发现打印出来的2个对象是不一致的
<img src="https://img-blog.csdnimg.cn/094abfa1fa75427095aa43bc3e6a41ee.png#pic_center" alt="image-20230808102446140" style="zoom:150%;" />
#### 4.3、@Configuration注解几种组合用法
`@Configuration`注解在Spring框架中通常和其他注解一起使用以满足各种各样的配置需求。下面是一些常见的组合用法
##### 4.3.1、与`@Bean`组合
这是最常见的用法,`@Bean`注解用在`@Configuration`类的方法上这个方法的返回值会作为一个bean注册到Spring容器中。
```java
@Configuration
public class MyConfiguration {
@Bean
public MyBean myBean(){
return new MyBean();
}
}
```
##### 4.3.2、与`@ComponentScan`组合
`@ComponentScan`注解用来配置Spring哪些包进行扫描。Spring会扫描指定包及其子包下的所有类如果这些类上有`@Component`、`@Controller`、`@Service`、`@Repository`或者`@Configuration`等注解Spring就会把这些类作为Bean定义注册到容器中。
```java
@Configuration
@ComponentScan(basePackages = "com.xcs.spring.bean")
public class MyConfiguration {
}
```
##### 4.3.3、与`@Import`组合
`@Import`注解用来导入其他的`@Configuration`类。这样可以把多个小的、专门用途的配置类组合成一个大的配置类
```java
@Configuration
@Import({DatabaseConfig.class, WebConfig.class})
public class MyConfiguration {
}
```
##### 4.3.4、与`@PropertySource`组合
`@PropertySource`注解用来指定加载哪些属性文件。加载的属性会添加到Spring的`Environment`中,可以通过`@Value`注解或者`Environment`对象来获取属性值。
```java
@Configuration
@PropertySource("classpath:application.properties")
public class MyConfiguration {
}
```
##### 4.3.5、与`@Enable*`组合
`@Enable*`是一类注解用来开启Spring的某些功能比如`@EnableTransactionManagement`开启事务管理,`@EnableScheduling`开启计划任务,`@EnableAsync`开启异步执行等。这些注解必须用在`@Configuration`类上才能生效。
```java
@Configuration
@EnableTransactionManagement
public class MyConfiguration {
}
```
##### 4.3.6、与`@Profile`组合
`@Profile`注解用来定义配置类或bean定义适用的环境。只有当前环境和`@Profile`指定的环境匹配时配置类或bean定义才会被注册到容器中。
```java
@Configuration
@Profile("development")
public class MyConfiguration {
}
```
##### 4.3.7、与@Configuration内嵌组合
你可以在一个`@Configuration`类中嵌套其他的`@Configuration`类,这是一种组织配置的方式,可以让你的配置更加模块化和层次化。这种方式通常用在一个大的配置类中,你可以将某些特定的配置组合在一起,放在一个内嵌的`@Configuration`类中。
```java
@Configuration
public class MyConfiguration {
@Bean
public MyBean myBean() {
return new MyBean();
}
@Configuration
public static class MyDatabaseConfig {
@Bean
public DataSource dataSource() {
return new DataSource();
}
}
@Configuration
public static class MyWebConfig {
@Bean
public Controller controller() {
return new Controller();
}
}
}
```
##### 4.3.8、与`@Conditional`组合
可以使得某个bean定义或者配置类只有在特定的条件满足时才会被注册。例如我们可以定义一个条件类检查某个系统属性是否存在然后用`@Conditional`注解将这个条件类应用到bean定义或配置类上。
```java
@Configuration
@Conditional(MyCondition.class)
public class MyConfiguration {
// ...
}
public class MyCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return System.getProperty("myProperty") != null;
}
}
```
##### 4.3.9、与`Environment`API组合
可以用来获取系统属性、环境变量、配置文件中的属性等然后根据这些属性来决定要创建哪些bean。例如我们可以在`@Bean`方法中检查一个特定的环境变量然后根据这个环境变量的值来决定创建哪个版本的bean。
```java
@Configuration
public class MyConfiguration {
@Autowired
Environment env;
@Bean
public MyBean myBean() {
if ("version1".equals(env.getProperty("myBean.version"))) {
return new MyBeanVersion1();
} else {
return new MyBeanVersion2();
}
}
}
```
##### 4.3.9、组合总结
以上这些Spring注解是可以多个组合使用的而且这种组合是很常见的。使用这些注解组合可以实现高级的配置策略增加配置的灵活性。例如你可以在一个`@Configuration`类中同时使用`@Profile`、`@Bean`、`@Import`等注解每个注解都有其特定的用途。通过组合多个注解我们可以为Spring应用创建出极具灵活性和动态性的配置。需要注意的是虽然使用多个注解可以带来很大的灵活性但同时也会增加配置的复杂性因此我们需要在保持配置简洁和提供足够的灵活性之间找到一个平衡点。
### 5、原理分析
当Spring容器加载`@Configuration`注解的类时它实际上会创建一个CGLIB代理的子类来替代这个类。在代理类中每个`@Bean`方法都会被重写以确保每个方法的调用都会通过Spring的Bean工厂这样就可以保证单例Bean的语义。
在Spring 5.2版本之前,对于`@Configuration`注解的类,无论其`@Bean`方法是否被设计为单例Spring容器都会确保它们的行为如同单例Bean一样。即使在同一个配置类中一个`@Bean`方法调用另一个`@Bean`方法也会得到同一个实例。这个特性就是full模式的核心。
```java
// 使用@Configuration5.2以前没有proxyBeanMethods字段默认就是full模式
@Configuration
public class MyConfiguration {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
@Bean
public ServiceB serviceB() {
// 这里调用的 serviceA() 返回的是同一个 ServiceA 实例
return new ServiceB(serviceA());
}
}
```
在Spring 5.2版本之前如果你想使用lite模式
```java
// 使用类标记为@Component或@Service等
@Component
public class MyConfiguration {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
@Bean
public ServiceB serviceB() {
// 这里调用的 serviceA() 每次都会返回一个新的 ServiceA 实例
return new ServiceB(serviceA());
}
}
```
而在Spring 5.2及之后的版本中,`@Configuration`注解有一个`proxyBeanMethods`属性它用来决定是否需要使用CGLIB代理。如果`proxyBeanMethods`设置为`true`那么Spring会为这个配置类创建一个CGLIB代理类这被称为full模式。
```java
// 使用@Configuration5.2以后新增proxyBeanMethods字段默认只为true表示走full模式
@Configuration(proxyBeanMethods = true)
public class MyConfiguration {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
@Bean
public ServiceB serviceB() {
// 这里调用的 serviceA() 返回的是同一个 ServiceA 实例
return new ServiceB(serviceA());
}
}
```
如果`proxyBeanMethods`设置为`false`那么Spring不会创建代理类这被称为lite模式。在lite模式下`@Bean`方法的调用就像普通的Java方法调用一样不会通过Spring的Bean工厂也不会确保单例语义。因此即使你将一个Bean定义为单例如果你在一个配置类中多次调用这个Bean的方法也会得到不同的实例。这种方式在某些场景下可能会更快但是需要你自己来保证配置的正确性。
```java
@Configuration(proxyBeanMethods = false)
public class MyConfiguration {
@Bean
public ServiceA serviceA() {
return new ServiceA();
}
@Bean
public ServiceB serviceB() {
// 这里调用的 serviceA() 每次都会返回一个新的 ServiceA 实例
return new ServiceB(serviceA());
}
}
```
前面说到`@Configuration`是走的CGLIB代理来实现的那么我们可以借助一个`arthas`的工具,查看一下是否生成了代理类,被代理后的类长什么样的呢?
#### 5.1、分析proxyBeanMethods为true
首先`MyConfiguration`类中的`proxyBeanMethods`字段默认为true此处就不设置了使用缺省值。
```java
@Configuration
public class MyConfiguration {
@Bean
public MyBean myBean(){
return new MyBean();
}
}
```
下面是我的启动类,通过`context.getBean(MyConfiguration.class)`获得`MyConfiguration`对象最后通过configuration.getClass().getName()打印类名查看是原始类名还是被CGLIB代理后的类名最后使用了System.in.read()是防止spring程序结束退出程序。
```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());
System.out.println("MyConfiguration = " + configuration.getClass().getName());
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
```
运行结果发现,`MyConfiguration`已经成功被CGLIB代理代理类为`com.xcs.spring.MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac`接下来我们使用arthas工具反编译一下此类查看具体被代理后的代码是什么样子的呢
[如果你还对arthas不了解请查看arthas官网文档](https://arthas.aliyun.com/doc/)
<img src="https://img-blog.csdnimg.cn/d1ea9d6651674d47863312591b8ef31b.png#pic_center" alt="image-20230808140424359" style="zoom:150%;" />
通过arthas的反编译首先我们发现 `MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac`是`MyConfiguration`的一个子类,并实现了`ConfigurationClassEnhancer.EnhancedConfiguration`接口中的setBeanFactory方法
```java
package com.xcs.spring;
import com.xcs.spring.MyConfiguration;
import com.xcs.spring.bean.MyBean;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.cglib.core.ReflectUtils;
import org.springframework.cglib.core.Signature;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.context.annotation.ConfigurationClassEnhancer;
public class MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac extends MyConfiguration implements ConfigurationClassEnhancer.EnhancedConfiguration {
// 标记是否已经将CGLIB的回调对象绑定到了当前对象上。
private boolean CGLIB$BOUND;
// 存储CGLIB用于创建代理对象的工厂数据。
public static Object CGLIB$FACTORY_DATA;
// 线程局部变量用于存储当前线程的CGLIB回调对象。
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
// 存储静态的CGLIB回调对象
private static final Callback[] CGLIB$STATIC_CALLBACKS;
// CGLIB的回调对象用于处理方法调用。Spring通过这些回调对象拦截对@Bean方法的调用并确保返回的是Spring容器管理的bean实例。
private MethodInterceptor CGLIB$CALLBACK_0;
private MethodInterceptor CGLIB$CALLBACK_1;
private NoOp CGLIB$CALLBACK_2;
// CGLIB的回调过滤器用于决定某个方法调用应该使用哪个回调对象来处理。
private static Object CGLIB$CALLBACK_FILTER;
// 被代理的方法例如myBean()方法和setBeanFactory()方法。
private static final Method CGLIB$myBean$0$Method;
// CGLIB的MethodProxy对象用于代理方法调用。
private static final MethodProxy CGLIB$myBean$0$Proxy;
// 这个字段是一个空的参数数组,用于在调用没有参数的方法时使用。
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$setBeanFactory$5$Method;
private static final MethodProxy CGLIB$setBeanFactory$5$Proxy;
// MyConfiguration类实现了BeanFactoryAware接口因此Spring在创建bean实例后会自动调用setBeanFactory方法
public BeanFactory $$beanFactory;
public MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac() {
MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac myConfiguration$$EnhancerBySpringCGLIB$$fce000ac = this;
MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac.CGLIB$BIND_CALLBACKS(myConfiguration$$EnhancerBySpringCGLIB$$fce000ac);
}
static {
MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac.CGLIB$STATICHOOK2();
MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac.CGLIB$STATICHOOK1();
}
public final MyBean myBean() {
// 首先,它尝试获取一个名为`CGLIB$CALLBACK_0这个拦截器是CGLIB回调机制的核心它负责处理`@Bean`方法的调用。
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
// 如果拦截器不存在,那么它会尝试调用`CGLIB$BIND_CALLBACKS(this)`方法,该方法负责将拦截器绑定到当前对象上。
if (methodInterceptor == null) {
MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac.CGLIB$BIND_CALLBACKS(this);
// 绑定拦截器后,它再次获取拦截器。
methodInterceptor = this.CGLIB$CALLBACK_0;
}
// 如果拦截器存在,那么它就调用拦截器的`intercept()`方法,处理对`myBean()`方法的调用。`intercept()`方法的参数包括:当前对象、代表`myBean()`方法的`Method`对象、一个空的参数数组(因为`myBean()`方法没有参数),以及一个`MethodProxy`对象用于通过CGLIB调用超类的原始方法
if (methodInterceptor != null) {
return (MyBean)methodInterceptor.intercept(this, CGLIB$myBean$0$Method, CGLIB$emptyArgs, CGLIB$myBean$0$Proxy);
}
// 如果拦截器不存在,那么它就直接调用超类的`myBean()`方法,即原始的`MyConfiguration`类的`myBean()`方法。
return super.myBean();
}
@Override
public final void setBeanFactory(BeanFactory beanFactory) throws BeansException {
// 获取一个名为CGLIB$CALLBACK_1的MethodInterceptor方法拦截器
MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_1;
// 如果拦截器不存在,那么它会尝试调用CGLIB$BIND_CALLBACKS(this)方法,该方法负责将拦截器绑定到当前对象上
if (methodInterceptor == null) {
MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac.CGLIB$BIND_CALLBACKS(this);
// 绑定拦截器后,它再次获取拦截器。
methodInterceptor = this.CGLIB$CALLBACK_1;
}
// 如果拦截器存在那么它就调用拦截器的intercept()方法处理对setBeanFactory方法的调用。
// 这个intercept()方法的参数包括当前对象、代表setBeanFactory方法的Method对象、一个包含一个元素即传入的BeanFactory的参数数组
// 以及一个MethodProxy对象用于通过CGLIB调用超类的原始方法
if (methodInterceptor != null) {
Object object = methodInterceptor.intercept(this, CGLIB$setBeanFactory$5$Method, new Object[]{beanFactory},CGLIB$setBeanFactory$5$Proxy);
return;
}
// 如果拦截器不存在那么它就直接调用超类的setBeanFactory方法即原始的MyConfiguration类的setBeanFactory方法。
super.setBeanFactory(beanFactory);
}
public static void CGLIB$SET_STATIC_CALLBACKS(Callback[] callbackArray) {
CGLIB$STATIC_CALLBACKS = callbackArray;
}
public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] callbackArray) {
CGLIB$THREAD_CALLBACKS.set(callbackArray);
}
// CGLIB库的内部实现细节一般情况下你不需要直接使用或理解这些代码
public static MethodProxy CGLIB$findMethodProxy(Signature signature) {
String string = ((Object)signature).toString();
switch (string.hashCode()) {
case -1352508034: {
if (!string.equals("myBean()Lcom/xcs/spring/bean/MyBean;")) break;
return CGLIB$myBean$0$Proxy;
}
case 2095635076: {
if (!string.equals("setBeanFactory(Lorg/springframework/beans/factory/BeanFactory;)V")) break;
return CGLIB$setBeanFactory$5$Proxy;
}
}
return null;
}
final void CGLIB$setBeanFactory$5(BeanFactory beanFactory) throws BeansException {
super.setBeanFactory(beanFactory);
}
// 通过这个方法CGLIB可以在需要的时候将回调对象绑定到代理对象上
// 然后通过这些回调对象来处理方法调用。这是CGLIB实现方法拦截的一部分也是Spring实现@Configuration注解的重要机制。
private static final void CGLIB$BIND_CALLBACKS(Object object) {
block2: {
Object object2;
MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac myConfiguration$$EnhancerBySpringCGLIB$$fce000ac;
block3: {
// 首先它将传入的对象转换为MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac类型。
myConfiguration$$EnhancerBySpringCGLIB$$fce000ac = (MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac)object;
// 如果这个对象还没有绑定回调对象CGLIB$BOUND字段为false,跳出整个代码块block2
if (myConfiguration$$EnhancerBySpringCGLIB$$fce000ac.CGLIB$BOUND) break block2;
// 那么它将CGLIB$BOUND字段设为true表示已经绑定了回调对象。
myConfiguration$$EnhancerBySpringCGLIB$$fce000ac.CGLIB$BOUND = true;
// 然后它尝试从CGLIB$THREAD_CALLBACKS线程局部变量中获取回调对象。
object2 = CGLIB$THREAD_CALLBACKS.get();
if (object2 != null) break block3;
// 如果没有找到就尝试获取静态的CGLIB$STATIC_CALLBACKS回调对象。
object2 = CGLIB$STATIC_CALLBACKS;
// 跳出整个代码块block2
if (CGLIB$STATIC_CALLBACKS == null) break block2;
}
// 如果找到了回调对象,就将它们绑定到当前对象上。例如,
// CGLIB$CALLBACK_2字段是一个NoOp对象它是Callback接口的一个实现
// 表示一个没有操作的回调。CGLIB$CALLBACK_0和CGLIB$CALLBACK_1字段是MethodInterceptor对象它们用于处理方法调用。
Callback[] callbackArray = (Callback[])object2;
MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac myConfiguration$$EnhancerBySpringCGLIB$$fce000ac2 = myConfiguration$$EnhancerBySpringCGLIB$$fce000ac;
myConfiguration$$EnhancerBySpringCGLIB$$fce000ac2.CGLIB$CALLBACK_2 = (NoOp)callbackArray[2];
myConfiguration$$EnhancerBySpringCGLIB$$fce000ac2.CGLIB$CALLBACK_1 = (MethodInterceptor)callbackArray[1];
myConfiguration$$EnhancerBySpringCGLIB$$fce000ac2.CGLIB$CALLBACK_0 = (MethodInterceptor)callbackArray[0];
}
}
// 这个方法在代理类的静态初始化块中被调用,它初始化了代理类需要的一些字段和数据结构。
// 静态初始化块是Java语言的一部分用于初始化静态字段。这个块在类被加载时执行一次。
static void CGLIB$STATICHOOK1() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class<?> clazz = Class.forName("com.xcs.spring.MyConfiguration$$EnhancerBySpringCGLIB$$fce000ac");
Class<?> clazz2 = Class.forName("org.springframework.beans.factory.BeanFactoryAware");
CGLIB$setBeanFactory$5$Method = ReflectUtils.findMethods(new String[]{"setBeanFactory", "(Lorg/springframework/beans/factory/BeanFactory;)V"}, clazz2.getDeclaredMethods())[0];
CGLIB$setBeanFactory$5$Proxy = MethodProxy.create(clazz2, clazz, "(Lorg/springframework/beans/factory/BeanFactory;)V", "setBeanFactory", "CGLIB$setBeanFactory$5");
clazz2 = Class.forName("com.xcs.spring.MyConfiguration");
CGLIB$myBean$0$Method = ReflectUtils.findMethods(new String[]{"myBean", "()Lcom/xcs/spring/bean/MyBean;"}, clazz2.getDeclaredMethods())[0];
CGLIB$myBean$0$Proxy = MethodProxy.create(clazz2, clazz, "()Lcom/xcs/spring/bean/MyBean;", "myBean", "CGLIB$myBean$0");
}
final MyBean CGLIB$myBean$0() {
return super.myBean();
}
static void CGLIB$STATICHOOK2() {
}
}
```
#### 5.2、分析proxyBeanMethods为false
我们再次调整`MyConfiguration`类中的`proxyBeanMethods`字段设置为false
```java
@Configuration(proxyBeanMethods = false)
public class MyConfiguration {
@Bean
public MyBean myBean(){
return new MyBean();
}
}
```
下面是我的启动类,通过`context.getBean(MyConfiguration.class)`获得`MyConfiguration`对象,最后通过`configuration.getClass().getName()`打印类名查看是原始类名还是被CGLIB代理后的类名最后使用了`System.in.read()`是防止spring程序结束退出程序。
```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());
System.out.println("MyConfiguration = " + configuration.getClass().getName());
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
```
我们运行后发现CGLIB代理并未生效而是使用原始的`MyConfiguration`作为Bean对象所以此处我们就没有必要进行类反编译操作啦到此已经发现full模式与lite模式的一些区别了吧。
<img src="https://img-blog.csdnimg.cn/f4a9c2cb3b83460cbefaacd98e1aa73f.png#pic_center" alt="image-20230808142855277" style="zoom:150%;" />
### 6、源码分析
首先在最前面透露一下,处理`@Configuration`的核心类是在`ConfigurationClassPostProcessor`类
首先通过IDEA查看类图发现`ConfigurationClassPostProcessor`类实现了一个重要的接口`BeanDefinitionRegistryPostProcessor`,并实现了该接口中有`postProcessBeanDefinitionRegistry`方法,并间接实现了`BeanFactoryPostProcessor`接口中的`postProcessBeanFactory`方法。那么`ConfigurationClassPostProcessor`类是什么时候还是起作用并生效的呢?我们此时需要跟踪一下源码就知道啦
![image-20230808173929978](https://img-blog.csdnimg.cn/885e52a890b44f02a83fbb2a595720ef.png#pic_center)
我们的`ConfigurationApplication`类的main方法开始跟踪源码我们使用的是`AnnotationConfigApplicationContext`做为上下文环境,并传入了一个组件类的类名,那么我们继续进入`AnnotationConfigApplicationContext`的构造函数查看源码
```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());
System.out.println("MyConfiguration = " + configuration.getClass().getName());
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
```
在`AnnotationConfigApplicationContext`构造函数中,执行了三个步骤
```java
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
// (this构造函数) 6.1
this();
// 注册MyConfiguration 6.2
register(componentClasses);
// 刷新容器 6.3
refresh();
}
```
#### 6.1、this构造函数
无参构造函数中使用了`AnnotatedBeanDefinitionReader`(该类主要用于从注解类中解析出 `BeanDefinition`,然后将解析出的 `BeanDefinition` 注册到 `DefaultListableBeanFactory`中)与`ClassPathBeanDefinitionScanner`(该类用于扫描类路径下指定包(包括子包)中的类,解析这些类中的注解信息,然后生成对应的 `BeanDefinition`,最后同样对的也是将解析出的 `BeanDefinition` 注册到 `DefaultListableBeanFactory`中),接下来我们重点关注一下`AnnotatedBeanDefinitionReader`类的构造函数
```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);
}
```
首先会通过调用 `getOrCreateEnvironment(registry)` 来获取或创建一个 `Environment`。`Environment` 是 Spring 中用于处理应用环境的接口,它能够访问到应用的环境变量、系统属性等信息。然后,构造函数会用传入的 `registry` 和获取到的 `Environment` 作为参数,调用另一个构造函数 `AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment)`,完成`AnnotatedBeanDefinitionReader`的初始化。
```java
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
this(registry, getOrCreateEnvironment(registry));
}
```
在继续跟进构造函数,在函数中做了一些参数校验工作,这里 `this.conditionEvaluator` 是通过创建一个新的 `ConditionEvaluator` 实例来初始化的,`ConditionEvaluator` 是用来处理 `@Conditional` 注解的,这里只做了解就好,不是本次关注的重点。接下来的重点在`AnnotationConfigUtils.registerAnnotationConfigProcessors()`是一个用来注册各种注解处理器的静态方法。其中我们本次关注的核心类`ConfigurationClassPostProcessor`就是在这里被注册上的。
```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);
}
```
如果容器中还没有这个后置处理器的 `ConfigurationClassPostProcessor`,那么就创建一个 `RootBeanDefinition` 对象,并设置其 Bean 类型为 `ConfigurationClassPostProcessor.class`,即后置处理器的类型。然后,为这个 `BeanDefinition` 设置来源 `source`,这里传入的 `source` 参数是一个可选的对象,用于标识注册这些 BeanDefinition 的来源。接着,通过调用 `registerPostProcessor()` 方法将这个 `BeanDefinition` 注册到容器中。
```java
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
Set<BeanDefinitionHolder> 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;
}
```
这个过程实际上是将 `ConfigurationClassPostProcessor` 类注册为一个特殊的后置处理器,用于处理 `@Configuration` 注解的配置类,使得这些配置类能够正常生效并且能够注册其中的 `BeanDefinition` 到容器中。
```java
private static BeanDefinitionHolder registerPostProcessor(BeanDefinitionRegistry registry, RootBeanDefinition definition, String beanName) {
definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(beanName, definition);
return new BeanDefinitionHolder(definition, beanName);
}
```
下面是`ConfigurationClassPostProcessor`被注册过程的时序图
~~~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)
~~~
#### 6.2、register(componentClasses)
在上一个步骤中`this()`已经执行完毕,接下来我们回到`AnnotationConfigApplicationContext`的构造函数中的`register(componentClasses)`方法来,**该方法我们重点关注MyConfiguration是如何被注册的过程**。
```java
public AnnotationConfigApplicationContext(Class<?>... componentClasses) {
this();
register(componentClasses);
refresh();
}
```
通过调用 `this.reader.register(componentClasses);` 进行实际的组件类注册。这里的 `reader` (`AnnotatedBeanDefinitionReader`),在构造函数中初始化完成的)是一个用于解析和注册注解配置的对象,这个方法会处理给定的组件类,解析其注解,并将相应的 `BeanDefinition` 注册到Spring容器中。`componentClasses`实际的类就是我们的`MyConfiguration`。
```java
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();
}
```
接收一个可变参数数组 `componentClasses`在循环内部,对每一个 `componentClass`,都调用了 `registerBean` 方法。这个方法
```java
public void register(Class<?>... componentClasses) {
for (Class<?> componentClass : componentClasses) {
registerBean(componentClass);
}
}
```
在这个方法中,主要的逻辑被委托给了 `doRegisterBean` 方法
```java
public void registerBean(Class<?> beanClass) {
doRegisterBean(beanClass, null, null, null, null);
}
```
在这个方法中为`MyConfiguration`类创建`BeanDefinition`定义最后bean定义被封装在`BeanDefinitionHolder`中,并使用`BeanDefinitionReaderUtils.registerBeanDefinition`方法在bean定义注册表中注册。
```java
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,
@Nullable BeanDefinitionCustomizer[] customizers) {
// 1. 给MyConfiguration类创建一个新的AnnotatedGenericBeanDefinition
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);
// 2. 使用conditionEvaluator检查当前bean是否应被跳过
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
}
// 3. 如果提供了Supplier则设置到bean定义中
abd.setInstanceSupplier(supplier);
// 4. 解析bean的作用域singleton, prototype等
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
// 5. 生成或使用给定的bean名称
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
// 6. 处理常见的注解定义(例如:@Lazy, @Primary等)
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
// 7. 根据给定的限定符处理bean定义
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
abd.setPrimary(true);
}
else if (Lazy.class == qualifier) {
abd.setLazyInit(true);
}
else {
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
}
// 8. 使用任何提供的自定义器来修改bean定义
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
}
// 9. 创建一个BeanDefinitionHolder来持有bean定义及其名称
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
// 10. 如果需要,应用作用域代理模式
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
// 11. 将bean定义注册到bean定义注册表
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
```
方法首先获取bean的主名称并使用此名称将bean定义注册到注册表中。如果bean有别名该方法还会将这些别名也注册到注册表中。别名在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);
}
}
}
```
下面是`MyConfiguration`被注册过程的时序图
~~~mermaid
sequenceDiagram
ConfigurationApplication->>AnnotationConfigApplicationContext: AnnotationConfigApplicationContext(componentClasses)
AnnotationConfigApplicationContext-->>ConfigurationApplication: 返回context
AnnotationConfigApplicationContext-->>AnnotationConfigApplicationContext: register(componentClasses)
AnnotationConfigApplicationContext-->>AnnotatedBeanDefinitionReader: register(componentClasses)
AnnotatedBeanDefinitionReader-->>AnnotatedBeanDefinitionReader: registerBean(beanClass)
AnnotatedBeanDefinitionReader-->>AnnotatedBeanDefinitionReader: doRegisterBean(beanClass,name,qualifiers, supplier,customizers)
AnnotatedBeanDefinitionReader-->>BeanDefinitionReaderUtils: registerBeanDefinition(definitionHolder,registry)
BeanDefinitionReaderUtils-->>DefaultListableBeanFactory: registerBeanDefinition(beanName,beanDefinition)
~~~
#### 6.3、refresh()
在上一个步骤中`register(componentClasses)`已经执行完毕,接下来我们关注`refresh()`方法,在`refresh()`方法中调用了`invokeBeanFactoryPostProcessors(beanFactory);`是一个关键步骤,它确保所有的`BeanFactoryPostProcessor`被按预期的顺序执行从而允许对bean定义进行必要的修改和处理而`ConfigurationClassPostProcessor`类间接实现了`BeanFactoryPostProcessor`接口。**该方法我们重点关注ConfigurationClassPostProcessor是如何被调用过程**。
```java
public void refresh() throws BeansException, IllegalStateException {
// ----------------------忽略其他代码---------------------------
invokeBeanFactoryPostProcessors(beanFactory);
// ----------------------忽略其他代码---------------------------
}
```
在`invokeBeanFactoryPostProcessors`方法中又委托了`PostProcessorRegistrationDelegate`类中的`invokeBeanFactoryPostProcessors`去执行。在`getBeanFactoryPostProcessors()`这个方法从当前的`ApplicationContext`实例中检索所有已注册`BeanFactoryPostProcessor`
```java
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
// ----------------------忽略其他代码---------------------------
}
```
```java
public static void invokeBeanFactoryPostProcessors(
ConfigurableListableBeanFactory beanFactory, List<BeanFactoryPostProcessor> beanFactoryPostProcessors) {
// ----------------------忽略其他代码---------------------------
// Now, invoke the postProcessBeanFactory callback of all processors handled so far.
invokeBeanFactoryPostProcessors(registryProcessors, beanFactory);
// ----------------------忽略其他代码---------------------------
}
```
最后在第147行被调用执行了`invokeBeanFactoryPostProcessors(registryProcessors, beanFactory)`方法
![image-20230809155509098](https://img-blog.csdnimg.cn/21c9eda01a9048a3a8b047076c92a8dc.png#pic_center)
在此代码段中,`ConfigurationClassPostProcessor`的`postProcessBeanFactory`方法被调用,
```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();
}
}
```
![image-20230809160938216](https://img-blog.csdnimg.cn/361a4012d236408fa55397129e5b4226.png#pic_center)
接下来,我们看看`ConfigurationClassPostProcessor`类中的`postProcessBeanFactory`方法是如何对`@Configuration`注解进行CGLIB增强的。`postProcessBeanFactory`方法中的`enhanceConfigurationClasses`调用,对标注为`@Configuration`的类进行了增强,确保了它们的`@Bean`方法行为符合预期。继续跟进`enhanceConfigurationClasses`方法
```java
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
// ----------------------忽略其他代码---------------------------
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
```
`enhanceConfigurationClasses`方法中主要的功能就是从`beanFactory.getBeanDefinitionNames()`中遍历`BeanDefinition`筛选出full模式(proxyBeanMethods = true)下的`@Configuration`注解,然后通过`ConfigurationClassEnhancer`这个类来生成代理类(`com.xcs.spring.MyConfiguration$$EnhancerBySpringCGLIB$$60658f22`),然后进行替换`BeanDefinition`对象中的`beanClass`字段
```java
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
StartupStep enhanceConfigClasses = this.applicationStartup.start("spring.context.config-classes.enhance");
// 用于存储需要增强的配置类的bean定义的map
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
// 循环遍历bean工厂中的所有bean定义
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
// 获取元数据属性以识别配置类
Object configClassAttr = beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE);
AnnotationMetadata annotationMetadata = null;
MethodMetadata methodMetadata = null;
if (beanDef instanceof AnnotatedBeanDefinition) {
AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDef;
annotationMetadata = annotatedBeanDefinition.getMetadata();
methodMetadata = annotatedBeanDefinition.getFactoryMethodMetadata();
}
// 检查bean是否为配置类且尚未增强
if ((configClassAttr != null || methodMetadata != null) && beanDef instanceof AbstractBeanDefinition) {
// Configuration class (full or lite) or a configuration-derived @Bean method
// -> eagerly resolve bean class at this point, unless it's a 'lite' configuration
// or component class without @Bean methods.
AbstractBeanDefinition abd = (AbstractBeanDefinition) beanDef;
if (!abd.hasBeanClass()) {
boolean liteConfigurationCandidateWithoutBeanMethods =
(ConfigurationClassUtils.CONFIGURATION_CLASS_LITE.equals(configClassAttr) &&
annotationMetadata != null && !ConfigurationClassUtils.hasBeanMethods(annotationMetadata));
if (!liteConfigurationCandidateWithoutBeanMethods) {
try {
abd.resolveBeanClass(this.beanClassLoader);
}
catch (Throwable ex) {
throw new IllegalStateException(
"Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
}
}
}
}
// 检查bean定义是否为full模式配置类(proxyBeanMethods = true)如果是则将其添加到configBeanDefs中。
if (ConfigurationClassUtils.CONFIGURATION_CLASS_FULL.equals(configClassAttr)) {
if (!(beanDef instanceof AbstractBeanDefinition)) {
throw new BeanDefinitionStoreException("Cannot enhance @Configuration bean definition '" +
beanName + "' since it is not stored in an AbstractBeanDefinition subclass");
}
else if (logger.isInfoEnabled() && beanFactory.containsSingleton(beanName)) {
logger.info("Cannot enhance @Configuration bean definition '" + beanName +
"' since its singleton instance has been created too early. The typical cause " +
"is a non-static @Bean method with a BeanDefinitionRegistryPostProcessor " +
"return type: Consider declaring such methods as 'static'.");
}
// 添加到需要增强的配置类的bean定义的configBeanDefs
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
// // 如果没有配置类需要增强,则只需结束该步骤
if (configBeanDefs.isEmpty() || NativeDetector.inNativeImage()) {
// nothing to enhance -> return immediately
enhanceConfigClasses.end();
return;
}
// 初始化增强器,用于增强配置类
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
// 循环遍历需要增强的配置类
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// 对于@Configuration类始终代理目标类
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
// 在定义中设置增强后的类作为bean类
Class<?> configClass = beanDef.getBeanClass();
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
if (logger.isTraceEnabled()) {
logger.trace(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
// 吧BeanClass修改为代理类
beanDef.setBeanClass(enhancedClass);
}
}
enhanceConfigClasses.tag("classCount", () -> String.valueOf(configBeanDefs.keySet().size())).end();
}
```
![image-20230809175058746](https://img-blog.csdnimg.cn/3241f2e0658944a0af51786b98012938.png#pic_center)
接下来我们看看`ConfigurationClassEnhancer`类中的`enhance`方法是如何产生代理类的呢在这个方法中调用了2个比较重要的方法`newEnhancer`方法,`createClass`方法,我们继续跟进源码。。。
```java
public Class<?> enhance(Class<?> configClass, @Nullable ClassLoader classLoader) {
// 如果configClass已经是增强的或者说它已经是EnhancedConfiguration的子类或实例
// 则不再进行增强并返回原始的configClass。
if (EnhancedConfiguration.class.isAssignableFrom(configClass)) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Ignoring request to enhance %s as it has " +
"already been enhanced. This usually indicates that more than one " +
"ConfigurationClassPostProcessor has been registered (e.g. via " +
"<context:annotation-config>). This is harmless, but you may " +
"want check your configuration and remove one CCPP if possible",
configClass.getName()));
}
return configClass;
}
// 如果configClass不是已知的增强类型那么它将会被增强。
// 使用CGLIB或其他技术为configClass创建一个新的增强版本。
Class<?> enhancedClass = createClass(newEnhancer(configClass, classLoader));
if (logger.isTraceEnabled()) {
logger.trace(String.format("Successfully enhanced %s; enhanced class name is: %s",
configClass.getName(), enhancedClass.getName()));
}
// 返回增强的类。
return enhancedClass;
}
```
这个方法`newEnhancer`的目的是为一个指定的配置类(`MyConfiguration`创建一个CGLIB的`Enhancer`对象用于后续生成该配置类的代理或子类。在Spring中CGLIB是用来在运行时生成Java类的代码库
```java
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
// 创建一个新的CGLIB Enhancer实例。
Enhancer enhancer = new Enhancer();
// 设置要增强的类的超类,即原始的配置类。
enhancer.setSuperclass(configSuperClass);
// 设置增强类实现的接口。这里增强的类会实现EnhancedConfiguration接口
// 这通常用于后续的检查或识别。
// 使用场景,在上个方法中(enhance)作为判断条件 if (EnhancedConfiguration.class.isAssignableFrom(configClass))
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
// 设置使用工厂模式。这里设置为false意味着生成的增强类不会实现Factory接口。
enhancer.setUseFactory(false);
// 设置命名策略,这决定了生成的增强类的名称。
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
// 设置策略,该策略决定如何生成增强类的字节码。
// 这里策略还负责使增强类变为“BeanFactory-aware”
// 这意味着它可以与Spring的BeanFactory交互。
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
// 设置回调过滤器,该过滤器决定哪些方法需要被代理以及如何被代理。
enhancer.setCallbackFilter(CALLBACK_FILTER);
// 设置增强类需要使用的回调类型,基于前面设置的回调过滤器。
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
// 返回为指定配置类准备好的Enhancer。
return enhancer;
}
```
传入的CGLIB的`Enhancer`对象来创建一个新的增强类,并注册其相关的回调,我们看看`CALLBACKS`到底设置了那些回调参数
```java
private Class<?> createClass(Enhancer enhancer) {
// 使用Enhancer创建一个新的增强类。这实际上会生成一个新的类的字节码
// 该类是原始配置类的子类,并增加了一些额外的功能或行为。
Class<?> subclass = enhancer.createClass();
// 注册回调函数。这些回调函数决定增强类中的哪些方法如何被增强或代理。
// 使用静态注册而不是基于线程局部的在OSGi环境中是关键的因为它确保
// 回调在所有线程和类加载器之间都是可见的和一致的。
// OSGi是一个Java模块化系统在这种环境中类加载器和线程的行为可能与
// 标准Java应用有所不同所以特殊处理是必要的。
Enhancer.registerStaticCallbacks(subclass, CALLBACKS);
// 返回新创建的增强类。
return subclass;
}
```
让我们看看这三个回调:
`BeanMethodInterceptor`: 用于增强或代理那些对应于bean的方法
`BeanFactoryAwareMethodInterceptor`: 为代理类中的`$$beanFactory`字段赋值具体请查看arthas反编译后的字节码类
`NoOp.INSTANCE`: 这是CGLIB提供的一个特殊回调代表不执行任何操作。当使用这个回调增强方法时方法的原始行为将不会被改变。
```java
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
```
首先看看`BeanMethodInterceptor`类的实现,此类主要用于拦截对 `@Configuration` 类中定义的 `@Bean` 方法的调用,以确保当这些方法被调用时,返回的 bean 实例是正确管理和处理的。
下面是拦截方法的参数介绍
- `enhancedConfigInstance`: 这是经过 CGLIB 增强的 `@Configuration` 类的实例。
- `beanMethod`: 被调用的 `@Bean` 方法。
- `beanMethodArgs`: 调用该方法时传递的参数。
- `cglibMethodProxy`: CGLIB 提供的方法代理,用于调用原始或超类的方法。
```java
/**
* 拦截并处理对 @Bean 方法的调用。
*/
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);
}
```
在看看`BeanFactoryAwareMethodInterceptor`类的实现,这个类的目的主要是为代理类设置的`$$beanFactory`的字段赋值
下面是拦截方法的参数介绍
- `obj`: 被代理的对象。
- `method`: 正在被调用的原方法。
- `args`: 调用方法时传入的参数。
- `proxy`: 代表CGLIB用于调用原始方法的`MethodProxy`对象。
```java
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 使用反射查找obj类中名为$$beanFactory的字段
Field field = ReflectionUtils.findField(obj.getClass(), BEAN_FACTORY_FIELD);
// 确保找到了相关字段,如果没有找到,则抛出异常。
Assert.state(field != null, "Unable to find generated BeanFactory field");
// 将obj对象的BEAN_FACTORY_FIELD字段设置为args[0]这里args[0]是BeanFactory的实例。
field.set(obj, args[0]);
// 检查obj的实际超类不包括CGLIB生成的部分是否实现了BeanFactoryAware接口。
if (BeanFactoryAware.class.isAssignableFrom(ClassUtils.getUserClass(obj.getClass().getSuperclass()))) {
// 如果实际超类实现了BeanFactoryAware接口那么它会有一个setBeanFactory()方法,
// 所以我们继续调用该方法。
return proxy.invokeSuper(obj, args);
}
// 如果实际超类没有实现BeanFactoryAware接口那么直接返回null。
return null;
}
```
最后看看`NoOp.INSTANCE`类的实现,你会发现这个类什么都没有干,那么为什么会设置一个这样的回调呢?其目的是为什么呢?
举个例子,假设你只想拦截和处理代理对象的`setXXX`方法,而其他所有方法(如`getXXX`)都应该按原样执行,没有额外的逻辑。在这种情况下,你可以为`setXXX`方法设置特定的拦截器,而为`getXXX`方法设置`NoOp.INSTANCE`。因为CGLIB默认是代理所有的方法的如果不提供NoOp.INSTANCE类那么你可能会出现一个这样的异常信息`Exception in thread "main" java.lang.IllegalArgumentException: No callback found for index 1`
```java
public interface NoOp extends Callback {
NoOp INSTANCE = new NoOp() {
};
}
```
下面是`ConfigurationClassPostProcessor`被执行的时序图
~~~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类
~~~
### 7、常见问题
#### 7.1、在@Bean主键在方法上时访问修饰符为什么不能是private或者final修饰呢
那么我们对这两种场景做个测试。。。
##### 7.1.1、private修饰符
```java
@Configuration
public class MyConfiguration {
@Bean
private MyBean myBean(){
return new MyBean();
}
}
```
运行代码发现直接报错,启动失败。
```java
8月 10, 2023 2:30:41 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'myBean' must not be private or final; change the method's modifiers to continue
Offending resource: com.xcs.spring.MyConfiguration
Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'myBean' must not be private or final; change the method's modifiers to continue
Offending resource: com.xcs.spring.MyConfiguration
at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:72)
at org.springframework.context.annotation.BeanMethod.validate(BeanMethod.java:52)
at org.springframework.context.annotation.ConfigurationClass.validate(ConfigurationClass.java:220)
at org.springframework.context.annotation.ConfigurationClassParser.validate(ConfigurationClassParser.java:216)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:332)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93)
at com.xcs.spring.ConfigurationApplication.main(ConfigurationApplication.java:15)
```
##### 7.1.2、final修饰符
```java
@Configuration
public class MyConfiguration {
@Bean
public final MyBean myBean(){
return new MyBean();
}
}
```
同样的依旧是此错误
```java
8月 10, 2023 2:33:24 下午 org.springframework.context.support.AbstractApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'myBean' must not be private or final; change the method's modifiers to continue
Offending resource: com.xcs.spring.MyConfiguration
Exception in thread "main" org.springframework.beans.factory.parsing.BeanDefinitionParsingException: Configuration problem: @Bean method 'myBean' must not be private or final; change the method's modifiers to continue
Offending resource: com.xcs.spring.MyConfiguration
at org.springframework.beans.factory.parsing.FailFastProblemReporter.error(FailFastProblemReporter.java:72)
at org.springframework.context.annotation.BeanMethod.validate(BeanMethod.java:52)
at org.springframework.context.annotation.ConfigurationClass.validate(ConfigurationClass.java:220)
at org.springframework.context.annotation.ConfigurationClassParser.validate(ConfigurationClassParser.java:216)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:332)
at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:247)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:311)
at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:112)
at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:746)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:564)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:93)
at com.xcs.spring.ConfigurationApplication.main(ConfigurationApplication.java:15)
```
##### 7.1.3、原因分析
1. **CGLIB代理**: 当Spring使用CGLIB创建`@Configuration`类的代理时它实际上是为这个类创建了一个子类。为了使代理可以工作CGLIB需要能够重写`@Configuration`类中的`@Bean`方法。
- **private方法**: 在java规范中被声明为`private`的方法不能被子类重写。由于CGLIB子类无法访问或重写这些方法所以如果`@Bean`方法被声明为`private`CGLIB代理将无法正确地管理它们。
- **final方法**: 同样地在java规范中`final`方法也不能被子类重写。因此,如果`@Bean`方法被声明为`final`CGLIB也将无法管理这些方法。
2. **单例语义**: 在标准的`@Configuration`类中,当一个`@Bean`方法被调用多次时它实际上只创建一个实例因为这些方法调用是通过代理拦截的。这保证了单例bean的单例语义。如果`@Bean`方法是`private`或`final`的Spring将无法拦截这些方法调用从而可能导致每次调用都创建一个新的bean实例违反了单例语义。
#### 7.2 @Configuration中full模式与lite模式如何选择
`@Configuration` 注解有两种模式:`full` 和 `lite`。它们在功能和性能上有所不同。了解它们的优缺点有助于为特定的场景做出合适的选择。
##### 7.2.1 Full 模式
- 启用方式:在 `@Configuration` 注解中不设置 `proxyBeanMethods` 或将其设置为 `true`
- 功能:当在配置类中的 `@Bean` 方法内部调用另一个 `@Bean` 方法时Spring 会确保返回的是容器中的单例bean而不是一个新的实例。这是通过CGLIB代理实现的。
- 优势保持单例语义确保容器中的单例Bean在配置类中的调用中始终是单例的。
- 劣势需要通过CGLIB创建配置类的子类可能带来一些性能开销增加了启动时间可能与某些库不兼容这些库期望操作实际类而不是其CGLIB代理。
##### 7.2.2 Lite 模式
- 启用方式:在 `@Configuration` 注解中设置 `proxyBeanMethods``false`
- 功能禁用CGLIB代理。`@Bean` 方法之间的调用就像普通的Java方法调用每次都会创建一个新的实例。
- 优势更快的启动时间因为不需要通过CGLIB增强配置类对于简单的注入这种模式可能更为简洁和直接。
- 劣势:不保持单例语义。如果在一个 `@Bean` 方法内部调用另一个 `@Bean` 方法会创建一个新的bean实例。
##### 7.2.3 如何选择
- 如果你的配置中需要确保在配置类中调用的bean始终是Spring容器中的单例bean选择full模式。
- 如果你的配置类只是简单地定义beans并注入依赖且不需要在配置类方法之间共享单例实例选择lite模式。
- 如果你关心应用的启动性能特别是在云环境或微服务中使用lite模式可能更合适因为它避免了额外的CGLIB处理。
最终根据项目的具体需求和场景选择合适的模式。如果没有特殊的单例需求推荐使用lite模式因为它更简单且启动性能更好。
### 8、总结
在这一节源码分析中,了解到了`@Configuration`的full模式`(proxyBeanMethods=true)`与lite模式`(proxyBeanMethods=false)`并在4.1与4.2中做了测试并验证,另外还了解到了`@Configuration`注解几种组合用法甚至我们可以多个组合使用的在spring中是非常常见的一种使用方式。然后我们利用arthas进行了反编译字节码进行原理分析发现是利用CGLIB对`MyConfiguration`类继承方式然后重写了@Bean注解修饰的的方法来完成代理并保证了`@Bean`的语义。最后我们对源码进行了分析,其中最核心的类就是`ConfigurationClassPostProcessor`这个类间接实现了`BeanFactoryPostProcessor`接口中的`postProcessBeanFactory`方法在这个方法中筛选出full模式下的的`BeanDefinition`然后进行CGLIB增强处理。