精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

我們一起聊聊什么是Spring的循環依賴

開發 架構
當我們使用構造方法進行注入時,就會遇到這種情況。如果您使用其它類型的注入,你應該不會遇到這個問題。因為它是在需要時才會被注入,而不是上下文加載被要求注入。

1. 什么是循環依賴?

一般場景是一個Bean A依賴Bean B,而Bean B也依賴Bean A :Bean A → Bean B → Bean A當然我們也可以添加更多的依賴層次,比如:Bean A → Bean B → Bean C → Bean D → Bean E → Bean A

2. Spring中的循環依賴

當Spring上下文在加載所有的bean時,他會嘗試按照他們他們關聯關系的順序進行創建。比如,如果不存在循環依賴時,例如:Bean A → Bean B → Bean CSpring會先創建Bean C,再創建Bean B(并將Bean C注入到Bean B中),最后再創建Bean A(并將Bean B注入到Bean A中)。但是,如果我們存在循環依賴,Spring上下文不知道應該先創建哪個Bean,因為它們依賴于彼此。在這種情況下,Spring會在加載上下文時,拋出一個BeanCurrentlyInCreationException。當我們使用構造方法進行注入時,就會遇到這種情況。如果您使用其它類型的注入,你應該不會遇到這個問題。因為它是在需要時才會被注入,而不是上下文加載被要求注入。

3. 讓我們看一個例子

我們定義兩個Bean并且互相依賴(通過構造函數注入)。

package com.test.demo.service;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class A {

    private B b;

    public A (B b) {
        this.b = b;
    }
}

package com.test.demo.service;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class B {

    private A a ;

    public B(A a) {
        this.a = a;
    }
}

這個時候我們我們啟動項目就會發現項目啟動不起來,如下提示信息

/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/bin/java -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -javaagent:/private/var/folders/03/_9q6fw895913yznz6wq544_00000gn/T/AppTranslocation/332DF0A0-AF1B-49F1-8739-1BF4A9F4BAF7/d/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=50456:/private/var/folders/03/_9q6fw895913yznz6wq544_00000gn/T/AppTranslocation/332DF0A0-AF1B-49F1-8739-1BF4A9F4BAF7/d/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_211.jdk/Contents/Home/lib/tools.jar:/Users/haolonglong/IdeaProjects/project_boot/target/classes:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-starter-web/2.4.0-SNAPSHOT/spring-boot-starter-web-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-starter-json/2.4.0-SNAPSHOT/spring-boot-starter-json-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/core/jackson-databind/2.11.1/jackson-databind-2.11.1.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.11.1/jackson-annotations-2.11.1.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/core/jackson-core/2.11.1/jackson-core-2.11.1.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jdk8/2.11.1/jackson-datatype-jdk8-2.11.1.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.11.1/jackson-datatype-jsr310-2.11.1.jar:/Users/haolonglong/.m2/repository/com/fasterxml/jackson/module/jackson-module-parameter-names/2.11.1/jackson-module-parameter-names-2.11.1.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-starter-tomcat/2.4.0-SNAPSHOT/spring-boot-starter-tomcat-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/org/apache/tomcat/embed/tomcat-embed-core/9.0.36/tomcat-embed-core-9.0.36.jar:/Users/haolonglong/.m2/repository/org/glassfish/jakarta.el/3.0.3/jakarta.el-3.0.3.jar:/Users/haolonglong/.m2/repository/org/apache/tomcat/embed/tomcat-embed-websocket/9.0.36/tomcat-embed-websocket-9.0.36.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-web/5.3.0-M1/spring-web-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-beans/5.3.0-M1/spring-beans-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-webmvc/5.3.0-M1/spring-webmvc-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-aop/5.3.0-M1/spring-aop-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-context/5.3.0-M1/spring-context-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-expression/5.3.0-M1/spring-expression-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-starter/2.4.0-SNAPSHOT/spring-boot-starter-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot/2.4.0-SNAPSHOT/spring-boot-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-autoconfigure/2.4.0-SNAPSHOT/spring-boot-autoconfigure-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/org/springframework/boot/spring-boot-starter-logging/2.4.0-SNAPSHOT/spring-boot-starter-logging-2.4.0-20200709.121446-178.jar:/Users/haolonglong/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar:/Users/haolonglong/.m2/repository/ch/qos/logback/logback-core/1.2.3/logback-core-1.2.3.jar:/Users/haolonglong/.m2/repository/org/apache/logging/log4j/log4j-to-slf4j/2.13.3/log4j-to-slf4j-2.13.3.jar:/Users/haolonglong/.m2/repository/org/apache/logging/log4j/log4j-api/2.13.3/log4j-api-2.13.3.jar:/Users/haolonglong/.m2/repository/org/slf4j/jul-to-slf4j/1.7.30/jul-to-slf4j-1.7.30.jar:/Users/haolonglong/.m2/repository/jakarta/annotation/jakarta.annotation-api/1.3.5/jakarta.annotation-api-1.3.5.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-core/5.3.0-M1/spring-core-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/springframework/spring-jcl/5.3.0-M1/spring-jcl-5.3.0-M1.jar:/Users/haolonglong/.m2/repository/org/yaml/snakeyaml/1.26/snakeyaml-1.26.jar:/Users/haolonglong/.m2/repository/org/slf4j/slf4j-api/1.7.30/slf4j-api-1.7.30.jar com.test.demo.DemoApplication

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::       (v2.4.0-SNAPSHOT)

