日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當前位置:首頁 > 科技  > 軟件

透過源碼,捋清楚循環依賴到底是如何解決的!

來源: 責編: 時間:2023-08-09 23:01:59 422觀看
導讀以下內容基于 Spring6.0.4。關于 Spring 循環依賴,松哥已經連著發了三篇文章了,本篇文章松哥從源碼的角度來和小伙伴們捋一捋 Spring 循環依賴到底是如何解決了。小伙伴們一定要先熟悉前面文章的內容,否則今天的源碼可能

以下內容基于 Spring6.0.4。MQf28資訊網——每日最新資訊28at.com

關于 Spring 循環依賴,松哥已經連著發了三篇文章了,本篇文章松哥從源碼的角度來和小伙伴們捋一捋 Spring 循環依賴到底是如何解決了。MQf28資訊網——每日最新資訊28at.com

小伙伴們一定要先熟悉前面文章的內容,否則今天的源碼可能會看起來有些吃力。MQf28資訊網——每日最新資訊28at.com

接下來我通過一個簡單的循環依賴的案例,來和大家梳理一下完整的 Bean 循環依賴處理流程。MQf28資訊網——每日最新資訊28at.com

1. 案例設計

假設我有如下 Bean:MQf28資訊網——每日最新資訊28at.com

@Servicepublic class A {    @Autowired    B b;}@Servicepublic class B {    @Autowired    A a;}

就這樣一個簡單的循環依賴,默認情況下,A 會被先加載,然后在 A 中做屬性填充的時候,去創建了 B,創建 B 的時候又需要 A,就會從緩存中拿到 A,大致流程如此,接下來我們結合源碼來驗證一下這個流程。MQf28資訊網——每日最新資訊28at.com

2. 源碼分析

首先我們來看獲取 Bean 的時候,如何利用這三級緩存。MQf28資訊網——每日最新資訊28at.com

小伙伴們知道,獲取 Bean 涉及到的就是 getBean 方法,像我們上面這個案例,由于都是單例的形式,所以 Bean 的初始化其實在容器創建的時候就完成了。MQf28資訊網——每日最新資訊28at.com

圖片圖片MQf28資訊網——每日最新資訊28at.com

在 preInstantiateSingletons 方法中,又調用到 AbstractBeanFactory#getBean 方法,進而調用到 AbstractBeanFactory#doGetBean 方法。MQf28資訊網——每日最新資訊28at.com

圖片圖片MQf28資訊網——每日最新資訊28at.com

Bean 的初始化就是從這里開始的,我們就從這里來開始看起吧。MQf28資訊網——每日最新資訊28at.com

2.1 doGetBean

AbstractBeanFactory#doGetBean(方法較長,節選部分關鍵內容):MQf28資訊網——每日最新資訊28at.com

