单例Bean依赖原型Bean
代码
@Component
public class SingletonBean {
@Autowired
private PrototypeBean bean;
public void print() {
System.out.println("bean hashcode =>" + bean.hashCode());
// System.out.println("bean toString =>" + bean.toString());
}
}
@Component
@Scope("prototype")
//@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class PrototypeBean {
@Autowired
public PrototypeBean() {
System.out.println("create prototypeBean + " + LocalDateTime.now().toString());
}
}
@ComponentScan("com.demo.v6")
public class AppConfigV6 {
}
@Test
public void v6Test1() {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfigV6.class);
SingletonBean singletonBean = (SingletonBean) applicationContext.getBean("singletonBean");
System.out.println(singletonBean);
singletonBean.print();
singletonBean.print();
}
方案一:
- 使用
ApplicationContext
在单例Bean中注入 ApplicationContext(或者实现ApplicationContextAware接口),每次调用方法时,都从容器中获取一个新的原型Bean
方案二:
- 使用
@Lookup注解
@Component
public abstract class SingletonBean {
private PrototypeBean bean;
public void print() {
bean = methodInject();
System.out.println("bean hashcode =>" + bean.hashCode());
// System.out.println("bean toString =>" + bean.toString());
}
@Lookup
// @Lookup("prototypeBean")
protected abstract PrototypeBean methodInject();
}
- 原理
单例Bean虽然是抽象的,但存在有Lookup注解修改的方法,故还是会解析成一个BeanDefiniton。
单例Bean在推断构造方法时,调用 AutowiredAnnotationBeanPostProcessor.determineCandidateConstructors(),方法内部,会判断当前类是否存在被 @Lookup注解修饰的方法,如果存在,会构建一个 LookupOverride对象,并将该对象添加到 RootBeanDefinition.methodOverrides中,注意,该属性非常重要,后面会用到。
执行Bean的实例化操作。调用
AbstractAutowireCapableBeanFactory.instantiateBean(),核心代码 beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, this);
getInstantiationStrategy()先获取一个实例化策略对象,此处获取的是:CglibSubclassingInstantiationStrategy,该类在DefaultListableBeanFactory创建时默认new出来的。
// AbstractAutowireCapableBeanFactory
/**
* Return the instantiation strategy to use for creating bean instances.
*/
protected InstantiationStrategy getInstantiationStrategy() {
return this.instantiationStrategy;
}
/** Strategy for creating bean instances. */
private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();
public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationStrategy
调用 instantiate()实际上执行的是父类SimpleInstantiationStrategy.instantiate(), 方法内部会判断 RootBeanDefinition.methodOverrides是否为空,上面在推断构造方法时,已经add值了,故这里是非空的,会执行下面逻辑:instantiateWithMethodInjection(bd, beanName, owner);
该方法会去调用子类CglibSubclassingInstantiationStrategy.instantiateWithMethodInjection(),最终调用子类CglibSubclassingInstantiationStrategy.instantiate()方法
方法内部调用 createEnhancedSubclass(),
在这里创建了一个Cglib代理类。最终用该代理类创建了一个代理对象。
故单例Bean在实例化时,创建了一个代理对象,并将该代理对象放到了Spring容器中。
执行单例对象的print()->methodInject(),会执行CglibSubclassingInstantiationStrategy.intercept()
内部调用 DefaultListableBeanFactory.getBean(Class<T> requiredType),最终实现每次都从容器中拿到一个新的原型对象。
方法三:
- 使用
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class PrototypeBean {
@Autowired
public PrototypeBean() {
System.out.println("create prototypeBean + " + LocalDateTime.now().toString());
}
}
- 原理
扫描Class文件,找到候选的BeanDefinition,在ClassPathBeanDefinitionScanner.doScan()内部,循环遍历BeanDefinition
由于原型Bean设置的proxyMode = ScopedProxyMode.TARGET_CLASS,在执行 AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);会构造一个新的BeanDefinition
最终调用 ScopedProxyUtils.createScopedProxy()
方法内部会创建一个新的 BeanDefinitionHolder,对应的beanName就是原始BeanDefinition的名字。而原始的BeanDefinition也会放到DefaultListableBeanFactory.beanDefinitionMap属性中,只不过beanName增加了前缀`scopedTarget.来区分。需要注意的一个属性,原始BeanDefinition.autowireCandidate被设置成false,在单例bean依赖注入原型Bean时有用。最终的 beanDefinitionMap属性
ScopedProxyFactoryBean,是一个FactoryBean,同时也实现了 BeanFactoryAware接口
Spring容器在实例化对象时,是根据beanDefinitionName来循环实例化对象的,由于PrototypeBean作用范围是prototype,故不会实例化一个对象。
prototypeBean->ScopedProxyFactoryBean,在Bean的生命周期过程中,由于是一个BeanFactoryAware,所以在实例化后,初始化前,会执行invokeAwareMethods,方法内部会调用 setBeanFactory(),ScopedProxyFactoryBean.setBeanFactory()方法会被调用
方法最后调用 pf.getProxy(cbf.getBeanClassLoader()) 产生一个代理对象,并赋值给 proxy属性,该属性很重要,依赖注入原型Bean的时候会用到,下面讲。
pf.getProxy()内部,通过AopProxyFactory 获取一个 AopProxy代理对象,这获取的是 ObjenesisCglibAopProxy
调用 CglibAopProxy.getProxy() 产生一个代理对象
最终容器内的Bean如下
单例Bean开始Bean生命周期,填充属性时,执行 AutowiredAnnotationBeanPostProcessor.postProcessProperties(),填充属性,执行到 DefaultListableBeanFactory.doResolveDependency(),方法内部执行 DefaultListableBeanFactory.findAutowireCandidates()
执行 String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager())
内部是执行 DefaultListableBeanFactory.getBeanNamesForType(),根据类型找到Bean的名字,属于是依赖注入的原理,这里不再讲。最终得到了两个beanName
紧接着来循环判断,候选的beanName是否支持依赖注入。在注册BeanDefinition时,由于原始BeanDefinition的autowireCandidate被设置成了false,所以这个beanName就被忽略了,只剩一下prototypeBean
执行 addCandidateEntry() => Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this); => beanFactory.getBean(beanName);最终还是根据 BeanName去BeanFactory中获取一个Bean。
由于prototypeBean对应的Bean已经放到BeanFactory.singletonObjects,所以直接拿到了一个Bean对象。但是这个Bean对象是一个FactoryBean,所以在执行 getObjectForBeanInstance()时,方法内部会去调用 => FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(),最终执行 ScopedProxyFactoryBean.getObject()
由于ScopedProxyFactoryBean.proxy在执行 ScopedProxyFactoryBean.setBeanFactory()时,已经设置值了,所以这里直接返回,返回的就是原型Bean的代理对象。
继续单例Bean的属性注入流程,根据属性bean的类型,已经找到了一个对象,通过反射,将找到的原型bean代理对象赋值给单例bean的属性上,接着,单例bean走完生命周期。由于单例Bean只会走一次Bean的生命周期,故单例bean中的属性是不会再改变的。至此,Spring容器初始化完成。
执行singletonBean.print()
public void print() {
// 打印原型Bean的hashCode
System.out.println("bean hashcode =>" + bean.hashCode());
}
// 如果不重写 PrototypeBean.hashCode 方法,断点调试的时候,不好验证,这里重写 hashCode方法
@Override
public int hashCode() {
int anInt = new Random().nextInt(10);
System.out.println("hashCode=>" + anInt);
return anInt;
}
执行到 HashCodeInterceptor.intercept()方法
执行多次 print()方法,并没有 执行到原型Bean的 hashCode方法,并且返回的多个hashCode都是一样的,将断点打到 HashCodeInterceptor.intercept方法,执行多次,返回的hashCode是一样的,根源是 this.advised是一样的,而 advised是创建代理对象的时候设置的,而代理对象只会创建一次。所以每次打印的hashCode都是一样的。
public void print() {
System.out.println("bean hashcode =>" + bean.toString());
}
// 重写 PrototypeBean.toString 方法
@Override
public String toString() {
return "toStr=>+" + new Random().nextInt(10);
}
执行到 DynamicAdvisedInterceptor.intercept()方法
执行 target = targetSource.getTarget();每次会从BeanFactory中拿到一个Bean对象
多次执行print(),执行父类的toString方法,多次返回的值不一样,证明了单例Bean依赖原型Bean,多次调用单例Bean的print()方法,都会去创建一个新的原型Bean对象。如果没有重写toString方法,则调用Object类的toString方法
附录
原型Bean生成的Cglib类
- hashCode
- toString