2020-07-10 08:01:20.125  INFO 879 --- [           main] com.test.demo.DemoApplication            : Starting DemoApplication using Java 1.8.0_211 on localhost with PID 879 (/Users/haolonglong/IdeaProjects/project_boot/target/classes started by haolonglong in /Users/haolonglong/IdeaProjects/project_boot)
2020-07-10 08:01:20.128  INFO 879 --- [           main] com.test.demo.DemoApplication            : No active profile set, falling back to default profiles: default
2020-07-10 08:01:21.143  INFO 879 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-07-10 08:01:21.155  INFO 879 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-07-10 08:01:21.155  INFO 879 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.36]
2020-07-10 08:01:21.226  INFO 879 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-07-10 08:01:21.226  INFO 879 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1051 ms
2020-07-10 08:01:21.263  WARN 879 --- [           main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a' defined in file [/Users/haolonglong/IdeaProjects/project_boot/target/classes/com/test/demo/service/A.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b' defined in file [/Users/haolonglong/IdeaProjects/project_boot/target/classes/com/test/demo/service/B.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
2020-07-10 08:01:21.265  INFO 879 --- [           main] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2020-07-10 08:01:21.276  INFO 879 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-07-10 08:01:21.278 ERROR 879 --- [           main] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  a defined in file [/Users/haolonglong/IdeaProjects/project_boot/target/classes/com/test/demo/service/A.class]
↑     ↓
|  b defined in file [/Users/haolonglong/IdeaProjects/project_boot/target/classes/com/test/demo/service/B.class]
└─────┘



Process finished with exit code 1

4 解決方法

4.1 重新設計

當你有一個循環依賴,很可能你有一個設計問題并且各責任沒有得到很好的分離。你應該盡量正確地重新設計組件,以便它們的層次是精心設計的,也沒有必要循環依賴。如果不能重新設計組件(可能有很多的原因:遺留代碼,已經被測試并不能修改代碼,沒有足夠的時間或資源來完全重新設計......),但有一些變通方法來解決這個問題。

4.2 使用 @Lazy

解決Spring 循環依賴的一個簡單方法就是對一個Bean使用延時加載。也就是說:這個Bean并沒有完全的初始化完,實際上他注入的是一個代理,只有當他首次被使用的時候才會被完全的初始化。

package com.test.demo.service;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class A {

    private B b;

    public A (@Lazy B b) {
        this.b = b;
    }
}

4.3 使用 Setter/Field 注入

其中最流行的解決方法,就是Spring文檔中建議,使用setter注入。簡單地說,你對你須要注入的bean是使用setter注入(或字段注入),而不是構造函數注入。通過這種方式創建Bean,實際上它此時的依賴并沒有被注入,只有在你須要的時候他才會被注入進來。

package com.test.demo.service;

import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class A {

    private B b;

    public void setB(B b) {
        this.b = b;
    }

    public B getB() {
        return b;
    }
}

package com.test.demo.service;

import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
public class B {

    private A a ;

    public void setA(A a) {
        this.a = a;
    }

    public A getA() {
        return a;
    }
}

4.4 使用 @PostConstruct

打破循環的另一種方式是,在要注入的屬性(該屬性是一個bean)上使用 @Autowired ,并使用@PostConstruct 標注在另一個方法,且該方法里設置對其他的依賴。我們的Bean將修改成下面的代碼:

4.5 實現ApplicationContextAware and InitializingBean接口

如果一個Bean實現了ApplicationContextAware,該Bean可以訪問Spring上下文,并可以從那里獲取到其他的bean。實現InitializingBean接口,表明這個bean在所有的屬性設置完后做一些后置處理操作(調用的順序為init-method后調用);在這種情況下,我們需要手動設置依賴。

@Component
public class CircularDependencyA implements ApplicationContextAware, InitializingBean {
 
    private CircularDependencyB circB;
 
    private ApplicationContext context;
 
    public CircularDependencyB getCircB() {
        return circB;
    }
 
    @Override
    public void afterPropertiesSet() throws Exception {
        circB = context.getBean(CircularDependencyB.class);
    }
 
    @Override
    public void setApplicationContext(final ApplicationContext ctx) throws BeansException {
        context = ctx;
    }
}

public class CircularDependencyB {
 
    private CircularDependencyA circA;
 
    private String message = "Hi!";
 
    @Autowired
    public void setCircA(CircularDependencyA circA) {
        this.circA = circA;
    }
 
    public String getMessage() {
        return message;
    }
}

同樣,我們可以運行之前的測試,看看有沒有異常拋出,程序結果是否是我們所期望的那樣。綜上,一般采用第三種setter的方式和第二種@Lazy延遲加載的策略

5 實現原理

5.1. 過程演示

關于Spring bean的創建,其本質上還是一個對象的創建,既然是對象,讀者朋友一定要明白一點就是,一個完整的對象包含兩部分:當前對象實例化和對象屬性的實例化。在Spring中,對象的實例化是通過反射實現的,而對象的屬性則是在對象實例化之后通過一定的方式設置的。這個過程可以按照如下方式進行理解:

然后我們看上面解決循環依賴注入的示例3

@Component
public class A {

    private B b;

    public void setB(B b) {
        this.b = b;
    }

    public B getB() {
        return b;
    }
}

可以看到,這里A和B中各自都以對方為自己的全局屬性。這里首先需要說明的一點是,Spring實例化bean是通過ApplicationContext.getBean()方法來進行的。如果要獲取的對象依賴了另一個對象,那么其首先會創建當前對象,然后通過遞歸的調用ApplicationContext.getBean()方法來獲取所依賴的對象,最后將獲取到的對象注入到當前對象中。這里我們以上面的首先初始化A對象實例為例進行講解。

  • 首先Spring嘗試通過ApplicationContext.getBean()方法獲取A對象的實例,由于Spring容器中還沒有A對象實例,因而其會創建一個A對象,然后發現其依賴了B對象,因而會嘗試遞歸的通過ApplicationContext.getBean()方法獲取B對象的實例。
  • Spring容器中此時也沒有B對象的實例,因而其還是會先創建一個B對象的實例。需要注意這個時間點,此時A對象和B對象都已經創建了,并且保存在Spring容器中了,只不過A對象的屬性b和B對象的屬性a都還沒有設置進去。
  • 在Spring創建B對象之后,Spring發現B對象依賴了屬性A,因而此時還是會嘗試遞歸的調用ApplicationContext.getBean()方法獲取A對象的實例,因為Spring中已經有一個A對象的實例,雖然只是半成品(其屬性b還未初始化),但其也還是目標bean,因而會將該A對象的實例返回。此時,B對象的屬性a就設置進去了。
  • 然后還是ApplicationContext.getBean()方法遞歸的返回,也就是將B對象的實例返回,此時就會將該實例設置到A對象的屬性b中。
  • 這個時候,注意A對象的屬性b和B對象的屬性a都已經設置了目標對象的實例了。讀者朋友可能會比較疑惑的是,前面在為對象B設置屬性a的時候,這個A類型屬性還是個半成品。但是需要注意的是,這個A是一個引用,其本質上還是最開始就實例化的A對象。而在上面這個遞歸過程的最后,Spring將獲取到的B對象實例設置到了A對象的屬性b中了,這里的A對象其實和前面設置到實例B中的半成品A對象是同一個對象,其引用地址是同一個,這里為A對象的b屬性設置了值,其實也就是為那個半成品的a屬性設置了值。下面我們通過一個流程圖來對這個過程進行講解:

圖中getBean()表示調用Spring的ApplicationContext.getBean()方法,而該方法中的參數,則表示我們要嘗試獲取的目標對象。圖中的黑色箭頭表示一開始的方法調用走向,走到最后,返回了Spring中緩存的A對象之后,表示遞歸調用返回了,此時使用綠色的箭頭表示。從圖中我們可以很清楚的看到,B對象的a屬性是在第三步中注入的半成品A對象,而A對象的b屬性是在第二步中注入的成品B對象,此時半成品的A對象也就變成了成品的A對象,因為其屬性已經設置完成了。

5.2 源碼解析

重點方法流程如下

對于Spring處理循環依賴問題的方式,我們這里通過上面的流程圖其實很容易就可以理解,需要注意的一個點就是,Spring是如何標記開始生成的A對象是一個半成品,并且是如何保存A對象的。

  • 標記工作Spring是使用ApplicationContext的屬性Set<String> singletonsCurrentlyInCreation來保存的,
  • 半成品的A對象則是通過Map<String, ObjectFactory<?>> singletonFactories來保存的,這里的ObjectFactory是一個工廠對象,可通過調用其getObject()方法來獲取目標對象。

在AbstractBeanFactory.doGetBean()方法中獲取對象的方法如下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
  @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
 // 嘗試通過bean名稱獲取目標bean對象,比如這里的A對象
 Object sharedInstance = getSingleton(beanName);
 
 // 我們這里的目標對象都是單例的
 if (mbd.isSingleton()) {
  // 這里就嘗試創建目標對象,第二個參數傳的就是一個ObjectFactory類型的對象,這里是使用Java8的lamada
  // 表達式書寫的,只要上面的getSingleton()方法返回值為空,則會調用這里的getSingleton()方法來創建
  // 目標對象
  sharedInstance = getSingleton(beanName, () -> {
   try {
    // 嘗試創建目標對象
    return createBean(beanName, mbd, args);
   } catch (BeansException ex) {
    throw ex;
   }
  });
 }
 return (T) bean;
}

這里的doGetBean()方法是非常關鍵的一個方法上面也主要有兩個步驟,

  • 第一個步驟的getSingleton()方法的作用是嘗試從緩存中獲取目標對象,如果沒有獲取到,則嘗試獲取半成品的目標對象;如果第一個步驟沒有獲取到目標對象的實例,那么就進入第二個步驟,
  • 第二個步驟的getSingleton()方法的作用是嘗試創建目標對象,并且為該對象注入其所依賴的屬性。這里其實就是主干邏輯,我們前面圖中已經標明,在整個過程中會調用三次doGetBean方法。
  • 第一次調用的時候會嘗試獲取A對象實例,此時走的是第一個getSingleton()方法,由于沒有已經創建的A對象的成品或半成品,因而這里得到的是null,
  • 然后就會調用第二個getSingleton()方法,創建A對象的實例,然后遞歸的調用doGetBean()方法,嘗試獲取B對象的實例以注入到A對象中,此時由于Spring容器中也沒有B對象的成品或半成品,因而還是會走到第二個getSingleton()方法,在該方法中創建B對象的實例,創建完成之后,嘗試獲取其所依賴的A的實例作為其屬性,因而還是會遞歸的調用doGetBean()方法。
  • 此時需要注意的是,在前面由于已經有了一個半成品的A對象的實例,因而這個時候,再嘗試獲取A對象的實例的時候,會走第一個getSingleton()方法,在該方法中會得到一個半成品的A對象的實例。然后將該實例返回,并且將其注入到B對象的屬性a中,此時B對象實例化完成。然后將實例化完成的B對象遞歸的返回,此時就會將該實例注入到A對象中,這樣就得到了一個成品的A對象。

第一個getSingleton()方法

@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
 // 嘗試從緩存中獲取成品的目標對象,如果存在,則直接返回
 Object singletonObject = this.singletonObjects.get(beanName);
 // 如果緩存中不存在目標對象,則判斷當前對象是否已經處于創建過程中,
    //在前面的講解中,第一次嘗試獲取A對象的實例之后,就會將A對象標記為正在創建中,
    //因而最后再嘗試獲取A對象的時候,這里的if判斷就會為true
 if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
  synchronized (this.singletonObjects) {
   singletonObject = this.earlySingletonObjects.get(beanName);
   if (singletonObject == null && allowEarlyReference) {
    // 這里的singletonFactories是一個Map,其key是bean的名稱,
    //而值是一個ObjectFactory類型的對象,
    //這里對于A和B而言,調用圖其getObject()方法返回的就是A和B對象的實例,無論是否是半成品
    ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
    if (singletonFactory != null) {
     // 獲取目標對象的實例
     singletonObject = singletonFactory.getObject();
     this.earlySingletonObjects.put(beanName, singletonObject);
     this.singletonFactories.remove(beanName);
    }
   }
  }
 }
 return singletonObject;
}

