## @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实例。如果设置为false(lite模式),那么`@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个对象是一致的image-20230808100238267 #### 4.2、测试proxyBeanMethods为false情况 创建`MyConfiguration`类,作为spring的启动配置引导类,此处proxyBeanMethods设置为false ```java @Configuration(proxyBeanMethods = false) public class MyConfiguration { @Bean public MyBean myBean(){ return new MyBean(); } } ``` 通过再次运行main方法,我们发现打印出来的2个对象是不一致的 image-20230808102446140 #### 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 // 使用@Configuration,5.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 // 使用@Configuration,5.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/) image-20230808140424359 通过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模式的一些区别了吧。 image-20230808142855277 ### 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 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; } ``` 这个过程实际上是将 `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 void doRegisterBean(Class beanClass, @Nullable String name, @Nullable Class[] qualifiers, @Nullable Supplier 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 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 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 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 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 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 " + "). 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.(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.(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增强处理。