摘要:
若你是一个有经验的程序员, 那你在开发中必然碰到过这种现象: 事务不生效. 或许刚说到这, 有的小伙伴就会大惊失色了. Spring 不是解决了循环依赖问题吗, 它是怎么又会发生循环依赖的呢?, 接下来就让我们一起揭秘 Spring 循环依赖的最本质原因.
Spring 循环依赖流程图
Spring 循环依赖发生原因
使用了具有代理特性的 BeanPostProcessor
典型的有 事务注解 @Transactional, 异步注解 @Async 等
源码分析揭秘
- protected Object doCreateBean( ... ){
- ...
- boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName));
- if (earlySingletonExposure) {
- addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
- }
- ...
- // populateBean 这一句特别的关键, 它需要给 A 的属性赋值, 所以此处会去实例化 B~~
- // 而 B 我们从上可以看到它就是个普通的 Bean(并不需要创建代理对象), 实例化完成之后, 继续给他的属性 A 赋值, 而此时它会去拿到 A 的早期引用
- // 也就在此处在给 B 的属性 a 赋值的时候, 会执行到上面放进去的 Bean A 流程中的 getEarlyBeanReference() 方法 从而拿到 A 的早期引用~~
- // 执行 A 的 getEarlyBeanReference() 方法的时候, 会执行自动代理创建器, 但是由于 A 没有标注事务, 所以最终不会创建代理, so B 合格属性引用会是 A 的 ** 原始对象 **
- // 需要注意的是:@Async 的代理对象不是在 getEarlyBeanReference() 中创建的, 是在 postProcessAfterInitialization 创建的代理
- // 从这我们也可以看出 @Async 的代理它默认并不支持你去循环引用, 因为它并没有把代理对象的早期引用提供出来~~~(注意这点和自动代理创建器的区别~)
- // 结论: 此处给 A 的依赖属性字段 B 赋值为了 B 的实例 (因为 B 不需要创建代理, 所以就是原始对象)
- // 而此处实例 B 里面依赖的 A 注入的仍旧为 Bean A 的普通实例对象 (注意 是原始对象非代理对象) 注: 此时 exposedObject 也依旧为原始对象
- populateBean(beanName, mbd, instanceWrapper);
- // 标注有 @Async 的 Bean 的代理对象在此处会被生成~~~ 参照类: AsyncAnnotationBeanPostProcessor
- // 所以此句执行完成后 exposedObject 就会是个代理对象而非原始对象了
- exposedObject = initializeBean(beanName, exposedObject, mbd);
- ...
- // 这里是报错的重点~~~
- if (earlySingletonExposure) {
- // 上面说了 A 被 B 循环依赖进去了, 所以此时 A 是被放进了二级缓存的, 所以此处 earlySingletonReference 是 A 的原始对象的引用
- // (这也就解释了为何我说: 如果 A 没有被循环依赖, 是不会报错不会有问题的 因为若没有循环依赖 earlySingletonReference =null 后面就直接 return 了)
- Object earlySingletonReference = getSingleton(beanName, false);
- if (earlySingletonReference != null) {
- // 上面分析了 exposedObject 是被 @Aysnc 代理过的对象, 而 bean 是原始对象 所以此处不相等 走 else 逻辑
- if (exposedObject == bean) {
- exposedObject = earlySingletonReference;
- }
- // allowRawInjectionDespiteWrapping 标注是否允许此 Bean 的原始类型被注入到其它 Bean 里面, 即使自己最终会被包装 (代理)
- // 默认是 false 表示不允许, 如果改为 true 表示允许, 就不会报错啦. 这是我们后面讲的决方案的其中一个方案~~~
- // 另外 dependentBeanMap 记录着每个 Bean 它所依赖的 Bean 的 Map~~~~
- else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
- // 我们的 Bean A 依赖于 B,so 此处值为 ["b"]
- String[] dependentBeans = getDependentBeans(beanName);
- Set actualDependentBeans = new LinkedHashSet<>(dependentBeans.length);
- // 对所有的依赖进行一一检查~ 比如此处 B 就会有问题
- // "b" 它经过 removeSingletonIfCreatedForTypeCheckOnly 最终返返回 false 因为 alreadyCreated 里面已经有它了表示 B 已经完全创建完成了~~~
- // 而 b 都完成了, 所以属性 a 也赋值完成儿聊 但是 B 里面引用的 a 和主流程我这个 A 竟然不相等, 那肯定就有问题 (说明不是最终的)~~~
- // so 最终会被加入到 actualDependentBeans 里面去, 表示 A 真正的依赖~~~
- for (String dependentBean : dependentBeans) {
- if (!removeSingletonIfCreatedForTypeCheckOnly(dependentBean)) {
- actualDependentBeans.add(dependentBean);
- }
- }
- // 若存在这种真正的依赖, 那就报错了~~~ 则个异常就是上面看到的异常信息
- if (!actualDependentBeans.isEmpty()) {
- throw new BeanCurrentlyInCreationException(beanName,
- "Bean with name'" + beanName + "'has been injected into other beans [" +
- StringUtils.collectionToCommaDelimitedString(actualDependentBeans) +
- "] in its raw version as part of a circular reference, but has eventually been" +
- "wrapped. This means that said other beans do not use the final version of the" +
- "bean. This is often the result of over-eager type matching - consider using" +
- "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
- }
- }
- }
- }
- ...
- }
问题简化
发生循环依赖时候
Object earlySingletonReference = getSingleton(beanName, false);
肯定有值
缓存工厂
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
将给实例对象添加
- SmartInstantiationAwareBeanPostProcessor
- AbstractAutoProxyCreator
是
SmartInstantiationAwareBeanPostProcessor
的子类, 一定记住了, 一定记住,
SmartInstantiationAwareBeanPostProcessor
的子类很关键!!!!!
exposedObject = initializeBean(beanName, exposedObject, mbd);
进行 BeanPostProcessor 后置处理, 注意是 BeanPostProcessor !!!!!
Spring 的循环依赖被它的三级缓存给轻易解决了, 但是这 2 个地方的后置处理带来了 循环依赖的问题.
对比 AbstractAdvisorAutoProxyCreator 和 AsyncAnnotationBeanPostProcessor
由于 SmartInstantiationAwareBeanPostProcessor 的子类会在两处都会执行后置处理, 所以前后都会相同的对象引用, 不会发生循环依赖问题, 异步注解就不行了 , 至于为什么? 自己看上面的分析, 仔细看哦!
如何解决循环依赖?
改变加载顺序
@Lazy 注解
allowRawInjectionDespiteWrapping
设置为 true (利用了判断的那条语句)
别使用相关的 BeanPostProcessor 设计到的注解,, 哈哈 这不太现实.
@Lazy
@Lazy 一般含义是懒加载, 它只会作用于 BeanDefinition.setLazyInit() . 而此处给它增加了一个能力: 延迟处理 (代理处理)
- // @since 4.0 出现得挺晚, 它支持到了 @Lazy 是功能最全的 AutowireCandidateResolver
- public class ContextAnnotationAutowireCandidateResolver extends QualifierAnnotationAutowireCandidateResolver {
- // 这是此类本身唯一做的事, 此处精析
- // 返回该 lazy proxy 表示延迟初始化, 实现过程是查看在 @Autowired 注解处是否使用了 @Lazy = true 注解
- @Override
- @Nullable
- public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, @Nullable String beanName) {
- // 如果 isLazy=true 那就返回一个代理, 否则返回 null
- // 相当于若标注了 @Lazy 注解, 就会返回一个代理 (当然 @Lazy 注解的 value 值不能是 false)
- return (isLazy(descriptor) ? buildLazyResolutionProxy(descriptor, beanName) : null);
- }
- // 这个比较简单,@Lazy 注解标注了就行 (value 属性默认值是 true)
- // @Lazy 支持标注在属性上和方法入参上~~~ 这里都会解析
- protected boolean isLazy(DependencyDescriptor descriptor) {
- for (Annotation ann : descriptor.getAnnotations()) {
- Lazy lazy = AnnotationUtils.getAnnotation(ann, Lazy.class);
- if (lazy != null && lazy.value()) {
- return true;
- }
- }
- MethodParameter methodParam = descriptor.getMethodParameter();
- if (methodParam != null) {
- Method method = methodParam.getMethod();
- if (method == null || void.class == method.getReturnType()) {
- Lazy lazy = AnnotationUtils.getAnnotation(methodParam.getAnnotatedElement(), Lazy.class);
- if (lazy != null && lazy.value()) {
- return true;
- }
- }
- }
- return false;
- }
- // 核心内容, 是本类的灵魂~~~
- protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final @Nullable String beanName) {
- Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory,
- "BeanFactory needs to be a DefaultListableBeanFactory");
- // 这里毫不客气的使用了面向实现类编程, 使用了 DefaultListableBeanFactory.doResolveDependency() 方法~~~
- final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
- //TargetSource 是它实现懒加载的核心原因, 在 AOP 那一章节了重点提到过这个接口, 此处不再叙述
- // 它有很多的著名实现如 HotSwappableTargetSource,SingletonTargetSource,LazyInitTargetSource,
- //SimpleBeanTargetSource,ThreadLocalTargetSource,PrototypeTargetSource 等等非常多
- // 此处因为只需要自己用, 所以采用匿名内部类的方式实现~~~ 此处最重要是看 getTarget 方法, 它在被使用的时候 (也就是代理对象真正使用的时候执行~~~)
- TargetSource ts = new TargetSource() {
- @Override
- public Class getTargetClass() {
- return descriptor.getDependencyType();
- }
- @Override
- public boolean isStatic() {
- return false;
- }
- // getTarget 是调用代理方法的时候会调用的, 所以执行每个代理方法都会执行此方法, 这也是为何 doResolveDependency
- // 我个人认为它在效率上, 是存在一定的问题的~~~ 所以此处建议尽量少用 @Lazy~~~
- // 不过效率上应该还好, 对比 http, 序列化反序列化处理, 简直不值一提 所以还是无所谓 用吧
- @Override
- public Object getTarget() {
- Object target = beanFactory.doResolveDependency(descriptor, beanName, null, null);
- if (target == null) {
- Class type = getTargetClass();
- // 对多值注入的空值的友好处理 (不要用 null)
- if (Map.class == type) {
- return Collections.emptyMap();
- } else if (List.class == type) {
- return Collections.emptyList();
- } else if (Set.class == type || Collection.class == type) {
- return Collections.emptySet();
- }
- throw new NoSuchBeanDefinitionException(descriptor.getResolvableType(),
- "Optional dependency not present for lazy injection point");
- }
- return target;
- }
- @Override
- public void releaseTarget(Object target) {
- }
- };
- // 使用 ProxyFactory 给 ts 生成一个代理
- // 由此可见最终生成的代理对象的目标对象其实是 TargetSource, 而 TargetSource 的目标才是我们业务的对象
- ProxyFactory pf = new ProxyFactory();
- pf.setTargetSource(ts);
- Class dependencyType = descriptor.getDependencyType();
- // 如果注入的语句是这么写的 private AInterface a; 那这类就是借口 值是 true
- // 把这个接口类型也得放进去 (不然这个代理都不属于这个类型, 反射 set 的时候岂不直接报错了吗????)
- if (dependencyType.isInterface()) {
- pf.addInterface(dependencyType);
- }
- return pf.getProxy(beanFactory.getBeanClassLoader());
- }
- }
标注有 @Lazy 注解完成注入的时候, 最终注入只是一个此处临时生成的代理对象, 只有在真正执行目标方法的时候才会去容器内拿到真是的 bean 实例来执行目标方法.
利用 allowRawInjectionDespiteWrapping 属性来强制改变判断
- @Component
- public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
- @Override
- public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
- ((AbstractAutowireCapableBeanFactory) beanFactory).setAllowRawInjectionDespiteWrapping(true);
- }
- }
这样会导致容器里面的是代理对象, 暴露给其他实例的是原始引用, 导致不生效了. 由于它只对循环依赖内的 Bean 受影响, 所以影响范围并不是全局, 因此当找不到更好办法的时候, 此种这样也不失是一个不错的方案.
来源: http://news.51cto.com/art/201909/602637.htm