這里我們會存在一個問題就是A的半成品實例是如何實例化的,然后是如何將其封裝為一個ObjectFactory類型的對象,并且將其放到上面的singletonFactories屬性中的。

這主要是在前面的第二個getSingleton()方法中,其最終會通過其傳入的第二個參數,從而調用

createBean()方法,該方法的最終調用是委托給了另一個doCreateBean()方法進行的,這里面有如下一段代碼:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
 throws BeanCreationException {

 // 實例化當前嘗試獲取的bean對象,比如A對象和B對象都是在這里實例化的
 BeanWrapper instanceWrapper = null;
 if (mbd.isSingleton()) {
  instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
 }
 if (instanceWrapper == null) {
  instanceWrapper = createBeanInstance(beanName, mbd, args);
 }
 
 // 判斷Spring是否配置了支持提前暴露目標bean,也就是是否支持提前暴露半成品的bean
 boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences 
  && isSingletonCurrentlyInCreation(beanName));
 if (earlySingletonExposure) {
  // 如果支持,為了解決循環依賴,再bean初始化完成前將創建實例的objectFactory加入工廠
  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
 }

 try {
  // 在初始化實例之后,這里就是判斷當前bean是否依賴了其他的bean,如果依賴了,
  // 就會遞歸的調用getBean()方法嘗試獲取目標bean
  populateBean(beanName, mbd, instanceWrapper);
 } catch (Throwable ex) {
  // 省略...
 }
 
 return exposedObject;
}

