在一个单例Bean中依赖原型Bean

358 阅读5分钟

单例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中,注意,该属性非常重要,后面会用到。 image-20220526111659357.png 执行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); image-20220526113154875.png

该方法会去调用子类CglibSubclassingInstantiationStrategy.instantiateWithMethodInjection(),最终调用子类CglibSubclassingInstantiationStrategy.instantiate()方法 image-20220526113321494.png

方法内部调用 createEnhancedSubclass()

image-20220526113404316.png

在这里创建了一个Cglib代理类。最终用该代理类创建了一个代理对象。

image-20220526103228896.png

image-20220526103334327.png

故单例Bean在实例化时,创建了一个代理对象,并将该代理对象放到了Spring容器中。

image-20220526113703360.png

执行单例对象的print()->methodInject(),会执行CglibSubclassingInstantiationStrategy.intercept()

image-20220526113924193.png

内部调用 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

image-20220526114542473.png

由于原型Bean设置的proxyMode = ScopedProxyMode.TARGET_CLASS,在执行 AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);会构造一个新的BeanDefinition image-20220526114741347.png

最终调用 ScopedProxyUtils.createScopedProxy()

image-20220526114845576.png

方法内部会创建一个新的 BeanDefinitionHolder,对应的beanName就是原始BeanDefinition的名字。而原始的BeanDefinition也会放到DefaultListableBeanFactory.beanDefinitionMap属性中,只不过beanName增加了前缀`scopedTarget.来区分。需要注意的一个属性,原始BeanDefinition.autowireCandidate被设置成false,在单例bean依赖注入原型Bean时有用。最终的 beanDefinitionMap属性

1653557729(1).jpg

222222.png

ScopedProxyFactoryBean,是一个FactoryBean,同时也实现了 BeanFactoryAware接口

image-20220526120044318.png

Spring容器在实例化对象时,是根据beanDefinitionName来循环实例化对象的,由于PrototypeBean作用范围是prototype,故不会实例化一个对象。

image-20220526134248929.png

prototypeBean->ScopedProxyFactoryBean,在Bean的生命周期过程中,由于是一个BeanFactoryAware,所以在实例化后,初始化前,会执行invokeAwareMethods,方法内部会调用 setBeanFactory()ScopedProxyFactoryBean.setBeanFactory()方法会被调用

image-20220526135021057.png

方法最后调用 pf.getProxy(cbf.getBeanClassLoader()) 产生一个代理对象,并赋值给 proxy属性,该属性很重要,依赖注入原型Bean的时候会用到,下面讲。

pf.getProxy()内部,通过AopProxyFactory 获取一个 AopProxy代理对象,这获取的是 ObjenesisCglibAopProxy

image-20220526135537428.png

调用 CglibAopProxy.getProxy() 产生一个代理对象

image-20220526140320454.png

image-20220526140807838.png

最终容器内的Bean如下

image-20220526141209450.png

单例Bean开始Bean生命周期,填充属性时,执行 AutowiredAnnotationBeanPostProcessor.postProcessProperties(),填充属性,执行到 DefaultListableBeanFactory.doResolveDependency(),方法内部执行 DefaultListableBeanFactory.findAutowireCandidates()

image-20220526141842105.png

执行 String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(this, requiredType, true, descriptor.isEager())

内部是执行 DefaultListableBeanFactory.getBeanNamesForType(),根据类型找到Bean的名字,属于是依赖注入的原理,这里不再讲。最终得到了两个beanName

image-20220526142338161.png

紧接着来循环判断,候选的beanName是否支持依赖注入。在注册BeanDefinition时,由于原始BeanDefinition的autowireCandidate被设置成了false,所以这个beanName就被忽略了,只剩一下prototypeBean

image-20220526142917145.png

执行 addCandidateEntry() => Object beanInstance = descriptor.resolveCandidate(candidateName, requiredType, this); => beanFactory.getBean(beanName);最终还是根据 BeanNameBeanFactory中获取一个Bean。

image-20220526143945019.png

由于prototypeBean对应的Bean已经放到BeanFactory.singletonObjects,所以直接拿到了一个Bean对象。但是这个Bean对象是一个FactoryBean,所以在执行 getObjectForBeanInstance()时,方法内部会去调用 => FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(),最终执行 ScopedProxyFactoryBean.getObject()

image-20220526144504572.png

由于ScopedProxyFactoryBean.proxy在执行 ScopedProxyFactoryBean.setBeanFactory()时,已经设置值了,所以这里直接返回,返回的就是原型Bean的代理对象。

image-20220526144713870.png

继续单例Bean的属性注入流程,根据属性bean的类型,已经找到了一个对象,通过反射,将找到的原型bean代理对象赋值给单例bean的属性上,接着,单例bean走完生命周期。由于单例Bean只会走一次Bean的生命周期,故单例bean中的属性是不会再改变的。至此,Spring容器初始化完成。

image-20220526145407561.png

执行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()方法

image-20220526150113745.png

执行多次 print()方法,并没有 执行到原型Bean的 hashCode方法,并且返回的多个hashCode都是一样的,将断点打到 HashCodeInterceptor.intercept方法,执行多次,返回的hashCode是一样的,根源是 this.advised是一样的,而 advised是创建代理对象的时候设置的,而代理对象只会创建一次。所以每次打印的hashCode都是一样的。

image-20220526153340526.png

public void print() {
    System.out.println("bean hashcode =>" + bean.toString());
}
// 重写 PrototypeBean.toString 方法
@Override
public String toString() {
    return "toStr=>+" + new Random().nextInt(10);
}

执行到 DynamicAdvisedInterceptor.intercept()方法

image-20220526150746439.png

执行 target = targetSource.getTarget();每次会从BeanFactory中拿到一个Bean对象

image-20220526155516961.png

image-20220526155535188.png

多次执行print(),执行父类的toString方法,多次返回的值不一样,证明了单例Bean依赖原型Bean,多次调用单例Bean的print()方法,都会去创建一个新的原型Bean对象。如果没有重写toString方法,则调用Object类的toString方法

附录

原型Bean生成的Cglib类

  • hashCode

image-20220526160024739.png

  • toString

image-20220526160051275.png