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

從一次啟動失敗深入剖析:Spring循環依賴的真相

開發 前端
循環依賴暴露了代碼結構的設計缺陷。理論上應通過分層和抽象來避免,但在復雜的業務交互中仍難以杜絕。雖然Spring利用三級緩存等機制默默解決了這一問題,使程序得以運行,但這絕不應是懈怠設計的借口。

一、背景

二、相關知識點簡介

    1. 循環依賴

    2.Spring創建Bean主要流程

三、案例分析

    1. 代碼分析

    2. 問題分析

    3. 解決方案

四、總結

一、背 景

預發環境一個后臺服務admin突然啟動失敗,異常如下:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'timeoutNotifyController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'spuCheckDomainServiceImpl': Bean with name 'spuCheckDomainServiceImpl' has been injected into other beans [...] 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.
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:598)
        at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:376)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1404)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
        at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
        at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:847)
        at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877)
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744)
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215)
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1204)
        at com.shizhuang.duapp.commodity.interfaces.admin.CommodityAdminApplication.main(CommodityAdminApplication.java:100)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:48)
        at org.springframework.boot.loader.Launcher.launch(Launcher.java:87)
        at org.springframework.boot.loader.Launcher.launch(Launcher.java:51)
        at org.springframework.boot.loader.PropertiesLauncher.main(PropertiesLauncher.java:578)

錯誤日志中明確寫道:“Bean has been injected into other beans ... in its raw version as part of a circular reference, but has eventually been wrapped.”這不僅僅是一個簡單的循環依賴錯誤。它揭示了一個更深層次的問題:當循環依賴遇上Spring的AOP代理(如@Transactional事務、自定義切面等),Spring在解決依賴的時,不得已將一個“半成品”(原始Bean)注入給了其他30多個Bean。而當這個“半成品”最終被“包裝”(代理)成“成品”時,先前那些持有“半成品”引用的Bean們,使用的卻是一個錯誤的版本。

這就像在組裝一個精密機器時,你把一個未經質檢的零件提前裝了進去,等質檢完成后,機器里混用著新舊版本的零件,最終的崩潰也就不可避免。

本篇文章將帶你一起:

  • 熟悉spring容器的循環依賴以及Spring容器如何解決循環依賴,創建bean相關的流程。
  • 深入解讀這條復雜錯誤日志背后的每一個關鍵線索;
  • 提供緊急止血方案;
  • 分享如何從架構設計上避免此類問題的實踐心得。

二、相關知識點簡介

循環依賴

什么是Bean循環依賴?

循環依賴:說白是一個或多個對象實例之間存在直接或間接的依賴關系,這種依賴關系構成了構成一個環形調用,主要有如下幾種情況。

第一種情況:自己依賴自己的直接依賴

圖片圖片

第二種情況:兩個對象之間的直接依賴

圖片圖片

前面兩種情況的直接循環依賴比較直觀,非常好識別,但是第三種間接循環依賴的情況有時候因為業務代碼調用層級很深,不容易識別出來。

循環依賴場景

構造器注入循環依賴:

@Service
public class A {public A(B b) {}}
@Service
public class B {public B(A a) {}}

結果:項目啟動失敗拋出異常BeanCurrentlyInCreationException

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a': Requested bean is currently in creation: Is there an unresolvable circular reference?
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:339)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:215)
        at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
        at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)

構造器注入構成的循環依賴,此種循環依賴方式無論是Singleton模式還是prototype模式都是無法解決的,只能拋出BeanCurrentlyInCreationException異常表示循環依賴。原因是Spring解決循環依賴依靠的是Bean的“中間態”這個概念,而中間態指的是已經實例化,但還沒初始化的狀態。而完成實例化需要調用構造器,所以構造器的循環依賴無法解決。

Singleton模式field屬性注入(setter方法注入)循環依賴:

這種方式是我們最為常用的依賴注入方式:

@Service
public class A {
    @Autowired
    private B b;
    }
@Service
public class B {
    @Autowired
    private A a;
    }

結果:項目啟動成功,正常運行

prototype field屬性注入循環依賴:

prototype在平時使用情況較少,但是也并不是不會使用到,因此此種方式也需要引起重視。

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class A {
    @Autowired
    private B b;
    }
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Service
public class B {
    @Autowired
    private A a;
    }

結果:需要注意的是本例中啟動時是不會報錯的(因為非單例Bean默認不會初始化,而是使用時才會初始化),所以很簡單咱們只需要手動getBean()或者在一個單例Bean內@Autowired一下它即可。

// 在單例Bean內注入
    @Autowired
    private A a;

這樣子啟動就報錯:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mytest.TestSpringBean': Unsatisfied dependency expressed through field 'a'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'a': Unsatisfied dependency expressed through field 'b'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'b': Unsatisfied dependency expressed through field 'a'; 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?
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:596)
        at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
        at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:374)

如何解決?可能有的小伙伴看到網上有說使用@Lazy注解解決:

@Lazy
    @Autowired
    private A a;

此處負責任的告訴你這樣是解決不了問題的(可能會掩蓋問題),@Lazy只是延遲初始化而已,當你真正使用到它(初始化)的時候,依舊會報如上異常。

對于Spring循環依賴的情況總結如下:

  • 不能解決的情況:構造器注入循環依賴,prototype field屬性注入循環依賴
  • 能解決的情況:field屬性注入(setter方法注入)循環依賴

Spring如何解決循環依賴

Spring 是通過三級緩存和提前曝光的機制來解決循環依賴的問題。

三級緩存

三級緩存其實就是用三個 Map 來存儲不同階段 Bean 對象。

