建手机网站的软件有哪些,编程基础知识入门,阿里云可以放几个网站,设计类专业介绍什么是循环依赖
类与类之间的依赖关系形成了闭环#xff0c;就会导致循环依赖问题的产生。举例来说#xff0c;假设存在两个服务类A和服务类B#xff0c;如果A通过依赖注入的方式引用了B#xff0c;且B通过依赖注入的方式引用了A#xff0c;那么A和B之间就存在循环依赖。…什么是循环依赖
类与类之间的依赖关系形成了闭环就会导致循环依赖问题的产生。举例来说假设存在两个服务类A和服务类B如果A通过依赖注入的方式引用了B且B通过依赖注入的方式引用了A那么A和B之间就存在循环依赖。 推广来说如果涉及多个类也存在这种依赖关系那么也是循环依赖问题。 循环依赖问题比较严重有时会影响服务启动有时会导致死循环调用(如果线上环境出现循环调用会导致程序进入死循环然后服务崩溃进而导致用户请求无法响应造成生产事故)应引起足够的重视。
循环依赖的处理现状
Spring 支持解决循环依赖但是仅能支持特定的场景对于不支持的场景会在启动时报错。Spring 提供的能力后面会详细重点说明下在Spring Boot 2.6.0版本开始默认禁用对循环依赖的支持。也就是说Spring Boot 2.6.0版本及之后版本如果存在循环依赖不管是何种场景都会报启动错误。启动报错关键信息如下
Description:The dependencies of some of the beans in the application context form a cycle:┌─────┐
| a (field private com.github.courage007.springbootstarter.service.B com.github.courage007.springbootstarter.service.A.b)
↑ ↓
| b (field private com.github.courage007.springbootstarter.service.A com.github.courage007.springbootstarter.service.B.a)
└─────┘Action:Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.总结来说Spring 支持一定程度的循环依赖的解决但是在2.6.0版本及之后的版本默认禁用该功能。也就是说Spring Boot 2.6.0版本及之后版本如果存在循环依赖不管是何种场景都会报启动错误。 那么Spring 都支持哪些循环依赖的场景呢后续如果出现循环依赖问题又该如何解决呢接下来详细介绍这些内容。
Spring IoC简介
在正式介绍 Spring 如何解决 Bean 的循环依赖问题前先简要介绍下Spring 如何实现Bean的注入。Spring使用IoC技术实现Bean的注入在具体策略上采用了依赖注入的方式。在Spring中支持的依赖注入实现主要有三种构造器注入、setter方法注入(也称属性注入)、接口注入等。相对于前两种注入方式接口注入比繁琐和死板被注入对象必须声明和实现另外的接口所以常用的Bean注入方式主要有两种构造器注入和setter方法注入。
Spring 如何解决循环依赖
根据依赖注入的方式不同可以将循环依赖的场景进行以下划分
依赖注入A构造器注入A属性注入B构造器注入启动报错Spring无法自动解决需要手动解决启动成功Spring可以自动解决B属性注入启动成功Spring可以自动解决启动成功Spring可以自动解决
A构造器注入BB构造器注入A
针对A构造器注入BB构造器注入A的情况Spring 无法解决这种循环依赖问题启动时就会报错报错内容如下
***************************
APPLICATION FAILED TO START
***************************Description:The dependencies of some of the beans in the application context form a cycle:┌─────┐
| a defined in file [/spring-boot-example/spring-boot-web-starter/target/classes/com/github/courage007/springbootwebstarter/resource/A.class]
↑ ↓
| b defined in file [/spring-boot-example/spring-boot-web-starter/target/classes/com/github/courage007/springbootwebstarter/resource/B.class]因为这种循环依赖在启动时就会被发现所以不会带入到线上环境影响可控但错误比较低级。
A构造器注入BB属性注入A
针对A构造器注入BB构造器注入A的情况Spring 可以解决这种循环依赖问题不会影响Spring应用的启动。示例代码如下
Service
public class A {private B b;public A(B a) {this.b b;}public void test() {System.out.println(A);}
}Service
public class B {Autowiredprivate A a;public void test() {System.out.println(B);}
}虽然Spring可以解决这种循环依赖问题但是却无法解决方法之间的循环调用如果同时存在相互调用则会导致实例不断的被初始化从而导致栈溢出。示例代码如下
Service
public class A {private B b;public A(B a) {this.b b;}public void test() {System.out.println(A);b.test();}
}Service
public class B {Autowiredprivate A a;public void test() {System.out.println(B);a.test();}
}如果客户端调用了实例A的test方法或实例B的test方法那么将会触发循环调用进而导致服务崩溃。异常堆栈示例如下
*** java.lang.instrument ASSERTION FAILED ***: !errorOutstanding with message transform method call failed at ./open/src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 872
2023-11-10 16:46:03.497 ERROR 13643 --- [nio-8082-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root causejava.lang.StackOverflowError: nullat java.base/java.nio.ByteBuffer.position(ByteBuffer.java:1158) ~[na:na]at java.base/java.nio.ByteBuffer.position(ByteBuffer.java:263) ~[na:na]at java.base/sun.nio.cs.UTF_8.updatePositions(UTF_8.java:80) ~[na:na]at java.base/sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:509) ~[na:na]at java.base/sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:564) ~[na:na]at java.base/java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:576) ~[na:na]at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:292) ~[na:na]at java.base/sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:281) ~[na:na]at java.base/sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125) ~[na:na]at java.base/java.io.OutputStreamWriter.write(OutputStreamWriter.java:208) ~[na:na]at java.base/java.io.BufferedWriter.flushBuffer(BufferedWriter.java:120) ~[na:na]at java.base/java.io.PrintStream.write(PrintStream.java:605) ~[na:na]at java.base/java.io.PrintStream.print(PrintStream.java:745) ~[na:na]at java.base/java.io.PrintStream.println(PrintStream.java:882) ~[na:na]at com.github.courage007.springbootwebstarter.resource.A.test(A.java:18) ~[classes/:na]at com.github.courage007.springbootwebstarter.resource.B.test(B.java:21) ~[classes/:na]at com.github.courage007.springbootwebstarter.resource.A.test(A.java:19) ~[classes/:na]at com.github.courage007.springbootwebstarter.resource.B.test(B.java:21) ~[classes/:na]针对这种情况因为是在运行时才会发现如果测试用例覆盖该场景则不会导致问题流出到现网环境否则会导致服务崩溃属于生产事故。
A属性注入BB构造器注入A
针对A属性注入BB构造器注入A的情况其分析与A构造器注入BB构造器注入A一致保持相同结论即可。
A属性注入BB属性注入A
针对A属性注入BB属性注入A的情况其分析与A构造器注入BB属性注入A相似保持相同结论即可。这里给出示例代码
Service
public class A {Autowiredprivate B b;public void test() {System.out.println(A);}
}Service
public class B {Autowiredprivate A a;public void test() {System.out.println(B);}
}源码分析循环依赖
接下来从Spring Bean的创建过程看看 Spring 是如何解决一定程度的循环依赖问题。首先定位到AbstractBeanFactory抽象类的createBean方法。这个方法是Bean创建的入口。 进入其实现会进入到AbstractAutowireCapableBeanFactory抽象类(这里仅展示关键源码完整代码可以执行查看源码)。
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactoryimplements AutowireCapableBeanFactory {// ...Overrideprotected Object createBean(String beanName, RootBeanDefinition mbd, Nullable Object[] args)throws BeanCreationException {// ...// 优先尝试从代理中获取Bean实例Object bean resolveBeforeInstantiation(beanName, mbdToUse);if (bean ! null) {return bean;}try {Object beanInstance doCreateBean(beanName, mbdToUse, args);if (logger.isTraceEnabled()) {logger.trace(Finished creating instance of bean beanName );}return beanInstance;} catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {// A previously detected exception with proper bean creation context already,// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.throw ex;} catch (Throwable ex) {throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, Unexpected exception during bean creation, ex);}}protected Object doCreateBean(String beanName, RootBeanDefinition mbd, Nullable Object[] args)throws BeanCreationException {// 从这几行代码可知对于多例模式是会直接创建Bean实例所以多例模式下循环依赖问题无法解决BeanWrapper instanceWrapper null;if (mbd.isSingleton()) {instanceWrapper this.factoryBeanInstanceCache.remove(beanName);}if (instanceWrapper null) {// 创建bean实例主要解决构造器注入的场景instanceWrapper createBeanInstance(beanName, mbd, args);}// 缓存的单例集合可以用来解决一定程度的循环依赖问题// 启用缓存的场景(1) 单例场景(2) 启用允许循环依赖的开关boolean earlySingletonExposure (mbd.isSingleton() this.allowCircularReferences isSingletonCurrentlyInCreation(beanName));if (earlySingletonExposure) {if (logger.isTraceEnabled()) {logger.trace(Eagerly caching bean beanName to allow for resolving potential circular references);}addSingletonFactory(beanName, () - getEarlyBeanReference(beanName, mbd, bean));}// Initialize the bean instance.Object exposedObject bean;try {// 创建bean实例主要解决属性注入的场景populateBean(beanName, mbd, instanceWrapper);// bean的实例化exposedObject initializeBean(beanName, exposedObject, mbd);}catch (Throwable ex) {if (ex instanceof BeanCreationException beanName.equals(((BeanCreationException) ex).getBeanName())) {throw (BeanCreationException) ex;}else {throw new BeanCreationException(mbd.getResourceDescription(), beanName, Initialization of bean failed, ex);}}if (earlySingletonExposure) {Object earlySingletonReference getSingleton(beanName, false);if (earlySingletonReference ! null) {if (exposedObject bean) {exposedObject earlySingletonReference;}else if (!this.allowRawInjectionDespiteWrapping hasDependentBean(beanName)) {String[] dependentBeans getDependentBeans(beanName);SetString actualDependentBeans new LinkedHashSet(dependentBeans.length);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 getBeanNamesForType with the allowEagerInit flag turned off, for example.);}}}}// ...return exposedObject;}protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {Object exposedObject bean;if (!mbd.isSynthetic() hasInstantiationAwareBeanPostProcessors()) {for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {exposedObject bp.getEarlyBeanReference(exposedObject, beanName);}}return exposedObject;}
}public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {// .../** Cache of singleton objects: bean name to bean instance. */private final MapString, Object singletonObjects new ConcurrentHashMap(256);/** Cache of singleton factories: bean name to ObjectFactory. */private final MapString, ObjectFactory? singletonFactories new HashMap(16);/** Cache of early singleton objects: bean name to bean instance. */private final MapString, Object earlySingletonObjects new ConcurrentHashMap(16);/** Set of registered singletons, containing the bean names in registration order. */private final SetString registeredSingletons new LinkedHashSet(256);protected void addSingletonFactory(String beanName, ObjectFactory? singletonFactory) {Assert.notNull(singletonFactory, Singleton factory must not be null);synchronized (this.singletonObjects) {if (!this.singletonObjects.containsKey(beanName)) {this.singletonFactories.put(beanName, singletonFactory);this.earlySingletonObjects.remove(beanName);this.registeredSingletons.add(beanName);}}}protected Object getSingleton(String beanName, boolean allowEarlyReference) {// Quick check for existing instance without full singleton lock// 先从一级本地缓存获取对象Object singletonObject this.singletonObjects.get(beanName);if (singletonObject null isSingletonCurrentlyInCreation(beanName)) {// 如果一级缓存没有则考虑从二级缓存获取对象singletonObject this.earlySingletonObjects.get(beanName);// 根据是否允许启用allowEarlyReference再次获取对象if (singletonObject null allowEarlyReference) {synchronized (this.singletonObjects) {// Consistent creation of early reference within full singleton locksingletonObject this.singletonObjects.get(beanName);if (singletonObject null) {singletonObject this.earlySingletonObjects.get(beanName);if (singletonObject null) {// 如果二级缓存没有则考虑从三级缓存获取对象并将其放置到二级缓存(实际从二级缓存获取对象)ObjectFactory? singletonFactory this.singletonFactories.get(beanName);if (singletonFactory ! null) {singletonObject singletonFactory.getObject();this.earlySingletonObjects.put(beanName, singletonObject);this.singletonFactories.remove(beanName);}}}}}}return singletonObject;}// ...
}经过上述源码分析可知对于Bean多例(Prototype)场景Spring 是不支持解决循环依赖的只支持Bean单例(Singleton)的场景。而且Spring还提供了允许循环依赖的开关(默认是关闭的)。在创建Bean的时候Spring 使用了三级缓存。Spring优先从一级缓存singletonObjects中获取对象如果没有则尝试从二级缓存earlySingletonObjects中获取如果还是没有就从三级缓存singletonFactory获取对象然后将对象放置到二级缓存并从三级缓存溢出该对象。这里只是Spring Bean对象创建的过程那么Spring 是如何解决Bean的循环依赖的问题呢 其实这里的关键是Spring引入了earlyBeanReference(早期Bean的引用)也就是说在创建Bean的时候会注入一个还没初始化的对象从而解决了循环依赖重复创建的问题。这里以A属性注入BB属性注入A为例介绍Spring是如何支持循环依赖的。首先在创建A的实例时会先创建A的对象引用然后再去查找A依赖的对象如果不存在则需要进入依赖对象的创建过程同样地会先创建对象的引用这里是对象B的引用。这样在A中就可以使用B而B也是类似的操作。 如果仅使用一级缓存和二级缓存就可以解决循环依赖问题为什么没有这么做呢这是因为并不是需要对所有对象都创建二级缓存这样可以减少不必要的对象创建从而节省内存。而引入三级缓存后如果存在允许循环引用的场景则会基于对象引用工厂创建对象的引用然后将其存储到二级缓存。可以说三级缓存的目的就是为了构造二级缓存中的元素从而达到节省内存的效果。
循环依赖的处理
了解了Spring 支持的循环依赖的解决能力接下来针对存在循环依赖的场景给出合适的处理方案。 如果需要Spring Boot版本从低于2.6.0的版本升级到高于2.6.0的版本如果现有代码存在循环依赖且无法再短时间内消减掉该问题则可以先在配置文件中补充下述配置做临时规避处理
spring:main:allow-circular-references:true #允许循环依赖当然不建议在新写的代码中引入循环依赖问题。接下来根据是否存在方法循环调用对问题进一步的划分不存在循环调用的循环依赖存在循环调用的循环依赖。 注意这里不再关注循环调用的方式统一使用A属性注入BB属性注入A这种方式进行说明。
不存在循环调用的循环依赖
如果存在循环依赖但是不存在循环调用如果是在2.6.0以下版本不会有问题只是有引入循环调用的风险如果是在2.6.0以上的版本如果不主动开启允许循环依赖的特性开关是会在启动的时候报循环依赖的错误的。但是如果开启允许循环依赖的特性开关则不会影响启动只是有引入循环调用的风险。 为消减掉循环调用的风险应尝试解决掉这种循环依赖这里给出示例代码
// 修改前
Service
public class A {Autowiredprivate B b;public void test() {System.out.println(A);b.test();}
}Service
public class B {Autowiredprivate A a;public void test() {System.out.println(B);}
}
// 修改后// 修改前
Service
public class A {Autowiredprivate C c;public void test() {System.out.println(A);c.test();}
}Service
public class B {Autowiredprivate A a;
}Service
public class C {public void test() {System.out.println(B);}
}可见这里解决掉循环依赖的方式就是将被依赖的类中的方法独立包装成一个类然后被引用。当然实际的情况可能更复杂但是核心的处理策略都是一样的。
存在循环调用的循环依赖
如果存在循环依赖且存在循环调用不管是哪一个Spring 版本都会因循环调用导致对象重复创建进而导致堆栈溢出引发程序崩溃。从直接现象来看这是开发人员因逻辑思考有问题写入了死循环的代码导致。且该代码并没有做自测如果没有测试用例看护那么这种问题流出到现网环境将会带来生产事故。 为解决这类Bug就是要思考现有实现看看代码逻辑正常来说可以先将循环调用消减掉然后再想办法(参考上一节)消减掉循环调用。这里给出存在循环调用的循环依赖的示例代码(之前已有介绍这里重复一下方便阅读)
Service
public class A {private B b;public A(B a) {this.b b;}public void test() {System.out.println(A);b.test();}
}Service
public class B {Autowiredprivate A a;public void test() {System.out.println(B);a.test();}
}总结
针对Spring Bean的循环依赖问题如果情况允许尽量不使用临时规避的手段容忍循环依赖问题。如果因存量代码的问题尽量将消除循环依赖作为高优先级历史债务进行解决。循环依赖的引入要么会影响应用的启动要么会因方法循环调用陷入死循环从而导致堆栈溢出给用户带来影响。
参考
https://blog.csdn.net/wangxuelei036/article/details/104960558 spring 循环依赖以及解决方案 https://www.cnblogs.com/daimzh/p/13256413.html 讲一讲Spring中的循环依赖 https://blog.csdn.net/qq_51076413/article/details/131061348 SpringBoot3.x循环依赖 https://juejin.cn/post/7032910060044943373 Spring Boot 2.6.0正式发布默认禁止循环依赖、增强Docker镜像构建 https://blog.csdn.net/Microhoo_/article/details/132188824 解决Spring Boot 2.6及之后版本取消了循环依赖的支持的问题 https://zhuanlan.zhihu.com/p/647315070 spring bean 三级缓存