到這里,Spring整個解決循環依賴問題的實現思路已經比較清楚了。對于整體過程,讀者朋友只要理解兩點:

  • Spring是通過遞歸的方式獲取目標bean及其所依賴的bean的;
  • Spring實例化一個bean的時候,是分兩步進行的,首先實例化目標bean,然后為其注入屬性。
  • 結合這兩點,也就是說,Spring在實例化一個bean的時候,是首先遞歸的實例化其所依賴的所有bean,直到某個bean沒有依賴其他bean,此時就會將該實例返回,然后反遞歸的將獲取到的bean設置為各個上層bean的屬性的。

5.3 三級緩存分析

對于單例來說,在Spring容器整個生命周期內,有且只有一個對象,所以很容易想到這個對象應該存在Cache中,Spring為了解決單例的循環依賴問題,使用了三級緩存。

首先我們看源碼,三級緩存主要指:

/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

這三級緩存分別指:

  • 一級緩存:singletonObjects:存放完全實例化屬性賦值完成的Bean,直接可以使用。
  • 二級緩存:earlySingletonObjects:存放早期Bean的引用,尚未屬性裝配的Bean。
  • 三級緩存:singletonFactories:存放可以生成Bean的工廠,可以通過工廠獲取需要的bean。

我們在創建bean的時候,首先想到的是從cache中獲取這個單例的bean,這個緩存就是singletonObjects。主要調用方法就就是:getSingleton(String beanName, boolean allowEarlyReference)