一級緩存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
二級緩存
private final Map<String, ObjectearlySingletonObjects = new HashMap<>(16);
//三級緩存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16)
  • singletonObjects:用于存放完全初始化好的 bean,從該緩存中取出的 bean 可以直接使用。
  • earlySingletonObjects:提前曝光的單例對象的cache,存放原始的 bean 對象(尚未填充屬性),用于解決循環依賴。
  • singletonFactories:單例對象工廠的cache,存放 bean 工廠對象,用于解決循環依賴。

三級緩存解決循環依賴過程

假設現在我們有ServiceA和ServiceB兩個類,這兩個類相互依賴,代碼如下:

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    }


@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA ;
    }

下面的時序圖說明了spring用三級緩存解決循環依賴的主要流程:

圖片圖片

為什么需要三級緩存?

這是一個理解Spring容器如何解決循環依賴的核心概念。三級緩存是Spring為了解決循環依賴的同時,又能保證AOP代理的正確性而設計的精妙機制。

為了理解為什么需要三級緩存,我們一步步來看。

如果沒有緩存(Level 0)

假設有兩個Bean:ServiceA  和 ServiceB,它們相互依賴。

Java

@Component
public class ServiceA  {
    @Autowired
    private ServiceB serviceB;
}
@Component
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

創建過程(無緩存):

  • 開始創建 ServiceA -> 發現 ServiceA 需要 ServiceB -> 開始創建 ServiceB
  • 開始創建 ServiceB -> 發現 ServiceB 需要 ServiceA -> 開始創建 ServiceA
  • 開始創建 ServiceA -> 發現 ServiceA 需要 ServiceB -> ... 無限循環,StackOverflowError

結論:無法解決循環依賴,直接死循環。

如果只有一級緩存(Singleton Objects)

一級緩存存放的是已經完全創建好、初始化完畢的Bean。

問題:在Bean的創建過程中(比如在填充屬性 populateBean 時),ServiceA還沒創建完,它本身不應該被放入"已完成"的一級緩存。但如果ServiceB需要ServiceA,而一級緩存里又沒有ServiceA的半成品,ServiceB就無法完成創建。這就回到了上面的死循環問題。

結論:一級緩存無法解決循環依賴。

如果使用二級緩存

二級緩存的核心思路是:將尚未完全初始化好的“早期引用”暴露出來。

現在我們有:

  • 一級緩存(成品庫):存放完全準備好的Bean。
  • 二級緩存(半成品庫):存放剛剛實例化(調用了構造方法),但還未填充屬性和初始化的Bean的早期引用。

創建過程(二級緩存):

開始創建ServiceA:

  • 實例化ServiceA(調用ServiceA的構造方法),得到一個ServiceA的原始對象。
  • 將ServiceA的原始對象放入二級緩存(半成品庫)。
  • 開始為ServiceA填充屬性 -> 發現需要ServiceB。

開始創建ServiceB:

  • 實例化ServiceB(調用B的構造方法),得到一個ServiceB的原始對象。
  • 將ServiceB的原始對象放入二級緩存。
  • 開始為ServiceB填充屬性 -> 發現需要ServiceA。

ServiceB從二級緩存中獲取A:

  • ServiceB成功從二級緩存中拿到了ServiceA的早期引用(原始對象)。
  • ServiceB順利完成了屬性填充、初始化等后續步驟,成為一個完整的Bean。
  • 將完整的ServiceB放入一級緩存(成品庫),并從二級緩存移除ServiceB。

ServiceA繼續創建:

  • ServiceA拿到了創建好的ServiceB,完成了自己的屬性填充和初始化。
  • 將完整的ServiceA放入一級緩存(成品庫),并從二級緩存移除ServiceA。

問題來了:如果ServiceA需要被AOP代理怎么辦?

如果A類上加了 @Transactional 等需要創建代理的注解,那么最終需要暴露給其他Bean的應該是ServiceA的代理對象,而不是ServiceA的原始對象。

在二級緩存方案中,ServiceB拿到的是A的原始對象。但最終ServiceA完成后,放入一級緩存的是ServiceA的代理對象。這就導致了:

  • ServiceB里面持有的ServiceA是原始對象。
  • 而其他地方注入的ServiceA是代理對象。
  • 這就造成了不一致!如果通過ServiceB的ServiceA去調用事務方法,事務會失效,因為那是一個沒有被代理的原始對象。

結論:二級緩存可以解決循環依賴問題,但無法正確處理需要AOP代理的Bean。

三級緩存的登場(Spring的終極方案)

為了解決代理問題,Spring引入了第三級緩存。它的核心不是一個直接存放對象(Object)的緩存,而是一個存放 ObjectFactory(對象工廠)的緩存。

三級緩存的結構是:Map<String, ObjectFactory<?>> singletonFactories

創建過程(三級緩存,以ServiceA需要代理為例):

  • 開始創建ServiceA:
  1. 實例化ServiceA,得到ServiceA的原始對象。
  2. 向三級緩存添加一個ObjectFactory。這個工廠的getObject()方法有能力判斷ServiceA是否需要代理,并返回相應的對象(原始對象或代理對象)。
  3. 開始為ServiceA填充屬性 -> 發現需要ServiceB。
  • 開始創建B:
  1. 實例化ServiceB。
  2. 同樣向三級緩存添加一個ServiceB的ObjectFactory。
  3. 開始為ServiceB填充屬性 -> 發現需要ServiceA。
  • ServiceB從緩存中獲取ServiceA:
  1. ServiceB發現一級緩存沒有ServiceA,二級緩存也沒有ServiceA。
  2. ServiceB發現三級緩存有A的ObjectFactory。
  3. B調用這個工廠的getObject()方法。此時,Spring會執行一個關鍵邏輯:

如果ServiceA需要被代理,工廠會提前生成ServiceA的代理對象并返回。

如果ServiceA不需要代理,工廠則返回A的原始對象。

  1. 將這個早期引用(可能是原始對象,也可能是代理對象)放入二級緩存,同時從三級緩存移除A的工廠。
  2. ServiceB拿到了ServiceA的正確版本的早期引用。

后續步驟:

  • ServiceB完成創建,放入一級緩存。
  • ServiceA繼續用ServiceB完成創建。在ServiceA初始化的最后,Spring會再次檢查:如果ServiceA已經被提前代理了(即在第3步中),那么就直接返回這個代理對象;如果沒有,則可能在此處創建代理(對于不需要解決循環依賴的Bean)。
  • 最終,將完整的ServiceA(代理對象)放入一級緩存,并清理二級緩存。

總結:為什么需要三級緩存?

需要三級緩存,是因為Spring要解決一個復雜問題:在存在循環依賴的情況下,如何確保所有Bean都能拿到最終形態(可能被AOP代理)的依賴對象,而不是原始的、未代理的對象。 三級緩存通過一個ObjectFactory將代理的時機提前,完美地解決了這個問題。二級緩存主要是為了性能優化而存在的。

spring三級緩存為什么不能解決

@Async注解的循環依賴問題

這觸及了 Spring 代理機制的一個深層次區別。@Async注解的循環依賴問題確實比@Transactional 更復雜,三級緩存無法完全解決。讓我們深入分析原因。

Spring創建Bean主要流程

為了容易理解 Spring 解決循環依賴過程,我們先簡單溫習下 Spring 容器創建 Bean 的主要流程。

從代碼看Spring對于Bean的生成過程,步驟還是很多的,我把一些擴展業務代碼省略掉:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
          throws BeanCreationException {
    if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    // Bean初始化第一步:默認調用無參構造實例化Bean
    // 如果是只有帶參數的構造方法,構造方法里的參數依賴注入,就是發生在這一步
    if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
    }


    //判斷Bean是否需要提前暴露對象用來解決循環依賴,需要則啟動spring三級緩存
    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創建第二步:填充屬性(DI依賴注入發生在此步驟)
      populateBean(beanName, mbd, instanceWrapper);
      // bean創建第三步:調用初始化方法,完成bean的初始化操作(AOP的第三個入口)
      // AOP是通過自動代理創建器AbstractAutoProxyCreator的postProcessAfterInitialization()
//方法的執行進行代理對象的創建的,AbstractAutoProxyCreator是BeanPostProcessor接口的實現
      exposedObject = initializeBean(beanName, exposedObject, mbd);




   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);
          Set<String> 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 " +
                   "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
          }
       }
    }
}


    } catch (Throwable ex) {
      // ...
    }
    // ...
    return exposedObject;
    }

從上述代碼看出,整體脈絡可以歸納成 3 個核心步驟:

  • 實例化Bean:主要是通過反射調用默認構造函數創建 Bean 實例,此時Bean的屬性都還是默認值null。被注解@Bean標記的方法就是此階段被調用的。
  • 填充Bean屬性:這一步主要是對Bean的依賴屬性進行填充,對@Value、@Autowired、@Resource注解標注的屬性注入對象引用。
  • 調用Bean初始化方法:調用配置指定中的init方法,如 xml文件指定Bean的init-method方法或注解 @Bean(initMethod = "initMethod")指定的方法。

三、案例分析

代碼分析

以下是我簡化后的類之間大體的依賴關系,工程內實際的依賴情況會比這個簡化版本復雜一些。

@RestController
public class OldCenterSpuController {
    @Resource
    private NewSpuApplyCheckServiceImpl newSpuApplyCheckServiceImpl;
}
@RestController
public class TimeoutNotifyController {
    @Resource
    private SpuCheckDomainServiceImpl spuCheckDomainServiceImpl;
}
@Component
public class NewSpuApplyCheckServiceImpl {
    @Resource
    private SpuCheckDomainServiceImpl spuCheckDomainServiceImpl;
}
@Component
@Slf4j
@Validated
public class SpuCheckDomainServiceImpl {
    @Resource
    private NewSpuApplyCheckServiceImpl newSpuApplyCheckServiceImpl;
}

從代碼看,主要是SpuCheckDomainServiceImpl和NewSpuApplyCheckServiceImpl 構成了一個依賴環。而我們從正常啟動的bean加載順序發現首先是從OldCenterSpuController開始加載的,具體情況如下所示:

OldCenterSpuController 
    ↓ (依賴)
NewSpuApplyCheckServiceImpl 
    ↓ (依賴)  
SpuCheckDomainServiceImpl 
    ↓ (依賴)
NewSpuApplyCheckServiceImpl

異常啟動的情況bean加載是從TimeoutNotifyController開始加載的,具體情況如下所示:

TimeoutNotifyController 
    ↓ (依賴)
SpuCheckDomainServiceImpl 
    ↓ (依賴)  
NewSpuApplyCheckServiceImpl 
    ↓ (依賴)
SpuCheckDomainServiceImpl

同一個依賴環,為什么從OldCenterSpuController 開始加載就可以正常啟動,而從TimeoutNotifyController 啟動就會啟動異常呢?下面我們會從現場debug的角度來分析解釋這個問題。

問題分析

在相關知識點簡介里面知悉到spring用三級緩存解決了循環依賴問題。為什么后臺服務admin啟動還會報循環依賴的問題呢?

