文章目录
相关文章:
【spring4.0】@Configuration、@Bean的使用
【spring】JavaConfig、@Configuration、@ComponentScan入门例子
1. 前言
本篇文章是接《ConfigurationClassPostProcessor》,在该篇最后,有2个疑问:
- 1.一个配置类必须加@Configuration注解?不加就不能被Spring解析了吗?
- 2.为什么加了@Configuration注解的类需要被CGLIB进行增强?
2. 问题1
配置类不必加@Configuration,可以仅加@ComponentScan。也就是说@bean也可以脱离@Configuration单独存在,但是要在被定义的扫描路径范围内。
代码来自《JavaConfig、@Configuration、@ComponentScan入门例子》中<1. 最简单的JavaConfig配置例子>章节的例子
我们注释掉@Configuration,发现代码仍能运行。
//@Configuration
public class MainConfig {
}
问题来了,那么加与不加@Configuration,有什么区别吗?
有区别:加了@Configuration的配置类,spring会对配置类进行cglib代理增强。
假如我们的配置类是MainConfig.java,spring会让MainConfig增加了一个接口EnhancedConfiguration,等价于:
public class MainConfig implements EnhancedConfiguration
于是MainConfig间接继承了BeanFactoryAware接口,那么加了该接口后,有什么用?下面我们来演示下。
2.1 演示加@Configuration和不加的效果
我们新增Driver.java,注意,没有任何注解修饰,我们将在配置类MainConfig中采用@Bean来注册:
内部持有有个Car属性
package com.test.spring;
public class Driver {
private String name;
private Car car;
public void setCar(Car car) {
this.car = car;
}
public void setName(String driver) {
this.name = driver;
}
public Car getCar() {
return this.car;
}
}
修改MainConfig.java,增加Driver的@Bean注释,同时注意有@Configuration:
通过dirive()和car()来注册Driver和Car,需要注意的是drive()内会调用一次car()
package com.test.spring;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* 定义扫描的路径,路径下带声明标签的类均会被解析
*/
@Configuration
@ComponentScan(basePackages = {"com.test.spring"})
public class MainConfig {
@Bean
public Driver driver(){
Driver driver = new Driver();
driver.setName("driver");
//注意:此处调用了car(),如果符合增强,那么此处的car()等价于 getBean("car"),单例模式,而不是把car()当做一个普通的方法
//当前前提是car()被@Bean修饰
driver.setCar(car());
return driver;
}
@Bean
public Car car(){
Car car = new Car();
car.setName("car");
return car;
}
}
修改执行的main方法:
package com.test.spring;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainStart {
public static void main(String[] args) {
// 加载spring上下文
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
Car car = (Car) context.getBean("car");
Driver driver = (Driver) context.getBean("driver");
//比较car是否是同一个实例
System.out.println(car == driver.getCar());
}
}
MainConfig.java,有@Configuration时执行结果:
true '说明car是同一个实例'
去掉MainConfig.java中的@Configuration时的执行结果:
false '说明car是2个不同的实例'
结论:有@Configuration,经过cglib增强后,会全局维护一个car实例。
那么是什么原理呢?
2.2 cglib增强的原理
简单来说,增强后,每次获取的bean的操作会经BeanMethodInterceptor处理,该拦截器会缓存初次创建的bean实例,后续的访问从缓存中(放入BeanFacotry)读取,这样就保证了共享一个实例。
先看下代码的执行顺序:
如上图所示,核心正是通过代理,绑定BeanMethodInterceptor和BeanFactoryAwareMethodInterceptor
2.2.1BeanMethodInterceptor
回调函数,类似拦截器,每次都会调用,负责返回实例对象:
核心原理如下图中直接调用car(),和在driver()中调用car()是同一个实例,原因是增强后,会把把car() 替换为 BeanFacotry.getBean(“car”):
BeanMethodInterceptor:
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
@Override
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
//[1] 获得BeanFactory
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
//[2] 获得原始方法返回对象使用的BeanName
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// [3]获得原始方法上的@Scope注解,如果指定了动态代理模型,则为其生成动态代理模型特征的BeanName
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;
}
}
/**
* [4]这里是对FactoryBean的处理,如果BeanFactory中存在BeanName的定义
* 并且存在'&'+BeanName的定义(BeanFactory默认BeanName)
* 则认为方法返回的类型是BeanFactory类型,则还需要对getObject()方法进行代理
* 也就是说代理中又有代理
*/
if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
factoryContainsBean(beanFactory, beanName)) {
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
if (factoryBean instanceof ScopedProxyFactoryBean) {
// Scoped proxy factory beans are a special case and should not be further proxied
}
else {
// It is a candidate FactoryBean - go ahead with enhancement
return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
}
}
//[5]判断是执行方法创建对象返回还是从容器中拿对象返回
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
// [5.1]创建新的实例,并返回
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
//[5.2]从容器中获取对象返回
return obtainBeanInstanceFromFactory(beanMethod, beanMethodArgs, beanFactory, beanName);
}
[5]会判断从容器中读取已有的,还是新创建(首次创建后,会加入到容器中),
判断的依据是比较当前线程调用的方法和动态代理调用的方法名称是否一致,一致的说明是初次实例化调用,调用父类方法进行实例化,不一致说明不是初次示例化调用,从BeanFactory中获取:
isCurrentlyInvokedFactoryMethod():
//入参是方法体内new xxx()中的xxx,例如car()
private boolean isCurrentlyInvokedFactoryMethod(Method method) {
//外层方法名称,比如driver()
Method currentlyInvoked = SimpleInstantiationStrategy.getCurrentlyInvokedFactoryMethod();
return (currentlyInvoked != null && method.getName().equals(currentlyInvoked.getName()) &&
Arrays.equals(method.getParameterTypes(), currentlyInvoked.getParameterTypes()));
}
3. 问题2 为什么加了@Configuration注解的类需要被CGLIB进行增强?
原因是为了保证单例原则
4. 总结
@Configuration注解对于配置类来说是非必须的。
@Configuration作用是使用CGLIB对配置类进行增强,增强的效果是保证@Bean下的单例原则。
如果我想让@Configuration配置类为多例时怎么办?
设置 @Configuration(proxyBeanMethods = false)
即可
参考:《执行ConfigurationClassPostProcessor#postProcessBeanFactory方法对@Configuration配置类的@Bean方法进行CGLIB代理增强》