@Configuration原理与实战
文章目录
- 前言
- 一、@Component与@Configuration区别
- 二、@Configuration的解析
- 三、生成CGLIB动态代理
- 四、目标方法的调用
- 4.1、BeanMethodInterceptor
- 总结
前言
@Configuration
是Spring提供的注解,其作用是将该类标识为一个Java 配置类,用来替代传统的 XML 配置文件:
@Configuration
public class AppConfig {@Beanpublic MyService myService() {return new MyService();}
}
上述代码等价于传统 XML 中:
<bean id="myService" class="com.example.MyService"/>
与标注了@Component
注解的配置类相比,区别在于Spring 在解析@Configuration
类时,会使用 CGLIB 生成子类代理,重写其中所有@Bean
方法。
一、@Component与@Configuration区别
@Configuration案例工程:
public class MainApp {public static void main(String[] args) {// 创建应用上下文AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);AppConfig appConfig = context.getBean(AppConfig.class);System.out.println("------ 执行 AppConfig 中的 testBeanMethodCall ------");appConfig.testBeanMethodCall(); // 使用的是代理对象,返回同一个 Beancontext.close();}
}class HelloService {}@Configuration
class AppConfig {@Beanpublic HelloService helloService() {return new HelloService();}public void testBeanMethodCall() {HelloService s1 = helloService();HelloService s2 = helloService();System.out.println("AppConfig -> helloService() s1 == s2 ? " + (s1 == s2));}
}
运行结果:
------ 执行 AppConfig 中的 testBeanMethodCall ------
AppConfig -> helloService() s1 == s2 ? true
@Component案例工程:
public class MainApp {public static void main(String[] args) {// 创建应用上下文AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);AppConfig appConfig = context.getBean(AppConfig.class);System.out.println("------ 执行 AppConfig 中的 testBeanMethodCall ------");appConfig.testBeanMethodCall(); // 使用的是代理对象,返回同一个 Beancontext.close();}
}class HelloService {}@Component
class AppConfig {@Beanpublic HelloService helloService() {return new HelloService();}public void testBeanMethodCall() {HelloService s1 = helloService();HelloService s2 = helloService();System.out.println("AppConfig -> helloService() s1 == s2 ? " + (s1 == s2));}
}
运行结果:
------ 执行 AppConfig 中的 testBeanMethodCall ------
AppConfig -> helloService() s1 == s2 ? false
通过最终的运行结果可以看出,被@Configuration
修饰的配置类中,获取多次被@Bean
注解标注的类,**获取到的都是相同的实例。**而被@Component
修饰的配置类中,获取多次被@Bean
注解标注的类,每次获取到的都是新的实例。
二、@Configuration的解析
@Configuration
的解析,同样在ConfigurationClassPostProcessor
后置处理器中:
如果当前配置类上,有@Configuration
注解,那么会根据proxyBeanMethods
的值,走不同的分支,proxyBeanMethods
是@Configuration
注解的属性,默认为true:
在配置类上加入了@Configuration
注解,并且没有显式地标注proxyBeanMethods
为false,则会将配置类
的bean定义中的attributes的value设置为full,表示当前的配置类是一个完整的配置类:
上述的过程是@Configuration的解析阶段
三、生成CGLIB动态代理
ConfigurationClassPostProcessor
后置处理器,实现了BeanDefinitionRegistryPostProcessor
,而BeanDefinitionRegistryPostProcessor
又继承了普通的bean工厂后处理器BeanFactoryPostProcessor
:
在invokeBeanFactoryPostProcessors
的invokeBeanFactoryPostProcessors
方法中,会调用ConfigurationClassPostProcessor
重写的父类BeanFactoryPostProcessor
的postProcessBeanFactory
方法:
收集所有bean定义的attributes的value为full的bean成map。key是bean的名称,value是对应的bean定义,在上面的案例工程中对应的就是AppConfig
类。
然后进行遍历,生成CGLIB动态代理。
这里设置的拦截器链,是定义在ConfigurationClassEnhancer
中的一个属性:
private static final Callback[] CALLBACKS = new Callback[] {new BeanMethodInterceptor(), // 拦截 @Bean 方法调用,核心代理逻辑new BeanFactoryAwareMethodInterceptor(),// 拦截实现 BeanFactoryAware 的方法NoOp.INSTANCE // 无操作回调,默认行为
};
最后将AppConfig的类型设置为代理类型。
四、目标方法的调用
在调用案例工程的testBeanMethodCall
时,会走过BeanMethodInterceptor
拦截器的intercept
方法。
4.1、BeanMethodInterceptor
如果容器是第一次调用@Bean的目标方法,就真正执行 @Bean 方法体:
否则,直接从容器中获取已有的 bean:
关键代码,保证每次从容器中拿到的都是同一个单例实例
总结
特性 | @Configuration | @Component |
---|---|---|
是否会被 Spring 扫描注册为 Bean | 是 | 是 |
是否支持 @Bean 方法注册 Bean | 支持(推荐) | 支持(不推荐) |
@Bean 方法是否被代理增强 | 会(返回容器单例对象) | 不会(每次 new 新对象) |
是否能作为配置中心使用 | 推荐用于声明多个 @Bean | 不推荐作为配置类使用 |