要得到問題的答案,還是需要回到源碼本身,前面我們分析了spring的創建Bean的主要流程,這里為了更好的分析問題,補充下通過容器獲取Bean的。

在通過spring容器獲取bean時,底層統一會調用doGetBean方法,大體如下:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
       @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    
    final String beanName = transformedBeanName(name);
    Object bean;
    
    // 從三級緩存獲取bean
    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
       bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    }else {
     if (mbd.isSingleton()) {
       sharedInstance = getSingleton(beanName, () -> {
       try {
         //如果是單例Bean,從三級緩存沒有獲取到bean,則執行創建bean邏輯
          return createBean(beanName, mbd, args);
       }
       catch (BeansException ex) {
          destroySingleton(beanName);
          throw ex;
       }
    });
    bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
  }   
 }

從doGetBean方法邏輯看,在spring從一二三級緩存獲取bean返回空時,會調用createBean方法去場景bean,createBean方法底層主要是調用前面我們提到的創建Bean流程的doCreateBean方法。

注意:doGetBean方法里面getSingleton方法的邏輯是先從一級緩存拿,拿到為空并且bean在創建中則又從二級緩存拿,二級緩存拿到為空 并且當前容器允許有循環依賴則從三級緩存拿。并且將對象工廠移到二級緩存,刪除三級緩存

doCreateBean方法如下:

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
          throws BeanCreationException {
    if (mbd.isSingleton()) {
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    // Bean初始化第一步:默認調用無參構造實例化Bean
    // 如果是只有帶參數的構造方法,構造方法里的參數依賴注入,就是發生在這一步
    if (instanceWrapper == null) {
      instanceWrapper = createBeanInstance(beanName, mbd, args);
    }


    //判斷Bean是否需要提前暴露對象用來解決循環依賴,需要則啟動spring三級緩存
    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創建第二步:填充屬性(DI依賴注入發生在此步驟)
      populateBean(beanName, mbd, instanceWrapper);
      // bean創建第三步:調用初始化方法,完成bean的初始化操作(AOP的第三個入口)
      // AOP是通過自動代理創建器AbstractAutoProxyCreator的postProcessAfterInitialization()
//方法的執行進行代理對象的創建的,AbstractAutoProxyCreator是BeanPostProcessor接口的實現
      exposedObject = initializeBean(beanName, exposedObject, mbd);




   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);
          Set<String> 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 " +
                   "'getBeanNamesOfType' with the 'allowEagerInit' flag turned off, for example.");
          }
       }
    }
}


    } catch (Throwable ex) {
      // ...
    }
    // ...
    return exposedObject;
    }

將doGetBean和doCreateBean的邏輯轉換成流程圖如下:

圖片圖片

從流程圖可以看出,后臺服務admin啟動失敗拋出UnsatisfiedDependencyException異常的必要條件是存在循環依賴,因為不存在循環依賴的情況bean只會存在單次加載,單次加載的情況bean只會被放進spring的第三級緩存。

而觸發UnsatisfiedDependencyException異常的先決條件是需要spring的第一二級緩存有當前的bean。所以可以知道當前bean肯定存在循環依賴。在存在循環依賴的情況下,當前bean被第一次獲取(即調用doGetBean方法)會緩存進spring的第三級緩存,然后會注入當前bean的依賴(即調用populateBean方法),在當前bean所在依賴環內其他bean都不在一二級緩存的情況下,會觸發當前bean的第二次獲取(即調用doGetBean方法),由于第一次獲取已經將Bean放進了第三級緩存,spring會將Bean從第三級緩存移到二級緩存并刪除第三級緩存。

最終會回到第一次獲取的流程,調用初始化方法做初始化。最終在初始化有對當前bean做代理增強的并且提前暴露到二級緩存的對象有被其他依賴引用到,而且allowRawInjectinotallow=false的情況下,會導致拋出UnsatisfiedDependencyException,進而導致啟動異常。

注意:在注入當前bean的依賴時,這里spring將Bean從第三級緩存移到二級緩存并刪除第三級緩存后,當前bean的依賴的其他bean會從二級緩存拿到當前bean做依賴。這也是后續拋異常的先決條件

結合admin有時候啟動正常,有時候啟動異常的情況,這里猜測啟動正常和啟動異常時bean加載順序不一致,進而導致啟動正常時當前Bean只會被獲取一次,啟動異常時當前bean會被獲取兩次。為了驗證猜想,我們分別針對啟動異常和啟動正常的bean獲取做了debug。

debug分析

首先我們從啟動異常提取到以下關鍵信息,從這些信息可以知道是spuCheckDomainServiceImpl的加載觸發的啟動異常。所以我們這里以spuCheckDomainServiceImpl作為前面流程分析的當前bean。

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'timeoutNotifyController': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'spuCheckDomainServiceImpl': Bean with name 'spuCheckDomainServiceImpl' has been injected into other beans [...] 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.

然后提前我們在doCreateBean方法設置好spuCheckDomainServiceImpl加載時的條件斷點。我們先debug啟動異常的情況。最終斷點信息如下:

圖片圖片

從紅框1里面的兩個引用看,很明顯調initializeBean方法時spring有對spuCheckDomainServiceImpl做代理增強。導致initializeBean后返回的引用和提前暴露到二級緩存的引用是不一致的。這里spuCheckDomainServiceImpl有二級緩存是跟我們前面分析的吻合,是因為spuCheckDomainServiceImpl被獲取了兩次,即調了兩次doGetBean。

從紅框2里面的actualDependentBeans的set集合知道提前暴露到二級緩存的引用有被其他33個bean引用到,也是跟異常提示的bean列表保持一致的。

