您现在的位置是:亿华云 > 人工智能
Springboot源码分析之Spring循环依赖揭秘
亿华云2025-10-03 02:51:43【人工智能】6人已围观
简介摘要:若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我们
摘要:
若你是源码一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。分析或许刚说到这,环依有的赖揭小伙伴就会大惊失色了。 Spring 不是源码解决了循环依赖问题吗,它是分析怎么又会发生循环依赖的呢?,接下来就让我们一起揭秘 Spring 循环依赖的环依最本质原因。
Spring循环依赖流程图Spring 的循环依赖被它的三级缓存给轻易解决了,但是这2个地方的后置处理带来了 循环依赖的问题。
对比AbstractAdvisorAutoProxyCreator和AsyncAnnotationBeanPostProcessor由于 SmartInstantiationAwareBeanPostProcessor 的子类会在两处都会执行后置处理,所以前后都会相同的对象引用,不会发生循环依赖问题,异步注解就不行了 ,至于为什么?自己看上面的分析,仔细看哦!
如何解决循环依赖? 改变加载顺序 @Lazy 注解 allowRawInjectionDespiteWrapping 设置为 true (利用了判断的那条语句) 别使用相关的 BeanPostProcessor 设计到的注解,,哈哈 这不太现实。
@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 受影响,所以影响范围并不是全局,因此当找不到更好办法的时候,此种这样也不失是一个不错的方案。
很赞哦!(14685)