java面试题:你知道什么是循环依赖么?Spring是如何解决循环依赖的?
首先向大家介绍下什么是循环依赖。
(学习视频分享:java视频教程)
所谓循环依赖就是A依赖B,同时B又依赖A,两者之间的依赖关系形成了一个圆环,一般是由于不正确的编码所导致。Spring只能解决属性循环依赖问题,不能解决构造函数循环依赖问题,因为这个问题无解。
接下来我们首先写一个Demo来演示Spring是如何处理属性循环依赖问题的。
Talk is cheap. Show me the code
第一步:定义一个类ComponentA,其有一个私有属性componentB。
package com.tech.ioc; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @author 君战 * **/ @Component public class ComponentA { @Autowired private ComponentB componentB; public void say(){ componentB.say(); } }
第二步:定义一个类ComponentB,其依赖ComponentA。并定义一个say方法便于打印数据。
package com.tech.ioc; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * @author 君战 * **/ @Component public class ComponentB { @Autowired private ComponentA componentA; public void say(){ System.out.println("componentA field " + componentA); System.out.println(this.getClass().getName() + " -----> say()"); } }
第三步:重点,编写一个类-SimpleContainer,模仿Spring底层处理循环依赖。如果理解这个代码,再去看Spring处理循环依赖的逻辑就会很简单。
package com.tech.ioc; import java.beans.Introspector; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 演示Spring中循环依赖是如何处理的,只是个简版,真实的Spring依赖处理远比这个复杂。 * 但大体思路都相同。另外这个Demo很多情况都未考虑,例如线程安全问题,仅供参考。 * @author 君战 * * **/ public class SimpleContainer { /*** * 用于存放完全初始化好的Bean,Bean处于就绪状态 * 这个Map定义和Spring中一级缓存命名一致 * */ private Map<String, Object> singletonObjects = new ConcurrentHashMap<>(); /*** * 用于存放刚创建出来的Bean,其属性还没有处理,因此存放在该缓存中的Bean还不可用。 * 这个Map定义和Spring中三级缓存命名一致 * */ private final Map<String, Object> singletonFactories = new HashMap<>(16); public static void main(String[] args) { SimpleContainer container = new SimpleContainer(); ComponentA componentA = container.getBean(ComponentA.class); componentA.say(); } public <T> T getBean(Class<T> beanClass) { String beanName = this.getBeanName(beanClass); // 首先根据beanName从缓存中获取Bean实例 Object bean = this.getSingleton(beanName); if (bean == null) { // 如果未获取到Bean实例,则创建Bean实例 return createBean(beanClass, beanName); } return (T) bean; } /*** * 从一级缓存和二级缓存中根据beanName来获取Bean实例,可能为空 * */ private Object getSingleton(String beanName) { // 首先尝试从一级缓存中获取 Object instance = singletonObjects.get(beanName); if (instance == null) { // Spring 之所以能解决循环依赖问题,也是靠着这个三级缓存--singletonFactories instance = singletonFactories.get(beanName); } return instance; } /*** * 创建指定Class的实例,返回完全状态的Bean(属性可用) * * */ private <T> T createBean(Class<T> beanClass, String beanName) { try { Constructor<T> constructor = beanClass.getDeclaredConstructor(); T instance = constructor.newInstance(); // 先将刚创建好的实例存放到三级缓存中,如果没有这一步,Spring 也无法解决三级缓存 singletonFactories.put(beanName, instance); Field[] fields = beanClass.getDeclaredFields(); for (Field field : fields) { Class<?> fieldType = field.getType(); field.setAccessible(true); // 精髓是这里又调用了getBean方法,例如正在处理ComponentA.componentB属性, // 执行到这里时就会去实例化ComponentB。因为在getBean方法首先去查缓存, // 而一级缓存和三级缓存中没有ComponentB实例数据,所以又会调用到当前方法, // 而在处理ComponentB.componentA属性时,又去调用getBean方法去缓存中查找, // 因为在前面我们将ComponentA实例放入到了三级缓存,因此可以找到。 // 所以ComponentB的实例化结束,方法出栈,返回到实例化ComponentA的方法栈中, // 这时ComponentB已经初始化完成,因此ComponentA.componentB属性赋值成功! field.set(instance, this.getBean(fieldType)); } // 最后再将初始化好的Bean设置到一级缓存中。 singletonObjects.put(beanName, instance); return instance; } catch (Exception e) { e.printStackTrace(); } throw new IllegalArgumentException(); } /** * 将类名小写作为beanName,Spring底层实现和这个差不多,也是使用javaBeans的 * {@linkplain Introspector#decapitalize(String)} **/ private String getBeanName(Class<?> clazz) { String clazzName = clazz.getName(); int index = clazzName.lastIndexOf("."); String className = clazzName.substring(index); return Introspector.decapitalize(className); } }
如果各位同学已经阅读并理解上面的代码,那么接下来我们就进行真实的Spring处理循环依赖问题源码分析,相信再阅读起来就会很容易。
底层源码分析
分析从AbstractBeanFactory的doGetBean方法着手。可以看到在该方法首先调用transformedBeanName(其实就是处理BeanName问题),和我们自己写的getBeanName方法作用是一样的,但Spring考虑的远比这个复杂,因为有FactoryBean、别名问题。
// AbstractBeanFactory#doGetBean protected <T> T doGetBean( String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException { String beanName = transformedBeanName(name); Object bean; // !!!重点是这里,首先从缓存中beanName来获取对应的Bean。 Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) { // 执行到这里说明缓存中存在指定beanName的Bean实例,getObjectForBeanInstance是用来处理获取到的Bean是FactoryBean问题 bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); else { try { // 删除与本次分析无关代码.... // 如果是单例Bean,则通过调用createBean方法进行创建 if (mbd.isSingleton()) { sharedInstance = getSingleton(beanName, () -> { try { return createBean(beanName, mbd, args); } catch (BeansException ex) { destroySingleton(beanName); throw ex; } }); } catch (BeansException ex) { cleanupAfterBeanCreationFailure(beanName); throw ex; } } return (T) bean; }
getSingleton方法存在重载方法,这里调用的是重载的getSingleton方法,注意这里传递的boolean参数值为true,因为该值决定了是否允许曝光早期Bean。
// DefaultSingletonBeanRegistry#getSingleton public Object getSingleton(String beanName) { return getSingleton(beanName, true); }
// DefaultSingletonBeanRegistry#getSingleton protected Object getSingleton(String beanName, boolean allowEarlyReference) { // 首先从一级缓存中获取 Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) { // 如果一级缓存中未获取到,再从二级缓存中获取 singletonObject = this.earlySingletonObjects.get(beanName); // 如果未从二级缓存中获取到并且allowEarlyReference值为true(前面传的为true) if (singletonObject == null && allowEarlyReference) { synchronized (this.singletonObjects) { //Double Check singletonObject = 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; }
(更多面试题请访问:java面试题及答案)
ok,看完Spring是如何从缓存中获取Bean实例后,那再看看creatBean方法是如何创建Bean的
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { // 删除与本次分析无关的代码... try {// createBean方法底层是通过调用doCreateBean来完成Bean创建的。 Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); } }
// AbstractAutowireCapableBeanFactory#doCreateBean protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { BeanWrapper instanceWrapper = null; if (mbd.isSingleton()) { instanceWrapper = this.factoryBeanInstanceCache.remove(beanName); } if (instanceWrapper == null) { // 创建Bean实例 instanceWrapper = createBeanInstance(beanName, mbd, args); } Object bean = instanceWrapper.getWrappedInstance(); // 如果允许当前Bean早期曝光。只要Bean是单例的并且allowCircularReferences 属性为true(默认为true) boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) { // 这里调用了addSingletonFactory方法将刚创建好的Bean保存到了三级缓存中。 addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // 删除与本次分析无关的代码..... Object exposedObject = bean; try {// Bean属性填充 populateBean(beanName, mbd, instanceWrapper); // 初始化Bean,熟知的Aware接口、InitializingBean接口.....都是在这里调用 exposedObject = initializeBean(beanName, exposedObject, mbd); } catch (Throwable ex) { } // 删除与本次分析无关的代码..... return exposedObject; }
先分析addSingletonFactory方法,因为在该方法中将Bean保存到了三级缓存中。
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) { Assert.notNull(singletonFactory, "Singleton factory must not be null"); synchronized (this.singletonObjects) { // 如果一级缓存中不存在指定beanName的key if (!this.singletonObjects.containsKey(beanName)) { // 将刚创建好的Bean实例保存到三级缓存中 this.singletonFactories.put(beanName, singletonFactory); // 从二级缓存中移除。 this.earlySingletonObjects.remove(beanName); this.registeredSingletons.add(beanName); } } }
处理Bean的依赖注入是由populateBean方法完成的,但整个执行链路太长了,这里就不展开讲了,只说下IoC容器在处理依赖时是如何一步一步调用到getBean方法的,这样就和我们自己写的处理字段注入的逻辑对上了。
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) { // 删除与本次分析无关代码... PropertyDescriptor[] filteredPds = null; if (hasInstAwareBpps) { if (pvs == null) { pvs = mbd.getPropertyValues(); } // 遍历所有已注册的BeanPostProcessor接口实现类,如果实现类是InstantiationAwareBeanPostProcessor接口类型的,调用其postProcessProperties方法。 for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName); // 删除与本次分析无关代码... pvs = pvsToUse; } } // 删除与本次分析无关代码... } }
在Spring 中,@Autowired注解是由AutowiredAnnotationBeanPostProcessor类处理,而@Resource注解是由CommonAnnotationBeanPostProcessor类处理,这两个类都实现了InstantiationAwareBeanPostProcessor接口,都是在覆写的postProcessProperties方法中完成了依赖注入。这里我们就分析@Autowired注解的处理。
// AutowiredAnnotationBeanPostProcessor#postProcessProperties public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) { // 根据beanName以及bean的class去查找Bean的依赖元数据-InjectionMetadata InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs); try {// 调用inject方法 metadata.inject(bean, beanName, pvs); } catch (BeanCreationException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex); } return pvs; }
在InjectionMetadata的inject方法中,获取当前Bean所有需要处理的依赖元素(InjectedElement),这是一个集合,遍历该集合,调用每一个依赖注入元素的inject方法。
// InjectionMetadata#inject public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { // 获取当前Bean所有的依赖注入元素(可能是方法,也可能是字段) Collection<InjectedElement> checkedElements = this.checkedElements; Collection<InjectedElement> elementsToIterate = (checkedElements != null ? checkedElements : this.injectedElements); if (!elementsToIterate.isEmpty()) { // 如果当前Bean的依赖注入项不为空,遍历该依赖注入元素 for (InjectedElement element : elementsToIterate) { // 调用每一个依赖注入元素的inject方法。 element.inject(target, beanName, pvs); } } }
在AutowiredAnnotationBeanPostProcessor类中定义了两个内部类-AutowiredFieldElement、AutowiredMethodElement继承自InjectedElement,它们分别对应字段注入和方法注入。
以大家常用的字段注入为例,在AutowiredFieldElement的inject方法中,首先判断当前字段是否已经被处理过,如果已经被处理过直接走缓存,否则调用BeanFactory的resolveDependency方法来处理依赖。
// AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable { Field field = (Field) this.member; Object value; if (this.cached) {// 如果当前字段已经被处理过,直接从缓存中获取 value = resolvedCachedArgument(beanName, this.cachedFieldValue); } else { // 构建依赖描述符 DependencyDescriptor desc = new DependencyDescriptor(field, this.required); desc.setContainingClass(bean.getClass()); Set<String> autowiredBeanNames = new LinkedHashSet<>(1); Assert.state(beanFactory != null, "No BeanFactory available"); TypeConverter typeConverter = beanFactory.getTypeConverter(); try {// 调用BeanFactory的resolveDependency来解析依赖 value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter); } catch (BeansException ex) { throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex); } // 删除与本次分析无关代码.... } if (value != null) { // 通过反射来对属性进行赋值 ReflectionUtils.makeAccessible(field); field.set(bean, value); } } }
在DefaultListableBeanFactory实现的resolveDependency方法,最终还是调用doResolveDependency方法来完成依赖解析的功能。在Spring源码中,如果存在do什么什么方法,那么该方法才是真正干活的方法。
// DefaultListableBeanFactory#resolveDependency public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { // ..... // 如果在字段(方法)上添加了@Lazy注解,那么在这里将不会真正的去解析依赖 Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary( descriptor, requestingBeanName); if (result == null) { // 如果未添加@Lazy注解,那么则调用doResolveDependency方法来解析依赖 result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter); } return result; }
// DefaultListableBeanFactory#doResolveDependency public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName, @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException { //..... try { // 根据名称以及类型查找合适的依赖 Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) {// 如果未找到相关依赖 if (isRequired(descriptor)) { // 如果该依赖是必须的(例如@Autowired的required属性),直接抛出异常 raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor); } return null; } String autowiredBeanName; Object instanceCandidate; // 如果查找到的依赖多于一个,例如某个接口存在多个实现类,并且多个实现类都注册到IoC容器中。 if (matchingBeans.size() > 1) {// 决定使用哪一个实现类,@Primary等方式都是在这里完成 autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (autowiredBeanName == null) { if (isRequired(descriptor) || !indicatesMultipleBeans(type)) { return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans); } else { return null; } } instanceCandidate = matchingBeans.get(autowiredBeanName); } else { // We have exactly one match. Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next(); autowiredBeanName = entry.getKey(); instanceCandidate = entry.getValue(); } if (autowiredBeanNames != null) { autowiredBeanNames.add(autowiredBeanName); } // 如果查找到的依赖是某个类的Class(通常如此),而不是实例, //调用描述符的方法来根据类型resolveCandidate方法来获取该类型的实例。 if (instanceCandidate instanceof Class) { instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this); } //... }
在依赖描述符的resolveCandidate方法中,是通过调用BeanFactory 的getBean方法来完成所依赖Bean实例的获取。
// DependencyDescriptor#resolveCandidate public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory) throws BeansException { return beanFactory.getBean(beanName); }
而在getBean方法实现中,依然是通过调用doGetBean方法来完成。这也和我们自己写的依赖处理基本一致,只不过我们自己写的比较简单,而Spring要考虑和处理的场景复杂,因此代码比较繁杂,但大体思路都是一样的。
// AbstractBeanFactory#getBean public Object getBean(String name) throws BeansException { return doGetBean(name, null, null, false); }
重点是前面我们写的处理循环依赖的Demo,如果理解那个代码,再看Spring的循环依赖处理,就会发现很简单。
总结:
循环依赖就是指两个Bean之间存在相互引用关系,例如A依赖B,B又依赖A,但Spring只能解决属性循环依赖,不能解决构造函数循环依赖,这种场景也无法解决。
Spring解决循环依赖的关键就是在处理Bean的属性依赖时,先将Bean存到三级缓存中,当存在循环依赖时,从三级缓存中获取到相关Bean,然后从三级缓存中移除,存入到二级缓存中,最后初始化完毕后存入到一级缓存中。
相关推荐:java入门教程
Atas ialah kandungan terperinci java面试题:你知道什么是循环依赖么?Spring是如何解决循环依赖的?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas





Panduan Nombor Sempurna di Jawa. Di sini kita membincangkan Definisi, Bagaimana untuk menyemak nombor Perfect dalam Java?, contoh dengan pelaksanaan kod.

Panduan untuk Weka di Jawa. Di sini kita membincangkan Pengenalan, cara menggunakan weka java, jenis platform, dan kelebihan dengan contoh.

Panduan untuk Nombor Smith di Jawa. Di sini kita membincangkan Definisi, Bagaimana untuk menyemak nombor smith di Jawa? contoh dengan pelaksanaan kod.

Dalam artikel ini, kami telah menyimpan Soalan Temuduga Spring Java yang paling banyak ditanya dengan jawapan terperinci mereka. Supaya anda boleh memecahkan temuduga.

Java 8 memperkenalkan API Stream, menyediakan cara yang kuat dan ekspresif untuk memproses koleksi data. Walau bagaimanapun, soalan biasa apabila menggunakan aliran adalah: bagaimana untuk memecahkan atau kembali dari operasi foreach? Gelung tradisional membolehkan gangguan awal atau pulangan, tetapi kaedah Foreach Stream tidak menyokong secara langsung kaedah ini. Artikel ini akan menerangkan sebab -sebab dan meneroka kaedah alternatif untuk melaksanakan penamatan pramatang dalam sistem pemprosesan aliran. Bacaan Lanjut: Penambahbaikan API Java Stream Memahami aliran aliran Kaedah Foreach adalah operasi terminal yang melakukan satu operasi pada setiap elemen dalam aliran. Niat reka bentuknya adalah

Panduan untuk TimeStamp to Date di Java. Di sini kita juga membincangkan pengenalan dan cara menukar cap waktu kepada tarikh dalam java bersama-sama dengan contoh.

Kapsul adalah angka geometri tiga dimensi, terdiri daripada silinder dan hemisfera di kedua-dua hujungnya. Jumlah kapsul boleh dikira dengan menambahkan isipadu silinder dan jumlah hemisfera di kedua -dua hujungnya. Tutorial ini akan membincangkan cara mengira jumlah kapsul yang diberikan dalam Java menggunakan kaedah yang berbeza. Formula volum kapsul Formula untuk jumlah kapsul adalah seperti berikut: Kelantangan kapsul = isipadu isipadu silinder Dua jumlah hemisfera dalam, R: Radius hemisfera. H: Ketinggian silinder (tidak termasuk hemisfera). Contoh 1 masukkan Jejari = 5 unit Ketinggian = 10 unit Output Jilid = 1570.8 Unit padu menjelaskan Kirakan kelantangan menggunakan formula: Kelantangan = π × r2 × h (4

Java ialah bahasa pengaturcaraan popular yang boleh dipelajari oleh pembangun pemula dan berpengalaman. Tutorial ini bermula dengan konsep asas dan diteruskan melalui topik lanjutan. Selepas memasang Kit Pembangunan Java, anda boleh berlatih pengaturcaraan dengan mencipta program "Hello, World!" Selepas anda memahami kod, gunakan gesaan arahan untuk menyusun dan menjalankan program, dan "Hello, World!" Pembelajaran Java memulakan perjalanan pengaturcaraan anda, dan apabila penguasaan anda semakin mendalam, anda boleh mencipta aplikasi yang lebih kompleks.
