From d5fe4cf858a7c891d61fc442992ea865bbe5f7b6 Mon Sep 17 00:00:00 2001 From: xuchengsheng Date: Sat, 7 Oct 2023 18:02:16 +0800 Subject: [PATCH] =?UTF-8?q?@Configuration=E6=B3=A8=E8=A7=A3=E6=BA=90?= =?UTF-8?q?=E7=A0=81=E5=88=86=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- .../README-BACK.md | 1322 ++++++++++++++++ spring-annotation-configuration/README.md | 1364 +---------------- .../xcs/spring/ConfigurationApplication.java | 11 +- .../spring/{ => config}/MyConfiguration.java | 2 +- 5 files changed, 1399 insertions(+), 1304 deletions(-) create mode 100644 spring-annotation-configuration/README-BACK.md rename spring-annotation-configuration/src/main/java/com/xcs/spring/{ => config}/MyConfiguration.java (91%) diff --git a/README.md b/README.md index d81cd4e..8142f7e 100644 --- a/README.md +++ b/README.md @@ -62,9 +62,9 @@ + [关于ApplicationContextAware源码分析](spring-aware-applicationContextAware/README.md) + [关于ImportAware源码分析](spring-aware-importAware/README.md) + 核心注解 - + [关于@Bean源码分析](spring-annotation-bean/README.md) - + [关于@ComponentScan源码分析](spring-annotation-componentScan/README.md) + [关于@Configuration源码分析](spring-annotation-configuration/README.md) + + [关于@ComponentScan源码分析](spring-annotation-componentScan/README.md) + + [关于@Bean源码分析](spring-annotation-bean/README.md) + [关于@Import源码分析](spring-annotation-import/README.md) + [关于@PropertySource源码分析](spring-annotation-propertySource/README.md) + Bean工厂 diff --git a/spring-annotation-configuration/README-BACK.md b/spring-annotation-configuration/README-BACK.md new file mode 100644 index 0000000..46cacb2 --- /dev/null +++ b/spring-annotation-configuration/README-BACK.md @@ -0,0 +1,1322 @@ +## @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.config.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.config.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.config.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增强处理。 diff --git a/spring-annotation-configuration/README.md b/spring-annotation-configuration/README.md index 248c326..e8d4c17 100644 --- a/spring-annotation-configuration/README.md +++ b/spring-annotation-configuration/README.md @@ -1,1318 +1,100 @@ ## @Configuration -[TOC] +- [@Configuration](#configuration) + - [一、注解描述](#一注解描述) + - [二、注解源码](#二注解源码) + - [三、主要功能](#三主要功能) + - [四、最佳实践](#四最佳实践) + - [五、时序图](#五时序图) + - [六、源码分析](#六源码分析) + - [七、注意事项](#七注意事项) + - [八、总结](#八总结) + - [8.1、最佳实践总结](#81最佳实践总结) + - [8.2、源码分析总结](#82源码分析总结) -### 1、注解说明 +### 一、注解描述 -`@Configuration`是Spring框架中的一个核心注解,主要用于类级别,标识该类是一个配置类。配置类是Spring IoC容器的重要组成部分,它包含了Spring容器如何初始化和配置应用中的bean的信息。在配置类中,你可以定义一个或多个公共的`@Bean`注解方法,这些方法会实例化、配置并初始化新的对象,然后这些对象被Spring IoC容器管理 +`@Configuration` 是 Spring 框架中提供的一个核心注解,它指示一个类声明了一个或多个 `@Bean` 定义方法,这些方法由 Spring 容器管理并执行,以便在运行时为 bean 实例化、配置和初始化对象。 -### 2、注解源码 +### 二、注解源码 ```java +/** + * @Configuration 是一个核心注解,用于指示该类是一个 Spring 配置类, + * 可能包含一个或多个 @Bean 定义方法。此注解与 XML 配置是等效的,但以编程方式 + * 提供了更多的类型安全和灵活性。它通常与 @Bean、@ComponentScan 和其他注解结合使用, + * 为 Spring 应用程序上下文定义 beans 和配置。 + * + * 通过 @Component 注解,@Configuration 被视为一个组件, + * 这意味着 Spring 的组件扫描可以自动检测和处理它。 + * + * 例如,当在应用程序上下文中注册 @Configuration 类时, + * 该类中定义的 @Bean 方法将被解析,并在上下文中注册相应的 beans。 + * + * @author Rod Johnson + * @author Chris Beams + * @author Juergen Hoeller + * @since 3.0 + * @see Bean + * @see Profile + * @see Import + * @see ImportResource + * @see ComponentScan + * @see Lazy + * @see PropertySource + * @see AnnotationConfigApplicationContext + * @see ConfigurationClassPostProcessor + * @see org.springframework.core.env.Environment + * @see org.springframework.test.context.ContextConfiguration + */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface Configuration { + /** + * 明确指定与 @Configuration 类相关的 Spring bean 定义的名称。 + * 如果未指定(这是常见情况),则会自动生成 bean 的名称。 + * 这个自定义名称仅在 @Configuration 类通过组件扫描被检测到, + * 或直接提供给 AnnotationConfigApplicationContext 时才有效。 + * 如果 @Configuration 类以传统的 XML bean 定义方式注册, + * 则 bean 元素的名称/ID 会优先。 + */ @AliasFor(annotation = Component.class) String value() default ""; + /** + * 指定是否应代理 @Bean 方法,以强制执行 bean 生命周期行为。 + * 例如,即使在用户代码中直接调用了 @Bean 方法,也要返回共享的单例 bean 实例。 + * 这个特性需要方法拦截,通过在运行时生成的 CGLIB 子类来实现, + * 这带有一些限制,如配置类和其方法不能声明为 final。 + * + * 默认为 true,这允许在配置类内部通过直接方法调用进行"bean之间的引用", + * 以及从另一个配置类对此配置的 @Bean 方法的外部调用。 + * 如果这不是必需的,因为此特定配置的每个 @Bean 方法都是独立的, + * 并设计为容器使用的简单工厂方法,可以将此标志设置为 false 以避免 CGLIB 子类处理。 + * + * 关闭 bean 方法拦截实际上是独立处理 @Bean 方法, + * 就像在非 @Configuration 类上声明的那样,即 "@Bean 简易模式"。 + * 在行为上,它等同于删除 @Configuration 注解。 + */ boolean proxyBeanMethods() default true; -} -``` - -### 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,此处使用缺省值 +#### 8.1、最佳实践总结 -```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增强处理。 +#### 8.2、源码分析总结 \ No newline at end of file diff --git a/spring-annotation-configuration/src/main/java/com/xcs/spring/ConfigurationApplication.java b/spring-annotation-configuration/src/main/java/com/xcs/spring/ConfigurationApplication.java index e65e108..a98d415 100644 --- a/spring-annotation-configuration/src/main/java/com/xcs/spring/ConfigurationApplication.java +++ b/spring-annotation-configuration/src/main/java/com/xcs/spring/ConfigurationApplication.java @@ -1,9 +1,8 @@ package com.xcs.spring; +import com.xcs.spring.config.MyConfiguration; import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import java.io.IOException; - /** * @author xcs * @date 2023年08月07日 16时21分 @@ -16,13 +15,5 @@ public class ConfigurationApplication { 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(); - } } } diff --git a/spring-annotation-configuration/src/main/java/com/xcs/spring/MyConfiguration.java b/spring-annotation-configuration/src/main/java/com/xcs/spring/config/MyConfiguration.java similarity index 91% rename from spring-annotation-configuration/src/main/java/com/xcs/spring/MyConfiguration.java rename to spring-annotation-configuration/src/main/java/com/xcs/spring/config/MyConfiguration.java index a191a57..755ac7b 100644 --- a/spring-annotation-configuration/src/main/java/com/xcs/spring/MyConfiguration.java +++ b/spring-annotation-configuration/src/main/java/com/xcs/spring/config/MyConfiguration.java @@ -1,4 +1,4 @@ -package com.xcs.spring; +package com.xcs.spring.config; import com.xcs.spring.bean.MyBean; import org.springframework.context.annotation.Bean;