作者:奔跑中的程序猿
链接:https://www.zhihu.com/question/360254920/answer/1661909687
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
明确回答你有陷阱,下面我们以例子来说明:
- 不使用@Configuration注解测试
public class Car {
}
public class Person {
public Person(Car car) {
System.out.println(car);
}
}
public class MainConfig {
@Bean
public Person person(){
return new Person(car());
}
@Bean
public Car car(){
return new Car();
}
}
测试类:
public class App
{
public static void main( String[] args )
{
//启动注册 bean MainConfig,所以不需要加 @Component注解
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MainConfig.class);
System.out.println(ctx.getBean("person"));
System.out.println(ctx.getBean("car"));
}
}
那么两次打印car对象时一个吗?
com.xx.testconfiuration.compent.Car@7e2d773b
com.xx.testconfiuration.compent.Person@87f383f
com.xx.testconfiuration.compent.Car@4eb7f003
不是同一个;
2. 使用@Configuration注解测试
@Configuration
public class MainConfig {
@Bean
public Person person(){
return new Person(car());
}
@Bean
public Car car(){
return new Car();
}
}
结果:
com.xx.testconfiuration.compent.Car@32a068d1
com.xx.testconfiuration.compent.Person@6b1274d2
com.xx.testconfiuration.compent.Car@32a068d1
是同一个对象,那么为什么会有以上两种结果呢?
Spring 容器在启动时,会加载默认的一些PostProcessor
,其中就有ConfigurationClassPostProcessor
,这个后置处理程序专门处理带有@Configuration
注解的类,这个程序会在bean 定义加载完成后,在bean初始化前进行处理。主要处理的过程就是使用cglib
动态代理增强类,而且是对其中带有@Bean
注解的方法进行处理。
在ConfigurationClassPostProcessor 中的 postProcessBeanFactory 方法中调用了下面的方法:
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
int factoryId = System.identityHashCode(beanFactory);
if (this.factoriesPostProcessed.contains(factoryId)) {
throw new IllegalStateException(
"postProcessBeanFactory already called on this post-processor against " + beanFactory);
}
this.factoriesPostProcessed.add(factoryId);
if (!this.registriesPostProcessed.contains(factoryId)) {
// BeanDefinitionRegistryPostProcessor hook apparently not supported...
// Simply call processConfigurationClasses lazily at this point then.
processConfigBeanDefinitions((BeanDefinitionRegistry) beanFactory);
}
// 使用cglib 对配置类进行代理,因为@Bean 方法到时候要进行创建Bean的实例
enhanceConfigurationClasses(beanFactory);
beanFactory.addBeanPostProcessor(new ImportAwareBeanPostProcessor(beanFactory));
}
public void enhanceConfigurationClasses(ConfigurableListableBeanFactory beanFactory) {
Map<String, AbstractBeanDefinition> configBeanDefs = new LinkedHashMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition beanDef = beanFactory.getBeanDefinition(beanName);
/*
只有full版配置类才会创建cglib代理
虽然我们在指定配置的时候不标注@Configuration也行,所以加不加注解的区别就在这里
那么加了@Configuration和不加有本质上有什么区别的?
当在配置类中一个@Bean 使用方法的方式引用另一个Bean如果不加注解就会重复加载Bean
如果加了@Configuration 则会在这里创建cglib代理,当调用@Bean方法时会先检测容器中是否存在*/
if (ConfigurationClassUtils.isFullConfigurationClass(beanDef)) {
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.isWarnEnabled() && beanFactory.containsSingleton(beanName)) {
logger.warn("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'.");
}
configBeanDefs.put(beanName, (AbstractBeanDefinition) beanDef);
}
}
if (configBeanDefs.isEmpty()) {
// nothing to enhance -> return immediately
return;
}
ConfigurationClassEnhancer enhancer = new ConfigurationClassEnhancer();
for (Map.Entry<String, AbstractBeanDefinition> entry : configBeanDefs.entrySet()) {
AbstractBeanDefinition beanDef = entry.getValue();
// If a @Configuration class gets proxied, always proxy the target class
beanDef.setAttribute(AutoProxyUtils.PRESERVE_TARGET_CLASS_ATTRIBUTE, Boolean.TRUE);
try {
// Set enhanced subclass of the user-specified bean class
Class<?> configClass = beanDef.resolveBeanClass(this.beanClassLoader);
if (configClass != null) {
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
if (configClass != enhancedClass) {
if (logger.isDebugEnabled()) {
logger.debug(String.format("Replacing bean definition '%s' existing class '%s' with " +
"enhanced class '%s'", entry.getKey(), configClass.getName(), enhancedClass.getName()));
}
// 重新修改Bean定义的Class,在创建Bean的实例时将会实例cglib的类
beanDef.setBeanClass(enhancedClass);
}
}
}
catch (Throwable ex) {
throw new IllegalStateException("Cannot load configuration class: " + beanDef.getBeanClassName(), ex);
}
}
}
在方法的第一次循环中,查找到所有带有@Configuration
注解的 bean 定义,然后在第二个 for 循环中,通过下面的方法对类进行增强:
Class<?> enhancedClass = enhancer.enhance(configClass, this.beanClassLoader);
然后使用增强后的类替换了原有的beanClass
:
beanDef.setBeanClass(enhancedClass);
所以到此时,所有带有@Configuration
注解的 bean 都已经变成了增强的类。
我们再看增强的方法 enhance
private Enhancer newEnhancer(Class<?> configSuperClass, @Nullable ClassLoader classLoader) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(configSuperClass);
enhancer.setInterfaces(new Class<?>[] {EnhancedConfiguration.class});
enhancer.setUseFactory(false);
enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader));
enhancer.setCallbackFilter(CALLBACK_FILTER);
enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes());
return enhancer;
}
通过cglib代理的类在调用方法时,会通过CallbackFilter
调用,这里的CALLBACK_FILTER
如下:
// The callbacks to use. Note that these callbacks must be stateless.
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
其中BeanMethodInterceptor
匹配方法如下:
@Override
public boolean isMatch(Method candidateMethod) {
return BeanAnnotationHelper.isBeanAnnotated(candidateMethod);
}
//BeanAnnotationHelper
public static boolean isBeanAnnotated(Method method) {
return AnnotatedElementUtils.hasAnnotation(method, Bean.class);
}
也就是当方法有@Bean
注解的时候,就会执行这个回调方法。
另一个BeanFactoryAwareMethodInterceptor
匹配的方法如下:
@Override
public boolean isMatch(Method candidateMethod) {
return (candidateMethod.getName().equals("setBeanFactory") &&
candidateMethod.getParameterTypes().length == 1 &&
BeanFactory.class == candidateMethod.getParameterTypes()[0] &&
BeanFactoryAware.class.isAssignableFrom(candidateMethod.getDeclaringClass()));
}
当前类还需要实现BeanFactoryAware
接口,上面的isMatch
就是匹配的这个接口的方法
BeanMethodInterceptor
,看看带有 @Bean
注解的方法执行的逻辑。下面分解来看intercept
方法。
//首先通过反射从增强的 Configuration 注解类中获取 beanFactory
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
//然后通过方法获取 beanName,默认为方法名,可以通过 @Bean 注解指定
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
//确定这个 bean 是否指定了代理的范围
//默认下面 if 条件 false 不会执行
Scope scope = AnnotatedElementUtils.findMergedAnnotation(beanMethod, Scope.class);
if (scope != null && scope.proxyMode() != ScopedProxyMode.NO) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}
//中间跳过一段 Factorybean 相关代码
//判断当前执行的方法是否为正在执行的 @Bean 方法
//因为存在在 userInfo() 方法中调用 country() 方法
//如果 country() 也有 @Bean 注解,那么这个返回值就是 false.
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
//直接调用原方法创建 bean
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
//如果不满足上面 if,也就是在 userInfo() 中调用的 country() 方法
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName)
关于isCurrentlyInvokedFactoryMethod
方法
可以参考 SimpleInstantiationStrategy 中的 instantiate 方法,这里先设置的调用方法:
currentlyInvokedFactoryMethod.set(factoryMethod);
return factoryMethod.invoke(factoryBean, args);
而通过方法内部直接调用 car() 方法时,不走上面的逻辑,直接进的代理方法,也就是当前的 intercept方法,因此当前的工厂方法和执行的方法就不相同了。
obtainBeanInstanceFromFactory
方法比较简单,就是通过beanFactory.getBean
获取Country
,如果已经创建了就会直接返回,如果没有执行过,就会通过invokeSuper
首次执行。
总结
那么加了@Configuration和不加有本质上有什么区别的?
当在配置类中一个@Bean 使用方法的方式引用另一个Bean如果不加注解就会重复加载Bean 如果加了@Configuration 则会在这里创建cglib代理,当调用@Bean方法时会先检测容器中是否存在