分析getSingleton()的整個過程,Spring首先從一級緩存singletonObjects中獲取。如果獲取不到,并且對象正在創建中,就再從二級緩存earlySingletonObjects中獲取。如果還是獲取不到且允許singletonFactories通過getObject()獲取,就從三級緩存singletonFactory.getObject()(三級緩存)獲取,如果獲取到了則從singletonFactories中移除,并放入earlySingletonObjects中。其實也就是從三級緩存移動到了二級緩存。

6 問題

1 為什么使用三級緩存

1 使用一級緩存

  • 實例化A -> 將半成品的A放入singletonObjects中。
  • 填充A的屬性時發現取不到B->實例化B。
  • 從singletonObjects中取出A填充B的屬性。
  • 將成品B放入singletonObjects->將B填充到A的屬性中。
  • 將成品A放入singletonObjects。

問題:這種基本流程是通的,但是如果在整個流程進行中,有另一個線程要來取A,那么有可能拿到的只是一個屬性都為null的半成品A,這樣就會有問題。

2 使用二級緩存

a)使用singletonObjects和earlySingletonObjects

成品放在singletonObjects中,半成品放在earlySingletonObjects中,流程如下:

  • 實例化A ->將半成品的A放入earlySingletonObjects中。
  • 填充A的屬性時發現取不到B->實例化B->將半成品的A放入earlySingletonObjects中
  • 從earlySingletonObjects中取出A填充B的屬性->將成品B放入singletonObjects
  • 并從earlySingletonObjects中刪除B->將B填充到A的屬性中。
  • 將成品A放入singletonObjects并刪除earlySingletonObjects。

問題:這樣的流程是線程安全的,不過如果A上加個切面(AOP),這種做法就沒法滿足需求了,因為earlySingletonObjects中存放的都是原始對象,而我們需要注入的其實是A的代理對象。

b)使用singletonObjects和singletonFactories

成品放在singletonObjects中,半成品通過singletonFactories來獲取,流程如下:

  • 實例化A ->創建A的對象工廠并放入singletonFactories中
  • 填充A的屬性時發現取不到B->實例化B->創建B的對象工廠并放入singletonFactories中
  • 從singletonFactories中獲取A的對象工廠并獲取A填充到B中->將成品B放入singletonObjects并從singletonFactories中刪除B的對象工廠
  • 將B填充到A的屬性中->將成品A放入singletonObjects并刪除A的對象工廠。