這里spuCheckDomainServiceImpl的加載為什么會調用兩次doGetBean方法呢?

從調用棧分析到該加載鏈如下:

TimeoutNotifyController  ->spuCheckDomainServiceImpl-> newSpuApplyCheckServiceImpl-> ... ->spuCheckDomainServiceImpl

TimeoutNotifyController注入依賴時第一次調用doGetBean獲取spuCheckDomainServiceImpl時,從一二三級緩存獲取不到,會調用doCreateBean方法創建spuCheckDomainServiceImpl。

首先會將spuDomainServiceImpl放進spring的第三級緩存,然后開始調populateBean方法注入依賴,由于在循環中間的newSpuApplyCheckServiceImpl是第一次獲取,一二三級緩存都獲取不到,會調用doCreateBean去創建對應的bean,然后會第二次調用doGetBean獲取spuCheckDomainServiceImpl,這時spuCheckDomainServiceImpl在第一次獲取已經將bean加載到第三級緩存,所以這次spring會將bean從第三級緩存直接移到第二級緩存,并將第三級緩存里面的spuCheckDomainServiceImpl對應的bean刪除,并直接返回二級緩存里面的bean,不會再調doCreateBean去創建spuCheckDomainServiceImpl。最終完成了循環中間的bean的初始化后(這里循環中間的bean初始化時依賴到的bean如果有引用到spuCheckDomainServiceImpl會調用doGetBean方法從二級緩存拿到spuCheckDomainServiceImpl提前暴露的引用),會回到第一次調用doGetBean獲取spuCheckDomainServiceImpl時調用的doCreateBean方法的流程。繼續調initializeBean方法完成初始化,然后將初始化完成的bean返回。最終拿初始化返回的bean引用跟二級緩存拿到的bean引用做對比,發現不一致,導致拋出UnsatisfiedDependencyException異常。

那么這里為什么spuCheckDomainServiceImpl調用initializeBean方法完成初始化后與提前暴露到二級緩存的bean會不一致呢?

看spuCheckDomainServiceImpl的代碼如下:

@Component
@Slf4j
@Validated
public class SpuCheckDomainServiceImpl {
    @Resource
    private NewSpuApplyCheckServiceImpl newSpuApplyCheckServiceImpl;
}

發現SpuCheckDomainServiceImpl類有使用到@Validated注解。查閱資料發現@Validated的實現是通過在initializeBean方法里面執行一個org.springframework.validation.beanvalidation.MethodValidationPostProcessor后置處理器實現的,MethodValidationPostProcessor會對SpuCheckDomainServiceImpl做一層代理。導致initializeBean方法返回的spuCheckDomainServiceImpl是一個新的代理對象,從而最終導致跟二級緩存的不一致。

debug視圖如下:

圖片圖片

那為什么有時候能啟動成功呢?什么情況下能啟動成功?

我們繼續debug啟動成功的情況。最終觀察到spuCheckDomainServiceImpl只會調用一次doGetBean,而且從一二級緩存拿到的spuCheckDomainServiceImpl提前暴露的引用為null,如下圖:

圖片圖片

這里為什么spuCheckDomainServiceImpl只會調用一次doGetBean呢?

首先我們根據調用棧整理到當前加載的引用棧:

oldCenterSpuController-> newSpuApplyCheckServiceImpl-> ... ->spuCheckDomainServiceImpl -> newSpuApplyCheckServiceImpl

根據前面啟動失敗的信息我們可以知道,spuCheckDomainServiceImpl處理依賴的環是:

spuCheckDomainServiceImpl ->newSpuApplyCommandServiceImpl-> ... ->spuCheckDomainServiceImpl

失敗的情況我們發現是從spuCheckDomainServiceImpl開始創建的,現在啟動正常的情況是從newSpuApplyCheckServiceImpl開始創建的。

創建 newSpuApplyCheckServiceImpl時,發現它依賴環中間這些bean會依次調用doCreateBean方法去創建對應的bean。

調用到spuCheckDomainServiceImpl時,由于是第一次獲取bean,也會調用doCreateBean方法創建bean,然后回到創建spuCheckDomainServiceImpl的doCreateBean流程,這里由于沒有將spuCheckDomainServiceImpl的三級緩存移到二級緩存,所以不會導致拋出UnsatisfiedDependencyException異常,最終回到newSpuApplyCheckServiceImpl的doCreateBean流程,由于newSpuApplyCheckServiceImpl在調用initializeBean方法沒有做代理增強,所以也不會導致拋出UnsatisfiedDependencyException異常。因此最后可以正常啟動。

這里我們會有疑問?類的創建順序由什么決定的呢?

通常不同環境下,代碼打包后的jar/war結構、@ComponentScan的basePackages配置細微差別,都可能導致Spring掃描和注冊Bean定義的順序不同。Java ClassLoader加載類的順序本身也有一定不確定性。如果Bean定義是通過不同的配置類引入的,配置類的加載順序會影響其中所定義Bean的注冊順序。

那是不是所有的類增強在有循環依賴時都會觸發UnsatisfiedDependencyException異常呢?

并不是,比如@Transactional就不會導致觸發UnsatisfiedDependencyException異常。讓我們深入分析原因。

核心區別在于代理創建時機不同。

@Transactional的代理時機如下:

// Spring 為 @Transactional 創建代理的流程1. 實例化原始 Bean
2. 放入三級緩存(ObjectFactory)
3. 當發生循環依賴時,調用 ObjectFactory.getObject()
4. 此時判斷是否需要事務代理,如果需要則提前創建代理
5. 將代理對象放入二級緩存,供其他 Bean 使用

@Validated的代理時機:

// @Validated 的代理創建在生命周期更晚的階段1. 實例化原始 Bean
2. 放入三級緩存(ObjectFactory)
3. 當發生循環依賴時,調用 ObjectFactory.getObject()
4.  ? 問題:此時 @Validated 的代理還未創建!
5. 其他 Bean 拿到的是原始對象,而不是異步代理對象

問題根源:@Transactional的代理增強是在三層緩存生成時觸發的, @Validated的增強是在初始化bean后通過后置處理器做的代理增強。

解決方案

短期方案

  • 移除SpuCheckDomainServiceImpl類上的Validated注解
  • @lazy 解耦

原理是發現有@lazy 注解的依賴為其生成代理類,依賴代理類,只有在真正需要用到對象時,再通過getBean的邏輯去獲取對象,從而實現了解耦。

長期方案

嚴格執行DDD代碼規范

這里是違反DDD分層規范導致的循環依賴。

梳理解決歷史依賴環

通過梳理修改代碼解決歷史存在的依賴環。我們內部實現了一個能檢測依賴環的工具,這里簡單介紹一下實現思路,詳情如下。

日常循環依賴環:實戰檢測工具類解析

在實際項目中,即使遵循了DDD分層規范和注入最佳實踐,仍有可能因業務復雜或團隊協作不充分而引入循環依賴。為了在開發階段盡早發現這類問題,我們可以借助自定義的循環依賴檢測工具類,在Spring容器啟動后自動分析并報告依賴環。

功能概述:

  • 條件啟用:通過配置circular.dependecy.analysis.enabled=true開啟檢測;
  • 依賴圖構建:掃描所有單例Bean,分析其構造函數、字段、方法注入及depends-on聲明的依賴;
  • 循環檢測算法:使用DFS遍歷依賴圖,識別所有循環依賴路徑;
  • 通知上報:檢測結果通過飛書機器人發送至指定接收人(targetId)。

簡潔代碼結構如下:

@Component
@ConditionalOnProperty(value = "circular.dependency.analysis.enabled", havingValue = "true")
public class TimingCircularDependencyHandler extends AbstractNotifyHandler<NotifyData>
    implements ApplicationContextAware, BeanFactoryAware {
    
    @Override
    public Boolean handler(NotifyData data) {
        dependencyGraph = new HashMap<>();
        handleContextRefresh(); // 觸發依賴圖構建與檢測
        return Boolean.TRUE;
    }
    
    private void buildDependencyGraph() {
        // 遍歷所有Bean,解析其依賴關系
        // 支持:構造器、字段、方法、depends-on
    }
    
    private void detectCircularDependencies() {
        // 使用DFS檢測環,記錄所有循環路徑
        // 輸出示例:循環依賴1: A -> B -> C -> A
    }
}

四、總結

循環依賴暴露了代碼結構的設計缺陷。理論上應通過分層和抽象來避免,但在復雜的業務交互中仍難以杜絕。雖然Spring利用三級緩存等機制默默解決了這一問題,使程序得以運行,但這絕不應是懈怠設計的借口。我們更應恪守設計原則,從源頭規避循環依賴,構建清晰、健康的架構。

責任編輯:武曉燕 來源: 得物技術
相關推薦

2024-04-12 07:51:05

SpringBean初始化

2023-11-29 12:12:24

Oceanbase數據庫

2011-02-22 09:29:23

jQueryJavaScript

2013-04-02 14:27:02

架構架構評審

2022-05-12 09:52:09

網絡架構HTTP跨域保護機制

2010-06-29 12:55:44

UML類圖依賴關系

2024-06-05 11:43:10

2013-12-23 09:38:11

2023-03-31 09:22:40

Hi3861芯片Flash

2011-06-28 10:41:50

DBA

2023-05-04 08:06:27

Spring循環依賴

2023-11-02 18:01:24

SpringMVC配置

2021-06-29 10:18:07

Kafka宕機系統

2024-04-15 08:17:21

Spring依賴注入循環依賴

2020-10-24 13:50:59

Python編程語言

2021-12-27 10:08:16

Python編程語言

2023-05-09 07:51:28

Spring循環依賴

2025-03-17 00:21:00

2020-02-10 15:50:18

Spring循環依賴Java

2019-11-26 14:30:20

Spring循環依賴Java
點贊
收藏

51CTO技術棧公眾號

