spring中@Bean是可以单独使用的,不配合@Configuration等使用会有什么风险或缺陷?

作者:奔跑中的程序猿
链接:https://www.zhihu.com/question/360254920/answer/1661909687
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

明确回答你有陷阱,下面我们以例子来说明:

  1. 不使用@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方法时会先检测容器中是否存在

  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值