問題:這樣的流程也適用于普通的IOC以及有并發的場景,但如果A上加個切面(AOP)的話,這種情況也無法滿足需求。因為不可能每次執行singleFactory.getObject()方法都給我產生一個新的代理對象,所以還要借助另外一個緩存來保存產生的代理對象。

責任編輯:武曉燕 來源: 今日頭條
相關推薦

2024-02-20 21:34:16

循環GolangGo

2022-08-01 07:57:03

數組操作內存

2024-02-28 08:41:51

Maven沖突版本

2022-05-26 00:19:29

通信信息5G

2022-02-23 08:41:58

NATIPv4IPv6

2024-01-15 08:41:25

SwiftTypeScrip語法

2024-03-26 07:38:16

正向代理反向代理代碼

2022-10-08 00:00:05

SQL機制結構

2023-06-30 08:18:51

敏捷開發模式

2023-08-10 08:28:46

網絡編程通信

2023-08-04 08:20:56

DockerfileDocker工具

2022-05-24 08:21:16

數據安全API

2023-09-10 21:42:31

2023-04-26 07:30:00

promptUI非結構化

2021-08-27 07:06:10

IOJava抽象

2023-03-29 08:26:06

2023-08-02 08:35:54

文件操作數據源

2024-06-14 09:32:12

2022-09-08 08:50:17

SSDOracleCPU

2024-09-09 08:53:56

點贊
收藏

51CTO技術棧公眾號