欧美激情亚洲国产| 91九色02白丝porn| 精选一区二区三区四区五区| 销魂美女一区二区| 偷拍欧美精品| 亚洲成人av片| 麻豆三级在线观看| 不卡一本毛片| 国产精品无码永久免费888| 3d精品h动漫啪啪一区二区| 日韩久久中文字幕| 欧美精品成人| 久久夜色精品一区| 国产日韩av在线播放| 精品无码人妻一区二区三区品 | 精品999网站| 国产亚洲精品久久久久久| 色综合五月婷婷| 日韩电影免费观看高清完整版| 综合久久国产九一剧情麻豆| 久久久久久久久久久久久久一区| 国产又粗又猛又色又| 国产日韩欧美一区在线 | 粉嫩一区二区三区| 精品久久久中文字幕人妻| 国语精品一区| 自拍偷拍亚洲精品| 国产精品国模在线| 999在线免费观看视频| 日本特级黄色片| 欧美三区不卡| 久久综合免费视频| 精品亚洲aⅴ无码一区二区三区| 999精品视频在这里| 69久久夜色精品国产69蝌蚪网 | 久久av在线| 欧美黄色免费网站| 性欧美疯狂猛交69hd| 国产亚洲一区| 亚洲美女精品久久| 超碰男人的天堂| 哺乳挤奶一区二区三区免费看| 777午夜精品视频在线播放| 久久99爱视频| 精品福利在线| 91在线观看地址| 99re6热在线精品视频播放速度| 在线免费观看视频网站| 视频一区二区欧美| 奇米四色中文综合久久| 日本高清www免费视频| 欧美偷窥清纯综合图区| 日韩免费电影一区| 韩国三级hd中文字幕有哪些| 精品国产伦一区二区三区观看说明| 欧美日韩一区二区在线视频| 成人黄色一区二区| 色香欲www7777综合网| 日韩欧美精品在线观看| 男人日女人bb视频| 免费观看亚洲| 91黄色免费观看| av无码精品一区二区三区| 欧美色999| 欧美视频一区在线| 黄色三级中文字幕| 污片在线免费观看| 亚洲自拍另类综合| av日韩在线看| 成人黄色动漫| 在线视频综合导航| 不卡的av中文字幕| 国产一区一区| 亚洲第一视频网| 在线免费播放av| 国产区精品区| 久久的精品视频| 久久在线视频精品| 免费日韩av片| 国产精品美女午夜av| 国产精品国产三级国产aⅴ| 国产美女视频91| 久久99精品久久久久久秒播放器 | 日本韩国欧美在线观看| 日韩久久一区二区三区| 91精品中文字幕一区二区三区 | 国产亚洲精品超碰| 最新欧美日韩亚洲| 狼人综合视频| 欧美群妇大交群中文字幕| 熟女人妻一区二区三区免费看| 日韩欧美美女在线观看| 在线看日韩欧美| 午夜写真片福利电影网| 美女黄网久久| 91久久嫩草影院一区二区| 视频污在线观看| 国产精品丝袜91| 国产主播自拍av| 欧美成人家庭影院| 亚洲国产精品免费| 久久精品日韩无码| 国产亚洲在线| 999国内精品视频在线| 欧美精品久久久久久久久久丰满| 国产精品第五页| 男人天堂1024| 亚洲一区网址| 色老头一区二区三区在线观看| 国产在线精品观看| 加勒比av一区二区| 欧美一区二区综合| 国产色婷婷在线| 欧美三区免费完整视频在线观看| 少妇被狂c下部羞羞漫画| 久久社区一区| 日韩免费av片在线观看| 国精产品乱码一区一区三区四区| 中文字幕一区二区三区色视频| 亚洲熟妇av日韩熟妇在线| 91麻豆精品国产综合久久久| 亚洲色图15p| 日韩伦理在线视频| 国产麻豆成人精品| 一本色道久久99精品综合| 欧美片第一页| 日韩av在线网页| 国产在线视频在线观看| 国内精品久久久久影院色| 日韩在线导航| 日韩欧美精品电影| 精品一区二区三区四区在线| 久青草免费视频| 国产一区欧美一区| 国产又大又长又粗又黄| 巨胸喷奶水www久久久| 亚洲欧美国内爽妇网| 日韩欧美a级片| 大桥未久av一区二区三区中文| 久久99国产精品一区| 24小时成人在线视频| 最近中文字幕mv在线一区二区三区四区| 精品国产xxx| 国产网站一区二区三区| 欧美精品第三页| 你懂的视频欧美| 日韩av电影院| 国产在线观看免费| 在线中文字幕一区二区| 影音先锋制服丝袜| 免费高清在线视频一区·| 亚洲mv在线看| 日本视频在线观看| 在线观看日韩av先锋影音电影院| 好吊视频在线观看| 爽好多水快深点欧美视频| 日本不卡一区二区三区在线观看| 午夜av成人| 综合久久五月天| 一区二区的视频| 亚洲精品写真福利| 精品无码av一区二区三区| 亚洲欧洲视频| 欧美极品色图| 九九九精品视频| 另类天堂视频在线观看| 丰满少妇一级片| 丰满岳妇乱一区二区三区| 法国伦理少妇愉情| 免费高清在线一区| 黄频视频在线观看| 欧美经典一区| 欧美亚州一区二区三区| 超碰在线国产| 欧美一二三区在线观看| 国产污视频在线看| 国产丝袜欧美中文另类| 亚洲精品永久视频| 国内精品久久久久久久影视蜜臀 | 欧美日韩人妻精品一区在线| 在线综合欧美| 伊人精品久久久久7777| av不卡一区| 国产精品久久久av| 五月婷婷视频在线观看| 亚洲免费视频一区二区| 91麻豆国产在线| 亚洲成a人v欧美综合天堂| 亚洲天堂岛国片| 成人国产视频在线观看| av网站在线不卡| 好看的av在线不卡观看| 色婷婷精品国产一区二区三区| 日本精品在线观看| 国产成人久久久| 天堂亚洲精品| 在线视频欧美日韩精品| 亚洲精品国产精| 欧美三级蜜桃2在线观看| 国产一级在线播放| 国产精品久久久久久久蜜臀 | 午夜精品久久久内射近拍高清 | 欧美激情视频播放| www.在线视频.com| 亚洲国产黄色片| 国产精选久久久| 色欧美88888久久久久久影院| 欧美色图亚洲视频| 国产日韩欧美高清在线| 亚洲精品激情视频| 精品一区二区在线视频| 成人av一级片| 亚洲天堂久久| 国产免费xxx| 日本一区二区高清不卡| 久久综合九色99| 国产无遮挡裸体免费久久| 国产中文字幕91| 外国电影一区二区| 5566日本婷婷色中文字幕97| 日本中文字幕中出在线| 日韩在线视频观看| 国产三级在线看| 日韩精品在线播放| 内射后入在线观看一区| 欧美一级夜夜爽| 国产精品久久久久久久一区二区| 91久久奴性调教| 无码人妻黑人中文字幕| 欧美日韩国产精品专区| 国产成人啪精品午夜在线观看| 亚洲免费观看高清完整版在线| 日韩av网站在线播放| 国产亚洲人成网站| 91视频免费观看网站| 91免费小视频| 短视频在线观看| 99久久免费视频.com| 91丨porny丨对白| 成人午夜电影久久影院| 精品无码人妻少妇久久久久久| 国产精品66部| 中文字幕在线观看视频www| 国产在线不卡一区| 欧美xxxxxbbbbb| 国产精品一区二区x88av| 一级黄色录像在线观看| 精品影视av免费| 永久免费黄色片| 国产一区二区在线免费观看| 国产免费中文字幕| 国产一区二区三区综合| 韩国三级与黑人| 成人免费高清在线| 国产精品久久久久久久无码| 99这里只有精品| 素人fc2av清纯18岁| 久久亚洲捆绑美女| www.黄色在线| 中文字幕在线一区免费| 久久久久久久久久久久久女过产乱| 亚洲男人的天堂av| 黄页网站免费观看| 亚洲444eee在线观看| 国产一级淫片a视频免费观看| 日本丶国产丶欧美色综合| 欧美视频xxxx| 欧美一级二级三级乱码| 五月婷在线视频| 亚洲最新中文字幕| av电影免费在线观看| 91超碰中文字幕久久精品| 日韩影片中文字幕| 91精品在线国产| 久久影视三级福利片| 日本一区二区在线| 亚洲91视频| 3d动漫一区二区三区| 日韩国产高清在线| 亚洲丝袜在线观看| 91蜜桃免费观看视频| 亚洲综合图片一区| 亚洲一级电影视频| 日韩av免费播放| 日韩美女一区二区三区四区| 四虎影院在线播放| 67194成人在线观看| 精品二区在线观看| 亚洲欧美制服另类日韩| 国产黄色小视频在线| 欧美亚洲国产视频| japansex久久高清精品| 免费在线成人av电影| 久久久久久久久久久9不雅视频| 国产a级片网站| 精品一区二区三区免费视频| 丰满大乳奶做爰ⅹxx视频| 中文字幕一区av| 久久久久99精品成人片三人毛片| 欧美理论电影在线| 三级视频在线| 欧美福利视频在线观看| 国产精品久久亚洲不卡| 国产伦理久久久| 91日韩欧美| 激情内射人妻1区2区3区| 国产91对白在线观看九色| 天天操天天干天天操天天干| 亚洲午夜久久久久| 一级全黄少妇性色生活片| 亚洲美女在线观看| 女同一区二区免费aⅴ| 成人激情视频网| 国产一区网站| 精品人妻一区二区三区四区在线 | 无码人妻aⅴ一区二区三区 | 久久精品视频一| 日韩一区二区三区免费| 精品国产综合区久久久久久| 综合在线视频| 91女神在线观看| 国产欧美视频一区二区| 国产一级精品视频| 精品剧情在线观看| 2020国产在线视频| 亚洲jizzjizz日本少妇| 色乱码一区二区三区网站| 国产成人欧美日韩在线电影| 久久久久麻豆v国产精华液好用吗| 亚洲视频在线一区| 中文天堂在线播放| 亚洲香蕉av在线一区二区三区| 高清毛片在线观看| 粉嫩av四季av绯色av第一区| 亚洲国产精品成人| 日韩在线一区视频| 国产精品乱码人人做人人爱| 日韩精选在线观看| 亚洲欧洲在线免费| 日韩欧美另类一区二区| 久久久久九九九| 亚洲一区二区毛片| 久久久久亚洲av无码专区桃色| 色窝窝无码一区二区三区成人网站| 97公开免费视频| 欧美日韩国产一区二区三区不卡 | 欧美特黄视频| 日本美女久久久| 亚洲人xxxx| 99热这里只有精| 欧美精品videos| 欧美性生活一级片| 37pao成人国产永久免费视频| 久久青草国产手机看片福利盒子| 波多野结衣视频网站| 国产视频精品va久久久久久| 在线黄色的网站| 日韩美女一区| 精品一区二区三区在线观看国产 | 欧美视频在线观看 亚洲欧| 殴美一级特黄aaaaaa| 97在线视频免费播放| 要久久爱电视剧全集完整观看| 北条麻妃在线一区| 国产精品毛片无遮挡高清| 91亚洲国产成人精品一区| 久久在线精品视频| 91午夜精品| 一本大道熟女人妻中文字幕在线| 国产午夜精品理论片a级大结局| 中文字幕日韩国产| 乱亲女秽乱长久久久| 国产精品调教| 97公开免费视频| 亚洲乱码日产精品bd| 丰满人妻一区二区三区免费| 成人免费av网站| 亚欧洲精品在线视频| 亚洲午夜精品久久久久久久久久久久| 福利一区二区三区视频在线观看 | 四虎永久在线精品| 亚洲欧美激情在线视频| 成人综合日日夜夜| 欧美在线一区视频| 欧美国产一区视频在线观看| av在线亚洲天堂| 日韩美女免费视频| 欧美在线资源| 一区二区三区伦理片| 欧美一区二区三区视频免费| 小早川怜子影音先锋在线观看| 亚洲精品一区二区三区樱花| 成人午夜伦理影院| 中文字幕第三页| 国内精品一区二区三区四区| 久久国产成人精品| 少妇激情一区二区三区视频| 欧美日韩另类一区| 国产在线88av| a级黄色片网站|