protected <T> T doGetBean(  String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)  throws BeansException { String beanName = transformedBeanName(name); Object beanInstance; // Eagerly check singleton cache for manually registered singletons. Object sharedInstance = getSingleton(beanName); if (sharedInstance != null && args == null) {  beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null); } else {  // Fail if we're already creating this bean instance:  // We're assumably within a circular reference.  if (isPrototypeCurrentlyInCreation(beanName)) {   throw new BeanCurrentlyInCreationException(beanName);  }  // Check if bean definition exists in this factory.  BeanFactory parentBeanFactory = getParentBeanFactory();  if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {   // Not found -> check parent.   String nameToLookup = originalBeanName(name);   if (parentBeanFactory instanceof AbstractBeanFactory abf) {    return abf.doGetBean(nameToLookup, requiredType, args, typeCheckOnly);   }   else if (args != null) {    // Delegation to parent with explicit args.    return (T) parentBeanFactory.getBean(nameToLookup, args);   }   else if (requiredType != null) {    // No args -> delegate to standard getBean method.    return parentBeanFactory.getBean(nameToLookup, requiredType);   }   else {    return (T) parentBeanFactory.getBean(nameToLookup);   }  }  if (!typeCheckOnly) {   markBeanAsCreated(beanName);  }  StartupStep beanCreation = this.applicationStartup.start("spring.beans.instantiate")    .tag("beanName", name);  try {   if (requiredType != null) {    beanCreation.tag("beanType", requiredType::toString);   }   RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);   checkMergedBeanDefinition(mbd, beanName, args);   // Guarantee initialization of beans that the current bean depends on.   String[] dependsOn = mbd.getDependsOn();   if (dependsOn != null) {    for (String dep : dependsOn) {     if (isDependent(beanName, dep)) {      throw new BeanCreationException(mbd.getResourceDescription(), beanName,        "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");     }     registerDependentBean(dep, beanName);     try {      getBean(dep);     }     catch (NoSuchBeanDefinitionException ex) {      throw new BeanCreationException(mbd.getResourceDescription(), beanName,        "'" + beanName + "' depends on missing bean '" + dep + "'", ex);     }    }   }   // Create bean instance.   if (mbd.isSingleton()) {    sharedInstance = getSingleton(beanName, () -> {     try {      return createBean(beanName, mbd, args);     }     catch (BeansException ex) {      // Explicitly remove instance from singleton cache: It might have been put there      // eagerly by the creation process, to allow for circular reference resolution.      // Also remove any beans that received a temporary reference to the bean.      destroySingleton(beanName);      throw ex;     }    });    beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);   }  } } return adaptBeanInstance(name, beanInstance, requiredType);}

這個方法比較長,我來和大家說幾個關鍵的點:MQf28資訊網——每日最新資訊28at.com

  1. 首先這個方法一開始就調用了 getSingleton 方法,這個是嘗試從三級緩存中獲取到想要的 Bean,但是,當我們第一次初始化 A 的時候,很顯然這一步是無法獲取到 A 的實例的,所以這一步會返回 null。
  2. 如果第一步拿到了 Bean,那么接下來就進入到 if 分支中,直接獲取到想要的 beanInstance 實例;否則進入到第三步。
  3. 如果第一步沒有從三級緩存中拿到 Bean,那么接下來就要檢查是否是循環依賴了,首先調用 isPrototypeCurrentlyInCreation 方法判斷當前 Bean 是否已經在創建了,如果已經在創建了,那么顯然要拋異常出去了(BeanCurrentlyInCreationException)。接下來就去 parent 容器中各種查找,看能否找到需要的 Bean,Spring 中的父子容器問題松哥在之前的文章中也已經講過了,小伙伴們可以參考:Spring 中的父子容器是咋回事?。
  4. 如果從父容器中也沒找到 Bean,那么接下來就會調用 markBeanAsCreated 方法來標記當前 Bean 已經創建或者正準備創建。
  5. 接下來會去標記一下創建步驟,同時檢查一下 Bean 的 dependsOn 屬性是否存在循環關系,這些跟我們本文關系都不大,我就不去展開了。
  6. 關鍵點來了,接下來判斷如果我們當前 Bean 是單例的,那么就調用 getSingleton 方法去獲取一個實例,該方法的第二個參數一個 Lambda 表達式,表達式的核心內容就是調用 createBean 方法去創建一個 Bean 實例,該方法將不負眾望,拿到最終想要的 Bean。

以上就是 doGetBean 方法中幾個比較重要的點。MQf28資訊網——每日最新資訊28at.com

其中有兩個方法我們需要展開講一下,第一個方法就是去三級緩存中查詢 Bean 的 getSingleton 方法(步驟一),第二個方法則是去獲取到 Bean 實例的 getSingleton 方法(步驟六),這是兩個重載方法。MQf28資訊網——每日最新資訊28at.com

接下來我們就來分析一下這兩個方法。MQf28資訊網——每日最新資訊28at.com

2.2 查詢三級緩存

DefaultSingletonBeanRegistry#getSingleton:MQf28資訊網——每日最新資訊28at.com

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);  if (singletonObject == null && allowEarlyReference) {   synchronized (this.singletonObjects) {    // Consistent creation of early reference within full singleton lock    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;}
  • 首先去 singletonObjects 中查找,這就是所謂的一級緩存,如果這里能直接找到想要的對象,那么直接返回即可。
  • 如果一級緩存中不存在想要的 Bean,那么接下來就該去二級緩存 earlySingletonObjects 中查找了,二級緩存要是有我們想要的 Bean,那么也是直接返回即可。
  • 二級緩存中如果也不存在,那么就是加鎖然后去三級緩存中查找了,三級緩存是 singletonFactories,我們從 singletonFactories 中獲取到的是一個 ObjectFactory 對象,這是一個 Lambda 表達式,調用這里的 getObject 方法最終有可能會促成提前 AOP,至于這個 Lambda 表達式的內容,松哥在前面的文章中已經和小伙伴們介紹過了,這里先不啰嗦(如何通過三級緩存解決 Spring 循環依賴)。
  • 如果走到三級緩存這一步了,從三級緩存中拿到了想要的數據,那么就把數據存入到二級緩存 earlySingletonObjects 中,以備下次使用。同時,移除三級緩存中對應的數據。

當我們第一次創建 A 對象的時候,很顯然三級緩存中都不可能有數據,所以這個方法最終返回 null。MQf28資訊網——每日最新資訊28at.com

2.3 獲取 Bean 實例

接下來看 2.1 小節步驟六的獲取 Bean 的方法。MQf28資訊網——每日最新資訊28at.com

DefaultSingletonBeanRegistry#getSingleton(方法較長,節選部分關鍵內容):MQf28資訊網——每日最新資訊28at.com

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) { synchronized (this.singletonObjects) {  Object singletonObject = this.singletonObjects.get(beanName);  if (singletonObject == null) {   if (this.singletonsCurrentlyInDestruction) {    throw new BeanCreationNotAllowedException(beanName,      "Singleton bean creation not allowed while singletons of this factory are in destruction " +      "(Do not request a bean from a BeanFactory in a destroy method implementation!)");   }   beforeSingletonCreation(beanName);   boolean newSingleton = false;   boolean recordSuppressedExceptions = (this.suppressedExceptions == null);   if (recordSuppressedExceptions) {    this.suppressedExceptions = new LinkedHashSet<>();   }   try {    singletonObject = singletonFactory.getObject();    newSingleton = true;   }   if (newSingleton) {    addSingleton(beanName, singletonObject);   }  }  return singletonObject; }}
  1. 這個方法首先也是嘗試從一級緩存中獲取到想要的 Bean,如果 Bean 為 null,就開始施法了。
  2. 首先會去判斷一下,如果這個工廠的單例正在銷毀,那么這個 Bean 的創建就不被允許。
  3. 接下來會有一堆準備工作,關鍵點在 singletonFactory.getObject(); 地方,這個就是方法第二個參數傳進來的回調函數,將來在回調函數中,會調用到 createBean 方法,真正開始 A 這個 Bean 的創建。將 A 對象創建成功之后,會把 newSingleton 設置為 true,第 4 步會用到。
  4. 現在調用 addSingleton 方法,把創建成功的 Bean 添加到緩存中。

我們來看下 addSingleton 方法:MQf28資訊網——每日最新資訊28at.com

protected void addSingleton(String beanName, Object singletonObject) { synchronized (this.singletonObjects) {  this.singletonObjects.put(beanName, singletonObject);  this.singletonFactories.remove(beanName);  this.earlySingletonObjects.remove(beanName);  this.registeredSingletons.add(beanName); }}

小伙伴們看一下,一級緩存中存入 Bean,二級緩存和三級緩存移除該 Bean,同時在 registeredSingletons 集合中記錄一下當前 Bean 已經創建。MQf28資訊網——每日最新資訊28at.com

所以現在的重點其實又回到了 createBean 方法了。MQf28資訊網——每日最新資訊28at.com

2.4 createBean

createBean 方法其實就到了 Bean 的創建流程了。bean 的創建流程在前面幾篇 Spring 源碼相關的文章中也都有所涉獵,所以今天我就光說一些跟本文主題相關的幾個點。MQf28資訊網——每日最新資訊28at.com

createBean 方法最終會調用到 AbstractAutowireCapableBeanFactory#doCreateBean 方法,這個方法也是比較長的,而我是關心如下幾個地方:MQf28資訊網——每日最新資訊28at.com

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)  throws BeanCreationException { // Eagerly cache singletons to be able to resolve circular references // even when triggered by lifecycle interfaces like BeanFactoryAware. boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&   isSingletonCurrentlyInCreation(beanName)); if (earlySingletonExposure) {  addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean)); } // Initialize the bean instance. Object exposedObject = bean; try {  populateBean(beanName, mbd, instanceWrapper);  exposedObject = initializeBean(beanName, exposedObject, mbd); } return exposedObject;}

這里我比較在意的有兩個地方,一個是調用 addSingletonFactory 方法向三級緩存中添加回調函數,回調函數是 getEarlyBeanReference,如果有需要,將來會通過這個回調提前進行 AOP,即使沒有 AOP,就是普通的循環依賴,三級緩存也是會被調用的,這個大家繼續往后看就知道了,另外還有一個比較重要的地方,在本方法一開始的時候,就已經創建出來 A 對象了,這個時候的 A 對象是一個原始 Bean,即單純的只是通過反射把對象創建出來了,Bean 還沒有經歷過完整的生命周期,這里 getEarlyBeanReference 方法的第三個參數就是該 Bean,這個也非常重要,牢記,后面會用到。MQf28資訊網——每日最新資訊28at.com

第二個地方就是 populateBean 方法,當執行到這個方法的時候,A 對象已經創建出來了,這個方法是給 A 對象填充屬性用的,因為接下來要注入 B 對象,就在這個方法中完成的。MQf28資訊網——每日最新資訊28at.com

由于我們第 1 小節是通過 @Autowired 來注入 Bean 的,所以現在在 populateBean 方法也主要是處理 @Autowired 注入的情況,那么這個松哥之前寫過文章,小伙伴們參考@Autowired 到底是怎么把變量注入進來的?,具體的注入細節我這里就不重復了,單說在注入的過程中,會經過一個 DefaultListableBeanFactory#doResolveDependency 方法,這個方法就是用來解析 B 對象的(至于如何到達 doResolveDependency 方法的,小伙伴們參考 @Autowired 到底是怎么把變量注入進來的?一文)。MQf28資訊網——每日最新資訊28at.com

doResolveDependency 方法也是比較長,我這里貼出來和本文相關的幾個關鍵地方:MQf28資訊網——每日最新資訊28at.com

@Nullablepublic Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,  @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {     //...  Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);  if (matchingBeans.isEmpty()) {   if (isRequired(descriptor)) {    raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);   }   return null;  }  String autowiredBeanName;  Object instanceCandidate;  if (matchingBeans.size() > 1) {   autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);   if (autowiredBeanName == null) {    if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {     return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);    }    else {     // In case of an optional Collection/Map, silently ignore a non-unique case:     // possibly it was meant to be an empty collection of multiple regular beans     // (before 4.3 in particular when we didn't even look for collection beans).     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);  }  if (instanceCandidate instanceof Class) {   instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);  }        //...}
  1. 在這個方法中,首先調用 findAutowireCandidates 方法,以類型為依據,找到所有滿足條件的 Class 并組成一個 Map 返回。例如第一小節的案例,這里就會找到所有 B 類型的 Class,通過一個 Map 返回。
  2. 如果第一步返回的 Map 存在多條記錄,那么就必須從中挑選一個出來,這就是 matchingBeans.size() > 1 的情況。
  3. 如果第一步返回的 Map 只有一條記錄,那么就從 Map 中提取出來 key 和 value,此時的 value 是一個 Class,所以接下來還要調用 descriptor.resolveCandidate 去完成 Class 到對象的轉變。

而 descriptor.resolveCandidate 方法又開啟了新一輪的 Bean 初始化,只不過這次初始化的 B 對象,如下:MQf28資訊網——每日最新資訊28at.com

public Object resolveCandidate(String beanName, Class<?> requiredType, BeanFactory beanFactory)  throws BeansException { return beanFactory.getBean(beanName);}

2.5 后續流程

后續流程其實就是上面的步驟,我就直接來跟大家說一說,就不貼代碼了。MQf28資訊網——每日最新資訊28at.com

現在系統調用 beanFactory.getBean 方法去查找 B 對象,結果又是走一遍本文第二小節的所有流程,當 B 創建出來之后,也要去做屬性填充,此時需要在 B 中注入 A,那么又來到本文的 2.4 小節,最終又是調用到 resolveCandidate 方法去獲取 A 對象。MQf28資訊網——每日最新資訊28at.com

此時,在獲取 A 對象的過程中,又會調用到 doGetBean 這個方法,在這個方法中調用 getSingleton 的時候(2.1 小節的第一步),這個時候的執行邏輯就跟前面不一樣了,我們再來看下這個方法的源碼:MQf28資訊網——每日最新資訊28at.com

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);  if (singletonObject == null && allowEarlyReference) {   synchronized (this.singletonObjects) {    // Consistent creation of early reference within full singleton lock    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;}

現在還是嘗試從三級緩存中獲取 A,此時一二級緩存中還是沒有 A,但是三級緩存中有一個回調函數,當執行 singletonFactory.getObject() 方法的時候,就會觸發該回調函數,這個回調函數就是我們前面 2.4 小節提到的 getEarlyBeanReference 方法,我們現在來看下這個方法:MQf28資訊網——每日最新資訊28at.com

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;}

這個方法有一個參數 Bean,這個參數 Bean 會經過一些后置處理器處理之后返回,后置處理器主要是看一下這個 Bean 是否需要 AOP,如果需要就進行 AOP 處理,如果不需要,直接就把這個參數 Bean 返回就行了。至于這個參數是哪來的,我在 2.4 小節中已經加黑標記出來了,這個參數 Bean 其實就是原始的 A 對象!MQf28資訊網——每日最新資訊28at.com

好了,現在 B 對象就從緩存池中拿到了原始的 A 對象,B 對象屬性注入完畢,對象創建成功,進而導致 A 對象也創建成功。MQf28資訊網——每日最新資訊28at.com

大功告成。MQf28資訊網——每日最新資訊28at.com

3. 小結

老實說,如果小伙伴們認認真真看過松哥最近發的 Spring 源碼文章,今天的內容很好懂~至此,Spring 循環依賴,從思路到源碼,都和大家分析完畢了~感興趣的小伙伴可以 DEBUG 走一遍哦~MQf28資訊網——每日最新資訊28at.com

本文鏈接:http://m.www897cc.com/showinfo-26-5097-0.html透過源碼,捋清楚循環依賴到底是如何解決的!

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: 上下文1.6萬token的編程大模型來了!與Stable Diffusion出自同門,一次吃5個Python文件不費勁

下一篇: 微軟發布.NET 8 最終預覽版,正式版計劃 11 月 14 日發布

標簽:
  • 熱門焦點
  • 一加Ace2 Pro真機揭曉 鈦空灰配色質感拉滿

    終于,在經過了幾波預熱之后,一加Ace2 Pro的外觀真機圖在網上出現了。還是博主數碼閑聊站曝光的,這次的外觀設計還是延續了一加11的方案,只是細節上有了調整,例如新加入了鈦空灰
  • 一篇聊聊Go錯誤封裝機制

    %w 是用于錯誤包裝(Error Wrapping)的格式化動詞。它是用于 fmt.Errorf 和 fmt.Sprintf 函數中的一個特殊格式化動詞,用于將一個錯誤(或其他可打印的值)包裝在一個新的錯誤中。使
  • 虛擬鍵盤 API 的妙用

    你是否在遇到過這樣的問題:移動設備上有一個固定元素,當激活虛擬鍵盤時,該元素被隱藏在了鍵盤下方?多年來,這一直是 Web 上的默認行為,在本文中,我們將探討這個問題、為什么會發生
  • 阿里大調整

    來源:產品劉有媒體報道稱,近期淘寶天貓集團啟動了近年來最大的人力制度改革,涉及員工績效、層級體系等多個核心事項,目前已形成一個初步的&ldquo;征求意見版&rdquo;:1、取消P序列
  • 馮提莫簽約抖音公會 前“斗魚一姐”消失在直播間

    來源:直播觀察提起&ldquo;馮提莫&rdquo;這個名字,很多網友或許聽過,但應該不記得她是哪位主播了。其實,作為曾經的&ldquo;斗魚一姐&rdquo;,馮提莫在游戲直播的年代影響力不輸于現
  • 華為發布HarmonyOS 4:更好玩、更流暢、更安全

    在8月4日的華為開發者大會2023(HDC.Together)大會上,HarmonyOS 4正式發布。自2019年發布以來,HarmonyOS一直以用戶為中心,經歷四年多的發展HarmonyOS已
  • 榮耀Magicbook V 14 2021曙光藍版本正式開售,擁有觸摸屏

    榮耀 Magicbook V 14 2021 曙光藍版本正式開售,搭載 i7-11390H 處理器與 MX450 顯卡,配備 16GB 內存與 512GB SSD,重 1.48kg,厚 14.5mm,具有 1.5mm 鍵盤鍵程、
  • onebot M24巧系列一體機采用輕薄機身設計,現已在各平臺開售

    onebot M24 巧系列一體機目前已在線上線下各平臺同步開售。onebot M24 巧系列采用一體化輕薄機身設計,最薄處為 10.15mm,擁有寶石紅、午夜藍、石墨綠、雅致
  • 2022爆款:ROG魔霸6 冰川散熱系統持續護航

    喜逢開學季,各大商家開始推出自己的新產品,進行打折促銷活動。對于忠實的端游愛好者來說,能夠擁有一款夢寐以求的筆記本電腦是一件十分開心的事。但是現在的
Top 日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不
亚洲精品1区| 在线不卡欧美| 国产精品久久久久9999高清| 国产精品网站视频| 国产亚洲成av人片在线观看桃| 国产一区二区三区观看| 亚洲第一黄色网| 亚洲精品美女91| 亚洲欧美网站| 蜜臀久久99精品久久久久久9| 欧美日本高清| 国产美女精品视频| 亚洲狠狠婷婷| 午夜久久久久久| 欧美电影免费观看大全| 国产精品h在线观看| 国产一二三精品| 亚洲精品欧美专区| 欧美一区二区福利在线| 欧美精品一区二区三区很污很色的| 国产精品免费久久久久久| 亚洲成人在线网站| 亚洲专区免费| 欧美国产成人精品| 国产日韩在线视频| 一区二区免费在线观看| 久久免费视频在线观看| 欧美午夜精品理论片a级按摩| 国精产品99永久一区一区| 日韩视频―中文字幕| 久久九九精品99国产精品| 欧美日韩在线视频一区二区| 伊人婷婷欧美激情| 亚洲免费视频成人| 欧美激情一区二区在线| 国产一区视频在线观看免费| 一卡二卡3卡四卡高清精品视频| 久久久久www| 国产精品家教| 日韩视频一区二区三区在线播放免费观看| 欧美一区二区三区精品| 欧美日韩国产影院| 亚洲激情自拍| 久久夜色精品国产欧美乱| 国产精品制服诱惑| 中文精品视频| 欧美另类亚洲| 91久久精品一区| 亚洲福利视频网| 亚洲在线中文字幕| 欧美日韩国产va另类| 亚洲国产精品一区二区www| 欧美怡红院视频| 国产精品国产三级国产普通话99| 亚洲精品久久久久| 免费短视频成人日韩| 激情久久久久久久| 欧美中文在线字幕| 国产九区一区在线| 亚洲女人小视频在线观看| 欧美日韩直播| 日韩一级视频免费观看在线| 欧美xx69| 亚洲国产欧美日韩精品| 看片网站欧美日韩| 好男人免费精品视频| 欧美在线观看一区| 国产免费成人在线视频| 亚洲免费在线电影| 国产精品美女一区二区| 亚洲天堂激情| 国产精品久久久久一区二区| 亚洲一级电影| 国产精品理论片| 亚洲在线一区二区三区| 欧美系列电影免费观看| 亚洲视频第一页| 欧美性做爰猛烈叫床潮| 亚洲午夜成aⅴ人片| 国产精品xvideos88| 亚洲女女女同性video| 国产伦精品一区二区三区在线观看| 亚洲女优在线| 国产嫩草一区二区三区在线观看| 亚洲女性裸体视频| 国产精品午夜电影| 午夜在线一区二区| 国内成+人亚洲+欧美+综合在线| 久久精品国产综合| 伊人久久婷婷| 欧美电影免费观看大全| 亚洲精品一区二区三区蜜桃久| 欧美精品成人一区二区在线观看 | 久久精品国产免费看久久精品| 国产亚洲观看| 久久久在线视频| 影音先锋中文字幕一区二区| 美女视频黄 久久| 亚洲人成在线观看一区二区| 欧美日韩国产精品一卡| 亚洲欧美美女| 国模私拍一区二区三区| 乱码第一页成人| 亚洲精品美女在线观看播放| 亚洲欧美成人综合| 国产精品视频一二三| 欧美在线观看视频一区二区| 在线成人激情| 欧美日韩网站| 香蕉av777xxx色综合一区| 极品少妇一区二区三区精品视频| 欧美成年人视频网站| 在线中文字幕不卡| 国产视频一区欧美| 免费亚洲网站| 亚洲午夜高清视频| 韩国精品一区二区三区| 欧美精品二区三区四区免费看视频| 亚洲无毛电影| 国内在线观看一区二区三区| 欧美福利影院| 午夜精品一区二区三区在线播放| 激情久久综合| 欧美日本不卡| 欧美中文在线观看| 亚洲免费激情| 国产日韩一区| 欧美另类一区| 久久成人免费日本黄色| 99re6这里只有精品| 国内精品免费在线观看| 欧美日韩精选| 久久免费黄色| 亚洲一区二区三区久久| 亚洲第一中文字幕在线观看| 欧美性一二三区| 久久亚洲国产成人| 亚洲一区欧美激情| 亚洲风情亚aⅴ在线发布| 国产精品久久久亚洲一区| 欧美sm视频| 欧美一区二区精品| 日韩午夜激情| 黄色av日韩| 国产精品久久久久免费a∨大胸| 免费亚洲一区二区| 亚洲欧美日韩视频一区| 亚洲欧洲一区二区三区久久| 国产午夜一区二区三区| 欧美日韩视频在线一区二区| 久久亚裔精品欧美| 亚洲欧美日韩精品| 亚洲欧洲三级电影| 国模吧视频一区| 国产精品福利久久久| 欧美国产国产综合| 久久久久久久国产| 亚洲欧美日韩综合| 艳妇臀荡乳欲伦亚洲一区| 一区二区三区在线视频观看| 国产精品色一区二区三区| 欧美看片网站| 猫咪成人在线观看| 久久国产精品72免费观看| 一本久久综合| 91久久国产综合久久91精品网站| 国产一区二区三区高清播放| 国产精品免费网站| 欧美日韩精品是欧美日韩精品| 六月丁香综合| 久久久久久久一区| 国产一区在线播放| 国产精品毛片a∨一区二区三区|国| 欧美激情久久久久久| 久久一区二区三区四区五区| 欧美一区2区三区4区公司二百| 亚洲私人影吧| 日韩天堂在线观看| 亚洲激情在线| 亚洲电影免费| 在线播放中文一区| 国内外成人免费激情在线视频| 国产精品综合不卡av| 国产精品久久久久久久app| 欧美日韩在线视频观看| 欧美精品v日韩精品v韩国精品v| 免费久久精品视频| 久久久噜噜噜| 久久久久欧美精品| 久久久久久国产精品mv| 久久国产加勒比精品无码| 欧美中文字幕在线| 欧美在线电影| 久久狠狠一本精品综合网| 久久国产日韩欧美| 久久精品国产清高在天天线| 欧美一区二区三区在线观看| 午夜精品一区二区三区电影天堂| 亚洲综合日韩在线| 亚洲欧美日本在线| 亚久久调教视频| 欧美在线观看一二区| 久久精品91久久香蕉加勒比| 久久国产黑丝|