亚洲欧美日韩视频二区| xxxx日韩| 日韩一区在线播放| av电影成人| 国产又大又黄又粗| 日韩欧美午夜| 欧美精品一区二区三区视频| 日韩有码免费视频| 中中文字幕av在线| 91麻豆蜜桃一区二区三区| 国产精品永久免费视频| 日韩久久久久久久久| 精品国产精品| 亚洲国产美女久久久久| 日本黄色的视频| 国产伦理精品| 综合av第一页| 日韩精品一区二区三区外面| 亚洲国产福利视频| 日韩av不卡在线观看| 久久男人av资源网站| 成人在线观看免费高清| 天天久久夜夜| 日韩视频在线一区二区| 天堂中文视频在线| sis001欧美| 亚洲一卡二卡三卡四卡| 亚洲一区免费看| 国产资源在线看| www.激情成人| 成人女人免费毛片| 91美女精品网站| 亚洲女人av| 久久久久久久久久久91| 最新av电影网站| 国产欧美一区二区三区精品观看| 精品国精品自拍自在线| 中文字幕线观看| 中韩乱幕日产无线码一区| 精品免费在线观看| 91免费黄视频| 日本片在线看| 一区二区三区欧美| 成年在线观看视频| 免费人成在线观看播放视频| 欧美国产精品一区二区| 欧洲久久久久久| 欧洲视频在线免费观看| 91在线视频官网| 狠狠色噜噜狠狠狠狠色吗综合| www.国产精品视频| 国产伦精品一区二区三区免费迷| 国产日韩欧美在线视频观看| 成人午夜精品视频| 日韩电影在线一区| 国产精品99久久久久久www| 久久国产精品免费看| 日韩视频一区| 欧美一级大片视频| 国产剧情在线视频| 日韩国产成人精品| 国产女同一区二区| 国产又大又黄又爽| 国产福利一区二区| 成人免费观看网站| 欧美特级特黄aaaaaa在线看| 波波电影院一区二区三区| 国产偷国产偷亚洲高清97cao| 欧美在线精品一区二区三区| 99久久免费精品高清特色大片| 精品国产乱码久久久久软件| 青青草手机在线| 国产拍揄自揄精品视频麻豆 | 丁香5月婷婷久久| 亚洲成人久久久久| 蜜桃av免费看| 国内成人自拍| 久久伊人精品一区二区三区| 久草视频免费在线| 亚洲一区日韩| 国产精品综合网站| www.精品久久| 99精品一区二区三区| 青青草成人激情在线| 欧美另类极品| 亚洲国产欧美日韩另类综合| 日韩a在线播放| 国产电影一区二区| 日韩高清不卡av| 中文字幕在线观看二区| 国产一区美女| 国产精品久久久久久av| 国产黄a三级三级三级| 91日韩在线专区| eeuss中文| 亚洲一区站长工具| 337p亚洲精品色噜噜| 国产麻豆xxxvideo实拍| 日韩欧美午夜| 欧美专区福利在线| 国产精品视频久久久久久| 久久综合久久综合久久综合| 中文字幕在线乱| 亚洲精品永久免费视频| 91精品国产综合久久精品性色| 国产美女视频免费观看下载软件| av永久不卡| 91国语精品自产拍在线观看性色| 中文天堂在线资源| 不卡一区在线观看| 丰满女人性猛交| 欧美在线va视频| 亚洲福利视频网| 极品颜值美女露脸啪啪| 日韩av午夜在线观看| 国内精品久久国产| av在线app| 欧美日韩一级二级| 亚洲成人网在线播放| 黄色欧美成人| 92国产精品久久久久首页| 超碰97在线免费观看| 五月天欧美精品| 先锋资源在线视频| 国产精品麻豆久久| 国产精品久久97| 可以在线观看的黄色| 亚洲国产日韩a在线播放性色| 亚洲一级片网站| 精品99在线| 日本精品在线视频| 欧美女v视频| 午夜精品久久久久| 丰满人妻一区二区三区免费视频棣| 欧美gayvideo| 国产精品成人v| 青青草免费观看免费视频在线| 亚洲在线成人精品| 一级全黄裸体片| 女人色偷偷aa久久天堂| 亚洲一区二区三区毛片| 日本综合在线| 欧美精品粉嫩高潮一区二区| 国产aaaaaaaaa| 日本美女一区二区| 亚洲国产精品一区二区第一页| 国产欧美一区二区三区精品酒店| 日韩国产一区三区| 超碰超碰超碰超碰| 国产亚洲1区2区3区| 国产天堂在线播放| 欧美日韩激情| 国产精品日韩专区| 快射av在线播放一区| 欧美另类变人与禽xxxxx| 免费精品在线视频| 国产乱码精品一区二区三区忘忧草| eeuss中文| 91精品国产自产精品男人的天堂| 国模精品系列视频| 丝袜视频国产在线播放| 日本韩国欧美国产| 国精产品一区一区三区免费视频| 久久精品观看| 亚洲欧美日产图| 亚洲国产91视频| 欧美日韩第一页| 天天干天天爽天天操| 欧美性高跟鞋xxxxhd| 国产91丝袜美女在线播放| 卡一卡二国产精品| 成人短视频在线观看免费| 国产精品白丝av嫩草影院| 欧美专区中文字幕| 1024国产在线| 精品国产亚洲一区二区三区在线观看| 精品视频一区二区在线观看| 99re66热这里只有精品3直播| 美女黄色片视频| 欧美一区高清| 久久久久综合一区二区三区| av免费在线一区| 欧美乱大交xxxxx| 青青草视频免费在线观看| 欧美日韩另类一区| 五月天婷婷丁香| 欧美韩日一区二区三区| 韩国av中国字幕| 日韩在线播放一区二区| wwwjizzjizzcom| 国模精品一区| 国产精品国产亚洲精品看不卡15| 亚洲天堂资源| 欧美—级高清免费播放| 黄色小视频在线免费观看| 欧美一区二区成人6969| 丁香六月婷婷综合| 亚洲美女视频在线观看| 久久精品国产亚洲av久| 国产高清成人在线| 日本激情视频在线播放| 亚洲精品在线二区| 精品少妇人妻av一区二区| 美女毛片一区二区三区四区| 91性高湖久久久久久久久_久久99| 日本三级一区| 欧美精品激情在线观看| √新版天堂资源在线资源| 亚洲国产精品va在线看黑人| 92久久精品一区二区| 欧美性精品220| 丰满少妇高潮久久三区| 欧美国产成人精品| 给我看免费高清在线观看| 国产精品69毛片高清亚洲| 91人人澡人人爽人人精品| 亚洲毛片一区| 免费看欧美黑人毛片| 91精品国偷自产在线电影| 视频一区二区精品| 神马电影久久| 精品一区久久久| 日韩精品一区二区三区中文在线| 国产精品一区专区欧美日韩| 中文字幕人成乱码在线观看| 欧美国产日本高清在线| 伊人在线视频| 在线视频亚洲欧美| 国产永久av在线| 日韩电影第一页| 欧美 日韩 国产 在线| 欧美不卡一区二区三区四区| 国产三级在线观看视频| 欧美人与禽zozo性伦| 亚洲免费视频二区| 欧美三级电影网| 超碰在线免费97| 欧洲亚洲精品在线| 波多野结衣高清视频| 色婷婷亚洲一区二区三区| 久久草视频在线| 午夜欧美2019年伦理| 国产一级片免费观看| 一区二区三区波多野结衣在线观看| 黄色片网站在线播放| 一区在线观看视频| 亚洲国产123| 亚洲视频小说图片| 日韩高清dvd碟片| 亚洲欧美日韩国产手机在线 | 亚洲国产成人tv| 中日韩精品视频在线观看| 精品久久久久久中文字幕| 91精品国产乱码久久久张津瑜| 五月天网站亚洲| 丰满人妻老熟妇伦人精品| 色婷婷久久一区二区三区麻豆| 黄色片视频免费| 欧美熟乱第一页| 国产精品毛片一区二区在线看舒淇| 欧美日韩国产bt| 国产福利资源在线| 亚洲国产精品大全| 你懂的视频在线播放| 中文国产亚洲喷潮| 在线观看电影av| 69av成年福利视频| 日韩免费福利视频| 国产精品视频在线观看| 电影中文字幕一区二区| 99国精产品一二二线| 久久久免费毛片| 亚洲欧美日韩另类精品一区二区三区 | 欧美黑人巨大videos精品| 亚洲第一国产精品| 日韩一级片免费观看| 精品呦交小u女在线| 国产一级二级三级在线观看| 日韩在线激情视频| 欧美aaaaaaa| 国产大片精品免费永久看nba| 99亚洲伊人久久精品影院| 亚洲a中文字幕| 日韩av字幕| 亚洲欧美成人一区| 亚洲国产导航| 在线免费观看视频黄| 国产成人在线网站| 成都免费高清电影| 亚洲激情男女视频| 日本熟妇一区二区三区| 日韩亚洲欧美高清| 成人在线观看网站| 欧美高清在线观看| 美女色狠狠久久| 国产日韩一区二区| 欧美国产一区二区三区激情无套| 一卡二卡三卡视频| 久久99日本精品| 一区二区三区少妇| 亚洲欧美偷拍另类a∨色屁股| 中日韩精品视频在线观看| 欧美美女视频在线观看| 日韩在线无毛| 欧美人与性动交a欧美精品| 日韩欧美一区二区三区在线观看| 古典武侠综合av第一页| 日本欧美国产| www.com毛片| 国产成人精品影视| 开心激情五月网| 91黄视频在线| 无码国产精品一区二区免费16| 久久人人爽亚洲精品天堂| 二吊插入一穴一区二区| 好吊色欧美一区二区三区四区 | 一区二区精品在线观看| 午夜一区二区三区不卡视频| 五月天六月丁香| 中日韩免费视频中文字幕| 免费av网站在线| 亚洲精品久久久久| 免费污视频在线| 91免费在线视频| 91欧美在线| 杨幂毛片午夜性生毛片| 久久久五月婷婷| 成人精品免费在线观看| 亚洲成年人在线播放| 在线观看的网站你懂的| 成人在线一区二区| 99精品小视频| 不卡的在线视频| 国产精品久久久久aaaa| 特级西西444www高清大视频| 亚洲精品一区av在线播放| 国产精品蜜芽在线观看| 国产精品日本一区二区| 欧美三级特黄| 国产国语老龄妇女a片| 一区二区三区四区蜜桃| 超碰在线播放97| 欧美丰满少妇xxxxx| 亚洲国产中文在线| 人妻无码一区二区三区四区| 国产伦精品一区二区三区免费迷 | 青春草视频在线观看| 97在线资源站| 在线成人国产| 四虎精品一区二区| 欧美日韩激情美女| 久久久久久女乱国产| 国产精品com| 日韩电影一区| 亚洲一区二区偷拍| 伊人色综合久久天天人手人婷| 亚洲国产精品二区| 久久理论片午夜琪琪电影网| 国产一区二区三区亚洲| 亚洲自偷自拍熟女另类| 久久精品欧美日韩| 136福利视频导航| 欧美xxxx做受欧美.88| 中文久久电影小说| 日韩av资源在线| 欧美激情在线一区二区| 国产剧情久久久| 久久久日本电影| 久久av导航| xxxx在线免费观看| 亚洲va中文字幕| 黄视频在线观看免费| 成人情趣片在线观看免费| 激情综合视频| 一道本在线观看| 欧美精品乱码久久久久久按摩| 欧美日韩经典丝袜| 欧美日韩一区在线播放| 精品一区二区三区av| 国产一级二级三级视频| 亚洲图中文字幕| 国产精品视频一区二区三区| 鲁一鲁一鲁一鲁一色| 欧美国产欧美综合| 亚洲精品.www| 国产精品激情av电影在线观看| 综合天堂av久久久久久久| 一级特级黄色片| 欧美日韩视频第一区| 99热99re6国产在线播放| 日韩经典在线视频| 成人免费看的视频| 中文在线字幕免费观| 久久久久久999| 久久视频在线| 青青草成人免费视频| 3d动漫精品啪啪| 写真福利精品福利在线观看| 欧美 日韩 国产精品| 欧美韩国一区二区|