好看又運動 15萬級職場精英必選合資小車有這些

個人認為,昂克賽拉的兩廂版本比三廂版本更能體現魂動設計的精髓,整體的車身線條勾畫風格帶有一種柔美的韻律感,立體感強烈的車頭造型和線條飽滿,層次感豐富的線條所勾勒出的車尾,讓這台小車的氣質更加靈動時尚。對於昂科賽拉,在裸車價十五萬這個層次可以考慮的是兩廂2。



高顏值運動兩廂車

隨着時代的發展,兩廂版本的車型越來越被普羅大眾所接受;憑藉著動感時尚的外形,靈活的操控特性,越來越多的處於職業上升期的職場精英人士都會考慮選擇一款兩廂車作為人生首台代步工具。

作為一款兩廂轎車,我們不需要太高的預算,15萬左右的售價區間是當下很多人可接受的購車標準,本次小編整理了三款兼顧了顏值和操控的兩廂小車,供各位看官參考。

美系的不羈—別克威朗

儘管別克威朗2017款三廂版本已經上市,但是小編個人覺得2016款的兩廂版本威朗更具有年輕氣質。

威朗兩廂版看上去非常緊湊,沒有一絲拖泥帶水之感;前臉直瀑式的進氣格柵較為狹窄,營造出一種較為兇狠的俯衝式前臉,攻擊性很足,而車尾的懸浮式車頂也是當下較為時尚流行的設計元素,車尾用大量的線條勾勒出層次感豐富,頗上檔次的感覺。

小編推薦的車型是兩廂GS 20T雙離合燃情運動型,搭載的是一款1.5T直噴渦輪增壓發動機,最大馬力169匹,峰值扭矩250牛米,與之配合的是7速雙離合變速箱,1700轉迎來高扭矩平台,速度起來后頗有一種小鋼炮的勁頭。

雖然指導價格在17.59萬,但由於現在別克車型的全系降價幅度非常客觀,以廣州為例,普遍降價都到達2萬,威朗GS燃情運動型優惠后裸車價也就是在15萬左右,是一款非常不錯的A+級緊湊兩廂小車。

日系的騷氣—馬自達昂克賽拉

初窺馬自達的魂動設計理念之時,小編腦海中首先蹦出來的形容詞就是“騷氣”,這個形容詞在這裏不包含任何貶義。

個人認為,昂克賽拉的兩廂版本比三廂版本更能體現魂動設計的精髓,整體的車身線條勾畫風格帶有一種柔美的韻律感,立體感強烈的車頭造型和線條飽滿,層次感豐富的線條所勾勒出的車尾,讓這台小車的氣質更加靈動時尚。

對於昂科賽拉,在裸車價十五萬這個層次可以考慮的是兩廂2.0L自動運動型,搭載的是創馳藍天2.0L自然吸氣發動機,最大馬力158匹,峰值扭矩202牛米,傳動系統採用的是6速手自一體變速箱。基於馬自達一貫對於操控性的調校,昂科賽拉的駕駛樂趣和運動性能可以說是同級中名列前茅的存在,儘管賬面參數看上去不大,但是主觀感受還是一款非常快的小車。

法式的浪漫—標緻308S

近來法系車的設計師的設計理念少了一絲誇張與天馬行空,轉而回歸到更加平實圓潤更讓大眾所接受的風格,但從308S的外觀上還是可以看出一點法國人的浪漫情調。

車頭圓潤陰柔,不張揚跋扈,卻也個性十足,308S的前臉可以說是目前標緻品牌所採用的家族風格前臉的開山之作,車尾的后保險杠在308S的車身上看起來顯得比較寬大,肌肉感非常強烈。

標緻308S推薦車型是1.2T自動勁馳版,別小看了這台1.2T發動機,最大馬力136匹,峰值扭矩230牛米,這個參數已經可以趕超目前很多小排量4缸渦輪增壓發動機,韌勁十足的底盤,1750轉迎來的高扭矩爆發,以及戰鬥氛圍充足的駕駛艙,動感時尚、個性十足是我對於這台法系小車的評價。

全文總結:以上三款車型都是將外觀設計感、操控運動感兼顧的比較均衡的兩廂車型,外觀上更符合當下年輕消費者的審美,而都具備着不俗的動力表現;至於選擇哪個,以上三款車代表了三個車系,就看你對哪個品牌、哪個車系的選擇更加具有好感了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

澳洲跨年野火災情慘 調查:30億動物死亡或遷徙

摘錄自2020年7月28日中央廣播電台報導

根據一項28日發布的報告,澳洲在2019年到2020年所發生的前所未見野火災難中,有將近30億動物死亡或被迫遷徙。報告指出,這場野火是「現代歷史中最慘烈的野火災難之一」。這項由數所澳洲大學科學家所進行的研究指出,被侵襲的野生生命包括1.43億哺乳類動物,24.6億爬蟲類、1.8億鳥類,以及5,100萬的蛙類。

雖然報告沒有說明有多少動物因野火死亡,但報告作者之一狄克曼(Chris Dickman)表示,由於缺乏食物、避難所以及未受到保護,那些從烈焰中逃出的動物前景「可能很不好」。

雪梨大學(University of Sydney)的首席科學家伊登(Lily van Eeden)表示,今天公佈的這項調查是首度涵蓋全澳洲大陸的野火區。

這項調查的結果仍在處理中,最終報告將於下個月底發布。不過,報告作者們表示,30億動物受影響的這個數字不太可能改變。

生物多樣性
環境新聞
國際新聞
澳洲
澳洲野火
氣候變遷
棲地保育
森林

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

談談Spring中的對象跟Bean,你知道Spring怎麼創建對象的嗎?

本系列文章:

讀源碼,我們可以從第一行讀起

你知道Spring是怎麼解析配置類的嗎?

配置類為什麼要添加@Configuration註解?

推薦閱讀:

Spring官網閱讀 | 總結篇

Spring雜談

本系列文章將會帶你一行行的將Spring的源碼吃透,推薦閱讀的文章是閱讀源碼的基礎!

兩個問題

在開始探討源碼前,我們先思考兩個問題:

1、在Spring中,什麼是Bean?跟對象有什麼區別?

通過new關鍵字,反射,克隆等手段創建出來的就是對象。在Spring中,Bean一定是一個對象,但是對象不一定是一個Bean,一個被創建出來的對象要變成一個Bean要經過很多複雜的工序,例如需要被我們的BeanPostProcessor處理,需要經過初始化,需要經過AOPAOP本身也是由後置處理器完成的)等。

2、在創建對象前,Spring還做了其它什麼事情嗎?

我們還是回到流程圖中,其中相關的步驟如下:

在前面的三篇文章中,我們已經分析到了第3-5步的源碼,而如果你對Spring源碼稍有了解的話,就是知道創建對象以及將對象變成一個Bean的過程發生在第3-11步驟中。中間的五步分別做了什麼呢?

1、registerBeanPostProcessors

就像名字所說的那樣,註冊BeanPostProcessor,這段代碼在Spring官網閱讀(八)容器的擴展點(三)(BeanPostProcessor)已經分析過了,所以在本文就直接跳過了,如果你沒有看過之前的文章也沒有關係,你只需要知道,在這裏Spring將所有的BeanPostProcessor註冊到了容器中

2、initMessageSource

初始化容器中的messageSource,如果程序員沒有提供,默認會創建一個org.springframework.context.support.DelegatingMessageSource,Spring官網閱讀(十一)ApplicationContext詳細介紹(上) 已經介紹過了。

3、initApplicationEventMulticaster

初始化事件分發器,如果程序員沒有提供,那麼默認創建一個org.springframework.context.event.ApplicationEventMulticaster,Spring官網閱讀(十二)ApplicationContext詳解(中)已經做過詳細分析,不再贅述

4、onRefresh

留給子類複寫擴展使用

5、registerListeners

註冊事件監聽器,就是將容器中所有實現了org.springframework.context.ApplicationListener接口的對象放入到監聽器的集合中。

創建對象的源碼分析

在完成了上面的一些準備工作后,Spring開始來創建Bean了,按照流程,首先被調用的就是finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory)方法,我們就以這個方法為入口,一步步跟蹤源碼,看看Spring中的Bean到底是怎麼創建出來的,當然,本文主要關注的是創建對象的這個過程,對象變成Bean的流程我們在後續文章中再分析

1、finishBeanFactoryInitialization

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
   // 初始化一個ConversionService用於類型轉換,這個ConversionService會在實例化對象的時候用到
   if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
         beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
      beanFactory.setConversionService(
            beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
   }

  // 添加一個StringValueResolver,用於處理佔位符,可以看到,默認情況下就是使用環境中的屬性值來替代佔位符中的屬性
   if (!beanFactory.hasEmbeddedValueResolver()) {
      beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
   }

   // 創建所有的LoadTimeWeaverAware
   String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
   for (String weaverAwareName : weaverAwareNames) {
      getBean(weaverAwareName);
   }

   // 靜態織入完成后將臨時的類加載器設置為null,所以除了創建LoadTimeWeaverAware時可能會用到臨時類加載器,其餘情況下都為空
   beanFactory.setTempClassLoader(null);

   // 將所有的配置信息凍結
   beanFactory.freezeConfiguration();

   // 開始進行真正的創建
   beanFactory.preInstantiateSingletons();
}

上面的方法最終調用了org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons來創建Bean。

其源碼如下:

2、preInstantiateSingletons

public void  preInstantiateSingletons() throws BeansException {
    	// 所有bd的名稱 
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
    	// 遍歷所有bd,一個個進行創建 
		for (String beanName : beanNames) {
            // 獲取到指定名稱對應的bd
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            // 對不是延遲加載的單例的Bean進行創建
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                // 判斷是否是一個FactoryBean
				if (isFactoryBean(beanName)) {
                    // 如果是一個factoryBean的話,先創建這個factoryBean,創建factoryBean時,需要在beanName前面拼接一個&符號
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						final FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
											((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
                            // 判斷是否是一個SmartFactoryBean,並且不是懶加載的,就意味着,在創建了這個factoryBean之後要立馬調用它的getObject方法創建另外一個Bean
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				else {
                    // 不是factoryBean的話,我們直接創建就行了
					getBean(beanName);
				}
			}
		}
		// 在創建了所有的Bean之後,遍歷
		for (String beanName : beanNames) {
            // 這一步其實是從緩存中獲取對應的創建的Bean,這裏獲取到的必定是單例的 
			Object singletonInstance = getSingleton(beanName);
            // 判斷是否是一個SmartInitializingSingleton,最典型的就是我們之前分析過的EventListenerMethodProcessor,在這一步完成了對已經創建好的Bean的解析,會判斷其方法上是否有	@EventListener註解,會將這個註解標註的方法通過EventListenerFactory轉換成一個事件監聽器並添加到監聽器的集合中
			if (singletonInstance instanceof SmartInitializingSingleton) {
				final SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
				if (System.getSecurityManager() != null) {
					AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
						smartSingleton.afterSingletonsInstantiated();
						return null;
					}, getAccessControlContext());
				}
				else {
					smartSingleton.afterSingletonsInstantiated();
				}
			}
		}
	}

上面這段代碼整體來說應該不難,不過它涉及到了一個點就是factoryBean,如果你對它不夠了解的話,請參考我之前的一篇文章:Spring官網閱讀(七)容器的擴展點(二)FactoryBean

3、doGetBean

從上面的代碼分析中我們可以知道,Spring最終都會調用到getBean方法,而getBean並不是真正幹活的,doGetBean才是。另外doGetBean可以分為兩種情況

  • 創建的是一個FactoryBean,此時實際傳入的name = & + beanName
  • 創建的是一個普通Bean,此時傳入的name = beanName

其代碼如下:

	protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
		// 前面我們說過了,傳入的name可能時& + beanName這種形式,這裏做的就是去除掉&,得到beanName
		final String beanName = transformedBeanName(name);
		Object bean;
		// 這個方法就很牛逼了,通過它解決了循環依賴的問題,不過目前我們只需要知道它是從單例池中獲取已經創建的Bean即可,循環依賴後面我單獨寫一篇文章
        // 方法作用:已經創建的Bean會被放到單例池中,這裏就是從單例池中獲取
		Object sharedInstance = getSingleton(beanName);
        
		if (sharedInstance != null && args == null) {
            // 如果直接從單例池中獲取到了這個bean(sharedInstance),我們能直接返回嗎?
            // 當然不能,因為獲取到的Bean可能是一個factoryBean,如果我們傳入的name是 & + beanName 這種形式的話,那是可以返回的,但是我們傳入的更可能是一個beanName,那麼這個時候Spring就還需要調用這個sharedInstance的getObject方法來創建真正被需要的Bean
			bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
		}
		else {
            // 在緩存中獲取不到這個Bean
            // 原型下的循環依賴直接報錯
			if (isPrototypeCurrentlyInCreation(beanName)) {
				throw new BeanCurrentlyInCreationException(beanName);
			}
            
            // 核心要義,找不到我們就從父容器中再找一次
			BeanFactory parentBeanFactory = getParentBeanFactory();
			if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
				String nameToLookup = originalBeanName(name);
				if (parentBeanFactory instanceof AbstractBeanFactory) {
					return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
							nameToLookup, requiredType, args, typeCheckOnly);
				}
				else if (args != null) {
					return (T) parentBeanFactory.getBean(nameToLookup, args);
				}
				else if (requiredType != null) {
					return parentBeanFactory.getBean(nameToLookup, requiredType);
				}
				else {
					return (T) parentBeanFactory.getBean(nameToLookup);
				}
			}
            
            // 如果不僅僅是為了類型推斷,也就是代表我們要對進行實例化
            // 那麼就將bean標記為正在創建中,其實就是將這個beanName放入到alreadyCreated這個set集合中
			if (!typeCheckOnly) {
				markBeanAsCreated(beanName);
			}
			try {
                
				final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
                
                // 檢查合併后的bd是否是abstract,這個檢查現在已經沒有作用了,必定會通過
				checkMergedBeanDefinition(mbd, beanName, args);

				// @DependsOn註解標註的當前這個Bean所依賴的bean名稱的集合,就是說在創建當前這個Bean前,必須要先將其依賴的Bean先完成創建
				String[] dependsOn = mbd.getDependsOn();
				if (dependsOn != null) {
                    // 遍歷所有申明的依賴
					for (String dep : dependsOn) {
                        // 如果這個bean所依賴的bean又依賴了當前這個bean,出現了循環依賴,直接報錯
						if (isDependent(beanName, dep)) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
						}
                        // 註冊bean跟其依賴的依賴關係,key為依賴,value為依賴所從屬的bean
						registerDependentBean(dep, beanName);
						try {
                            // 先創建其依賴的Bean
							getBean(dep);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(mbd.getResourceDescription(), beanName,
									"'" + beanName + "' depends on missing bean '" + dep + "'", ex);
						}
					}
				}
				// 我們目前只分析單例的創建,單例看懂了,原型自然就懂了
				if (mbd.isSingleton()) {
                    // 這裏再次調用了getSingleton方法,這裏跟方法開頭調用的getSingleton的區別在於,這個方法多傳入了一個ObjectFactory類型的參數,這個ObjectFactory會返回一個Bean
					sharedInstance = getSingleton(beanName, () -> {
						try {
							return createBean(beanName, mbd, args);
						}
						catch (BeansException ex) {
							destroySingleton(beanName);
							throw ex;
						}
					});
					bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
				}
		// 省略原型跟域對象的相關代碼
		return (T) bean;
	}

配合註釋看這段代碼應該也不難吧,我們重點關注最後在調用的這段方法即可

4、getSingleton(beanName,ObjectFactory)

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
   Assert.notNull(beanName, "Bean name must not be null");
   synchronized (this.singletonObjects) {
       // 從單例池中獲取,這個地方肯定也獲取不到
      Object singletonObject = this.singletonObjects.get(beanName);
      if (singletonObject == null) {
          // 工廠已經在銷毀階段了,這個時候還在創建Bean的話,就直接拋出異常
         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!)");
         }
         // 在單例創建前,記錄一下正在創建的單例的名稱,就是把beanName放入到singletonsCurrentlyInCreation這個set集合中去
         beforeSingletonCreation(beanName);
         boolean newSingleton = false;
         boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
         if (recordSuppressedExceptions) {
            this.suppressedExceptions = new LinkedHashSet<>();
         }
         try {
             // 這裏調用了singletonFactory的getObject方法,對應的實現就是在doGetBean中的那一段lambda表達式
            singletonObject = singletonFactory.getObject();
            newSingleton = true;
         }
        // 省略異常處理
         finally {
            if (recordSuppressedExceptions) {
               this.suppressedExceptions = null;
            }
             // 在單例完成創建后,將beanName從singletonsCurrentlyInCreation中移除
             // 標志著這個單例已經完成了創建
            afterSingletonCreation(beanName);
         }
         if (newSingleton) {
             // 添加到單例池中
            addSingleton(beanName, singletonObject);
         }
      }
      return singletonObject;
   }
}

分析完上面這段代碼,我們會發現,核心的創建Bean的邏輯就是在singletonFactory.getObject()這句代碼中,而其實現就是在doGetBean方法中的那一段lambda表達式,如下:

實際就是通過createBean這個方法創建了一個Bean然後返回,createBean又幹了什麼呢?

5、createBean

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

   RootBeanDefinition mbdToUse = mbd;
    
    // 解析得到beanClass,為什麼需要解析呢?如果是從XML中解析出來的標籤屬性肯定是個字符串嘛
    // 所以這裏需要加載類,得到Class對象
   Class<?> resolvedClass = resolveBeanClass(mbd, beanName);
   if (resolvedClass != null && !mbd.hasBeanClass() && mbd.getBeanClassName() != null) {
      mbdToUse = new RootBeanDefinition(mbd);
      mbdToUse.setBeanClass(resolvedClass);
   }
   // 對XML標籤中定義的lookUp屬性進行預處理,如果只能根據名字找到一個就標記為非重載的,這樣在後續就不需要去推斷到底是哪個方法了,對於@LookUp註解標註的方法是不需要在這裏處理的,AutowiredAnnotationBeanPostProcessor會處理這個註解
   try {
      mbdToUse.prepareMethodOverrides();
   }
   // 省略異常處理...
   try {
       // 在實例化對象前,會經過後置處理器處理
       // 這個後置處理器的提供了一個短路機制,就是可以提前結束整個Bean的生命周期,直接從這裏返回一個Bean
       // 不過我們一般不會這麼做,它的另外一個作用就是對AOP提供了支持,在這裡會將一些不需要被代理的Bean進行標記,就本文而言,你可以暫時理解它沒有起到任何作用
      Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
      if (bean != null) {
         return bean;
      }
   }
    // 省略異常處理...
   try {
       // doXXX方法,真正幹活的方法,doCreateBean,真正創建Bean的方法
      Object beanInstance = doCreateBean(beanName, mbdToUse, args);
      if (logger.isDebugEnabled()) {
         logger.debug("Finished creating instance of bean '" + beanName + "'");
      }
      return beanInstance;
   }
  // 省略異常處理...
}

6、doCreateBean

本文只探討對象是怎麼創建的,至於怎麼從一個對象變成了Bean,在後面的文章我們再討論,所以我們主要就關注下面這段代碼

// 這個方法真正創建了Bean,創建一個Bean會經過 創建對象 > 依賴注入 > 初始化 這三個過程,在這個過程中,BeanPostPorcessor會穿插執行,本文主要探討的是創建對象的過程,所以關於依賴注入及初始化我們暫時省略,在後續的文章中再繼續研究
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
      throws BeanCreationException {

   // Instantiate the bean.
   BeanWrapper instanceWrapper = null;
   if (mbd.isSingleton()) {
       // 這行代碼看起來就跟factoryBean相關,這是什麼意思呢?
       // 在下文我會通過例子介紹下,你可以暫時理解為,這個地方返回的就是個null
      instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
   }
   if (instanceWrapper == null) {
       // 這裏真正的創建了對象
      instanceWrapper = createBeanInstance(beanName, mbd, args);
   }
   // 省略依賴注入,初始化
}

這裏我先分析下this.factoryBeanInstanceCache.remove(beanName)這行代碼。這裏需要說一句,我寫的這個源碼分析的系列非常的細節,之所以選擇這樣一個個扣細節是因為我自己在閱讀源碼過程中經常會被這些問題阻塞,那麼藉著這些文章將自己踩過的坑分享出來可以減少作為讀者的你自己在閱讀源碼時的障礙,其次也能夠提升自己閱讀源碼的能力。如果你對這些細節不感興趣的話,可以直接跳過,能把握源碼的主線即可。言歸正傳,我們回到這行代碼this.factoryBeanInstanceCache.remove(beanName)。什麼時候factoryBeanInstanceCache這個集合中會有值呢?這裏我還是以示例代碼來說明這個問題,示例代碼如下:

public class Main {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
	}
}

// 沒有做什麼特殊的配置,就是掃描了需要的組件,測試時換成你自己的包名
@ComponentScan("com.dmz.source.instantiation")
@Configuration
public class Config {
}

// 這裏申明了一個FactoryBean,並且通過@DependsOn註解申明了這個FactoryBean的創建要在orderService之後,主要目的是為了在DmzFactoryBean創建前讓容器發生一次屬性注入
@Component
@DependsOn("orderService")
public class DmzFactoryBean implements FactoryBean<DmzService> {
	@Override
	public DmzService getObject() throws Exception {
		return new DmzService();
	}

	@Override
	public Class<?> getObjectType() {
		return DmzService.class;
	}
}

// 沒有通過註解的方式將它放到容器中,而是通過上面的DmzFactoryBean來管理對應的Bean
public class DmzService {
}

// OrderService中需要注入dmzService
@Component
public class OrderService {
	@Autowired
	DmzService dmzService;
}

在這段代碼中,因為我們明確的表示了DmzFactoryBean是依賴於orderService的,所以必定會先創建orderService再創建DmzFactoryBean,創建orderService的流程如下:

其中的屬性注入階段,我們需要細化,也可以畫圖如下:

為orderService進行屬性注入可以分為這麼幾步

  1. 找到需要注入的注入點,也就是orderService中的dmzService字段

  2. 根據字段的類型以及名稱去容器中查詢符合要求的Bean

  3. 當遍歷到一個FactroyBean時,為了確定其getObject方法返回的對象的類型需要創建這個FactroyBean(只會到對象級別),然後調用這個創建好的FactroyBean的getObjectType方法明確其類型並與注入點需要的類型比較,看是否是一個候選的Bean,在創建這個FactroyBean時就將其放入了factoryBeanInstanceCache中。

  4. 在確定了唯一的候選Bean之後,Spring就會對這個Bean進行創建,創建的過程又經過三個步驟

    • 創建對象
    • 屬性注入
    • 初始化

    在創建對象時,因為此時factoryBeanInstanceCache已經緩存了這個Bean對應的對象,所以直接通過this.factoryBeanInstanceCache.remove(beanName)這行代碼就返回了,避免了二次創建對象。

7、createBeanInstance

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
   
   Class<?> beanClass = resolveBeanClass(mbd, beanName);
   // 省略異常
    
    // 通過bd中提供的instanceSupplier來獲取一個對象
    // 正常bd中都不會有這個instanceSupplier屬性,這裏也是Spring提供的一個擴展點,但實際上不常用
   Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
   if (instanceSupplier != null) {
      return obtainFromSupplier(instanceSupplier, beanName);
   }

   // bd中提供了factoryMethodName屬性,那麼要使用工廠方法的方式來創建對象,工廠方法又會區分靜態工廠方法跟實例工廠方法
   if (mbd.getFactoryMethodName() != null) {
      return instantiateUsingFactoryMethod(beanName, mbd, args);
   }

   // 在原型模式下,如果已經創建過一次這個Bean了,那麼就不需要再次推斷構造函數了
   boolean resolved = false;  // 是否推斷過構造函數
   boolean autowireNecessary = false;  // 構造函數是否需要進行注入
   if (args == null) {
      synchronized (mbd.constructorArgumentLock) {
         if (mbd.resolvedConstructorOrFactoryMethod != null) {
            resolved = true;
            autowireNecessary = mbd.constructorArgumentsResolved;
         }
      }
   }
   if (resolved) {
      if (autowireNecessary) {
         return autowireConstructor(beanName, mbd, null, null);
      }
      else {
         return instantiateBean(beanName, mbd);
      }
   }

   // 推斷構造函數
   Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
   if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
         mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
      return autowireConstructor(beanName, mbd, ctors, args);
   }

   // 調用無參構造函數創建對象
   return instantiateBean(beanName, mbd);
}

上面這段代碼在Spring官網閱讀(一)容器及實例化 已經分析過了,但是當時我們沒有深究創建對象的細節,所以本文將詳細探討Spring中的這個對象到底是怎麼創建出來的,這也是本文的主題。

在Spring官網閱讀(一)容器及實例化 這篇文章中,我畫了下面這麼一張圖

從上圖中我們可以知道Spring在實例化對象的時候有這麼幾種方式

  1. 通過bd中的supplier屬性
  2. 通過bd中的factoryMethodName跟factoryBeanName
  3. 通過構造函數

我們接下來就一一分析其中的細節:

》通過bd中的supplier屬性實例化對象

在Spring官網閱讀(一)容器及實例化 文中介紹過這種方式,因為這種方式我們基本不會使用,並不重要,所以這裏就不再贅述,我這裏就直接給出一個使用示例,大家自行體會吧

public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    // 直接註冊一個Bean,並且指定它的supplier就是Service::new
		ac.registerBean("service", Service.class,Service::new,zhe'sh);
		ac.refresh();
		System.out.println(ac.getBean("service"));
}

》通過bd中的factoryMethodName跟factoryBeanName實例化對象

對應代碼如下:

protected BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs);
}

上面這段代碼主要幹了兩件事

  • 創建一個ConstructorResolver對象,從類名來看,它是一個構造器解析器
  • 調用了這個構造器解析器的instantiateUsingFactoryMethod方法,這個方法見名知意,使用FactoryMethod來完成實例化

基於此,我們解決一個問題,ConstructorResolver是什麼?

ConstructorResolver是什麼?

在要研究一個類前,我們最先應該從哪裡入手呢?很多沒有經驗的同學可能會悶頭看代碼,但是實際上最好的學習方式是先閱讀類上的javaDoc

ConstructorResolver上的javaDoc如下:

上面這段javaDoc翻譯過來就是這個類就是用來解析構造函數跟工廠方法的代理者,並且它是通過參數匹配的方式來進行推斷構造方法或者工廠方法

看到這裏不知道小夥伴們是否有疑問,就是明明這個類不僅負責推斷構造函數,還會負責推斷工廠方法,那麼為什麼類名會叫做ConstructorResolver呢?我們知道Spring的代碼在業界來說絕對是最規範的,沒有之一,這樣來說的話,這個類最合適的名稱應該是ConstructorAndFactoryMethodResolver才對,因為它不僅負責推斷了構造函數還負責推斷了工廠方法嘛!

這裏我需要說一下我自己的理解。對於一個Bean,它是通過構造函數完成實例化的,或者通過工廠方法實例化的,其實在這個Bean看來都沒有太大區別,這兩者都可以稱之為這個Bean的構造器,因為通過它們都能構造出一個Bean。所以Spring就把兩者統稱為構造器了,所以這個類名也就被稱為ConstructorResolver了。

Spring在很多地方體現了這種實現,例如在XML配置的情況下,不論我們是使用構造函數創建對象還是使用工廠方法創建對象,其參數的標籤都是使用constructor-arg。比如下面這個例子

<bean id="dmzServiceGetFromStaticMethod"
      factory-bean="factoryBean"
      factory-method="getObject">
    <constructor-arg type="java.lang.String" value="hello" name="s"/>
    <constructor-arg type="com.dmz.source.instantiation.service.DmzFactory" ref="factoryBean"/>
</bean>

<!--測試靜態工廠方法創建對象-->
<bean id="service"
      class="com.dmz.official.service.MyFactoryBean"
      factory-method="staticGet">
    <constructor-arg type="java.lang.String" value="hello"/>
</bean>

<bean id="dmzService" class="com.dmz.source.instantiation.service.DmzService">
    <constructor-arg name="s" value="hello"/>
</bean>

在對這個類有了大概的了解后,我們就需要來分析它的源碼,這裏我就不把它單獨拎出來分析了,我們藉著Spring的流程看看這個類幹了什麼事情

instantiateUsingFactoryMethod方法做了什麼?

核心目的:推斷出要使用的factoryMethod以及調用這個FactoryMethod要使用的參數,然後反射調用這個方法實例化出一個對象

這個方法的代碼太長了,所以我們將它拆分成為一段一段的來分析

方法參數分析

在分析上面的代碼之前,我們先來看看這個方法的參數都是什麼含義

方法上關於參數的介紹如圖所示

  • beanName:當前要實例化的Bean的名稱
  • mbd:當前要實例化的Bean對應的BeanDefinition
  • explicitArgs:這個參數在容器啟動階段我們可以認定它就是null,只有显示的調用了getBean方法,並且傳入了明確的參數,例如:getBean("dmzService","hello")這種情況下才會不為null,我們分析這個方法的時候就直接認定這個參數為null即可
第一段
public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    // 第一段代碼:創建並初始話一個BeanWrapperImpl
    BeanWrapperImpl bw = new BeanWrapperImpl();
    this.beanFactory.initBeanWrapper(bw);
    // ......
}

BeanWrapperImpl是什麼呢?如果你看過我之前的文章:Spring官網閱讀(十四)Spring中的BeanWrapper及類型轉換,那麼你對這個類應該不會陌生,它就是對Bean進行了一層包裝,並且在創建Bean的時候以及進行屬性注入的時候能夠進行類型轉換。就算你沒看過之前的文章也沒關係,只要記住兩點

  • BeanWrapperImpl包裝了一個實例化好的對象
  • BeanWrapperImpl能夠對屬性進行類型轉換

其層級關係如下:

回到我們的源碼分析,我們先來看看new BeanWrapperImpl()做了什麼事情?

對應代碼如下:

// 第一步:調用空參構造
public BeanWrapperImpl() {
    // 調用另外一個構造函數,表示要註冊默認的屬性編輯器
    this(true);
}

// 這個構造函數表明是否要註冊默認編輯器,上面傳入的值為true,表示需要註冊
public BeanWrapperImpl(boolean registerDefaultEditors) {
    super(registerDefaultEditors);
}

// 調用到父類的構造函數,確定要使用默認的屬性編輯器
protected AbstractNestablePropertyAccessor(boolean registerDefaultEditors) {
    if (registerDefaultEditors) {
        registerDefaultEditors();
    }
    // 對typeConverterDelegate進行初始化
    this.typeConverterDelegate = new TypeConverterDelegate(this);
}

總的來說創建的過程非常簡單。第一,確定要註冊默認的屬性編輯器;第二,對typeConverterDelegate屬性進行初始化。

緊接着,我們看看在初始化這個BeanWrapper做了什麼?

// 初始化BeanWrapper,主要就是將容器中配置的conversionService賦值到當前這個BeanWrapper上
// 同時註冊定製的屬性編輯器
protected void initBeanWrapper(BeanWrapper bw) {
    bw.setConversionService(getConversionService());
    registerCustomEditors(bw);
}

還記得conversionService在什麼時候被放到容器中的嗎?就是在finishBeanFactoryInitialization的時候啦~!

conversionService屬性完成賦值后就開始註冊定製的屬性編輯器,代碼如下:

// 傳入的參數就是我們的BeanWrapper,它同時也是一個屬性編輯器註冊表
protected void registerCustomEditors(PropertyEditorRegistry registry) {
    PropertyEditorRegistrySupport registrySupport =
        (registry instanceof PropertyEditorRegistrySupport ? (PropertyEditorRegistrySupport) registry : null);
    if (registrySupport != null) {
        // 這個配置的作用就是在註冊默認的屬性編輯器時,可以增加對數組到字符串的轉換功能
        // 默認就是通過","來切割字符串轉換成數組,對應的屬性編輯器就是StringArrayPropertyEditor
        registrySupport.useConfigValueEditors();
    }
    // 將容器中的屬性編輯器註冊到當前的這個BeanWrapper
    if (!this.propertyEditorRegistrars.isEmpty()) {
        for (PropertyEditorRegistrar registrar : this.propertyEditorRegistrars) {
            registrar.registerCustomEditors(registry);
            // 省略異常處理~
        }
    }
    // 這裏我們沒有添加任何的自定義的屬性編輯器,所以肯定為空
    if (!this.customEditors.isEmpty()) {
        this.customEditors.forEach((requiredType, editorClass) ->
                                   registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass)));
    }
}
第二段
public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {

    // 省略已經分析的第一段代碼,到這裏已經得到了一個具有類型轉換功能的BeanWrapper
	
    // 實例化這個Bean的工廠Bean
    Object factoryBean;
    // 工廠Bean的Class
    Class<?> factoryClass;
    // 靜態工廠方法或者是實例化工廠方法
    boolean isStatic;
	
    /*下面這段代碼就是為上面申明的這三個屬性賦值*/ 
    String factoryBeanName = mbd.getFactoryBeanName();
    // 如果創建這個Bean的工廠就是這個Bean本身的話,那麼直接拋出異常
    if (factoryBeanName != null) {
        if (factoryBeanName.equals(beanName)) {
            throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
                                                   "factory-bean reference points back to the same bean definition");
        }
        // 得到創建這個Bean的工廠Bean
        factoryBean = this.beanFactory.getBean(factoryBeanName);
        if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
            throw new ImplicitlyAppearedSingletonException();
        }
        factoryClass = factoryBean.getClass();
        isStatic = false;
    }
    else {
        // factoryBeanName為null,說明是通過靜態工廠方法來實例化Bean的
        // 靜態工廠進行實例化Bean,beanClass屬性必須要是工廠的class,如果為空,直接報錯
        if (!mbd.hasBeanClass()) {
            throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
                                                   "bean definition declares neither a bean class nor a factory-bean reference");
        }
        factoryBean = null;
        factoryClass = mbd.getBeanClass();
        isStatic = true;
    }
    // 省略後續代碼
}

小總結:

這段代碼很簡單,就是確認實例化當前這個Bean的工廠方法是靜態工廠還是實例工廠,如果是實例工廠,那麼找出對應的工廠Bean。

第三段
public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    
    // 省略第一段,第二段代碼
    
    // 到這裏已經得到了一個BeanWrapper,明確了實例化當前這個Bean到底是靜態工廠還是實例工廠
    // 並且已經確定了工廠Bean
    
    // 最終確定的要用來創建對象的方法
    Method factoryMethodToUse = null;
    ArgumentsHolder argsHolderToUse = null;
    Object[] argsToUse = null;
	
    // 參數分析時已經說過,explicitArgs就是null
    if (explicitArgs != null) {
        argsToUse = explicitArgs;
    }
    else {
        // 下面這段代碼是什麼意思呢?
        // 在原型模式下,我們會多次創建一個Bean,所以Spring對參數以及所使用的方法做了緩存
        // 在第二次創建原型對象的時候會進入這段緩存的邏輯
        // 但是這裡有個問題,為什麼Spring對參數有兩個緩存呢?
        // 一:resolvedConstructorArguments
        // 二:preparedConstructorArguments
        // 這裏主要是因為,直接使用解析好的構造的參數,因為這樣會導致創建出來的所有Bean都引用同一個屬性
        Object[] argsToResolve = null;
        synchronized (mbd.constructorArgumentLock) {
            factoryMethodToUse = (Method) mbd.resolvedConstructorOrFactoryMethod;
            // 緩存已經解析過的工廠方法或者構造方法
            if (factoryMethodToUse != null && mbd.constructorArgumentsResolved) {
                // resolvedConstructorArguments跟preparedConstructorArguments都是對參數的緩存
                argsToUse = mbd.resolvedConstructorArguments;
                if (argsToUse == null) {
                    argsToResolve = mbd.preparedConstructorArguments;
                }
            }
        }
        if (argsToResolve != null) {
            // preparedConstructorArguments需要再次進行解析
            argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve);
        }
    }
    // 省略後續代碼
}

小總結:

上面這段代碼應該沒什麼大問題,其核心思想就是從緩存中取已經解析出來的方法以及參數,這段代碼只會在原型模式下生效,因為單例的話對象只會創建一次嘛~!最大的問題在於,為什麼在對參數進行緩存的時候使用了兩個不同的集合,並且緩存后的參數還需要再次解析,這個問題我們暫且放着,不妨帶着這個問題往下看。

因為接下來要分析的代碼就比較複雜了,所以為了讓你徹底看到代碼的執行流程,下面我會使用示例+流程圖+文字的方式來分析源碼。

示例代碼如下(這個例子覆蓋接下來要分析的所有流程):

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
	   default-autowire="constructor"><!--這裏開啟自動注入,並且是通過構造函數進行自動注入-->

	<!--factoryObject 提供了創建對象的方法-->
	<bean id="factoryObject" class="com.dmz.spring.first.instantiation.service.FactoryObject"/>

	<!--提供一個用於測試自動注入的對象-->
	<bean class="com.dmz.spring.first.instantiation.service.OrderService" id="orderService"/>
	
    <!--主要測試這個對象的實例化過程-->
	<bean id="dmzService" factory-bean="factoryObject" factory-method="getDmz" scope="prototype">
		<constructor-arg name="name" value="dmz"/>
		<constructor-arg name="age" value="18"/>
		<constructor-arg name="birthDay" value="2020-05-23"/>
	</bean>
	
    <!--測試靜態方法實例化對象的過程-->
	<bean id="indexService" class="com.dmz.spring.first.instantiation.service.FactoryObject"
		  factory-method="staticGetIndex"/>
	
    <!--提供這個轉換器,用於轉換dmzService中的birthDay屬性,從字符串轉換成日期對象-->
	<bean class="org.springframework.context.support.ConversionServiceFactoryBean" id="conversionService">
		<property name="converters">
			<set>
				<bean class="com.dmz.spring.first.instantiation.service.ConverterStr2Date"/>
			</set>
		</property>
	</bean>
</beans>

測試代碼:

public class FactoryObject {

	public DmzService getDmz(String name, int age, Date birthDay, OrderService orderService) {
		System.out.println("getDmz with "+"name,age,birthDay and orderService");
		return new DmzService();
	}

	public DmzService getDmz(String name, int age, Date birthDay) {
		System.out.println("getDmz with "+"name,age,birthDay");
		return new DmzService();
	}

	public DmzService getDmz(String name, int age) {
		System.out.println("getDmz with "+"name,age");
		return new DmzService();
	}

	public DmzService getDmz() {
		System.out.println("getDmz with empty arg");
		return new DmzService();
	}

	public static IndexService staticGetIndex() {
		return new IndexService();
	}
}

public class DmzService {
}

public class IndexService {
}

public class OrderService {
}

public class ConverterStr2Date implements Converter<String, Date> {
	@Override
	public Date convert(String source) {
		try {
			return new SimpleDateFormat("yyyy-MM-dd").parse(source);
		} catch (ParseException e) {
			return null;
		}
	}
}

/**
 * @author 程序員DMZ
 * @Date Create in 23:14 2020/5/21
 * @Blog https://daimingzhi.blog.csdn.net/
 */
public class Main {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext();
		cc.setConfigLocation("application.xml");
		cc.refresh();
		cc.getBean("dmzService");
        // 兩次調用,用於測試緩存的方法及參數
//		cc.getBean("dmzService");

	}
}

運行上面的代碼會發現,程序打印:

getDmz with name,age,birthDay and orderService

具體原因我相信你看了接下來的源碼分析自然就懂了

第四段
public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
//  第一段代碼:到這裏已經得到了一個BeanWrapper,並對這個BeanWrapper做了初始化
//  第二段代碼:明確了實例化當前這個Bean到底是靜態工廠還是實例工廠
//	第三段代碼:以及從緩存中取過了對應了方法以及參數

// 進入第四段代碼分析,執行到這段代碼說明是第一次實例化這個對象
if (factoryMethodToUse == null || argsToUse == null) {
			// 如果被cglib代理的話,獲取父類的class
			factoryClass = ClassUtils.getUserClass(factoryClass);
			// 獲取到工廠類中的所有方法,接下來要一步步從這些方法中篩選出來符合要求的方法
			Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
			List<Method> candidateList = new ArrayList<>();
    		// 第一步篩選:之前 在第二段代碼中已經推斷了方法是靜態或者非靜態的
    		// 所以這裏第一個要求就是要滿足靜態/非靜態這個條件
    		// 第二個要求就是必須符合bd中定義的factoryMethodName的名稱
    		// 其中第二個要求請注意,如果bd是一個configurationClassBeanDefinition,也就是說是通過掃描@Bean註解產生的,那麼在判斷時還會添加是否標註了@Bean註解
			for (Method candidate : rawCandidates) {
				if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
					candidateList.add(candidate);
				}
			}
    		// 將之前得到的方法集合轉換成數組
    		// 到這一步得到的其實就是某一個方法的所有重載方法
    	 	// 比如dmz(),dmz(String name),dmz(String name,int age)
			Method[] candidates = candidateList.toArray(new Method[0]);
    
    		// 排序,public跟參數多的優先級越高
			AutowireUtils.sortFactoryMethods(candidates);
			
    		// 用來保存從配置文件中解析出來的參數
			ConstructorArgumentValues resolvedValues = null;
            // 是否使用了自動注入,本段代碼中沒有使用到這個屬性,但是在後面用到了
			boolean autowiring = (mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
			int minTypeDiffWeight = Integer.MAX_VALUE;
    		// 可能出現多個符合要求的方法,用這個集合保存,實際上如果這個集合有值,就會拋出異常了
			Set<Method> ambiguousFactoryMethods = null;

			int minNrOfArgs;
    		// 必定為null,不考慮了
			if (explicitArgs != null) {
				minNrOfArgs = explicitArgs.length;
			}
			else {
                // 就是說配置文件中指定了要使用的參數,那麼需要對其進行解析,解析后的值就存儲在resolvedValues這個集合中
				if (mbd.hasConstructorArgumentValues()) {
                    // 通過解析constructor-arg標籤,將參數封裝成了ConstructorArgumentValues
                    // ConstructorArgumentValues這個類在下文我們專門分析
					ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
					resolvedValues = new ConstructorArgumentValues();
                    // 解析標籤中的屬性,類似進行類型轉換,後文進行詳細分析
					minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
				}
				else {
                    // 配置文件中沒有指定要使用的參數,所以執行方法的最小參數個數就是0
					minNrOfArgs = 0;
				}
			}
	// 省略後續代碼....
}

小總結:

因為在實例化對象前必定要先確定具體要使用的方法,所以這裏先做的第一件事就是確定要在哪個範圍內去推斷要使用的factoryMethod呢?

最大的範圍就是這個factoryClass的所有方法,也就是源碼中的rawCandidates

其次需要在rawCandidates中進一步做推斷,因為在前面第二段代碼的時候已經確定了是靜態方法還是非靜態方法,並且BeanDefinition也指定了factoryMethodName,那麼基於這兩個條件這裏就需要對rawCandidates進一步進行篩選,得到一個candidateList集合。

我們對示例的代碼進行調試會發現

確實如我們所料,rawCandidates是factoryClass中的所有方法,candidateList是所有getDmz的重載方法。

在確定了推斷factoryMethod的範圍后,那麼接下來要根據什麼去確定到底使用哪個方法呢?換個問題,怎麼區分這麼些重載的方法呢?肯定是根據方法參數嘛!

所以接下來要做的就是去解析要使用的參數了~

對於Spring而言,方法的參數會分為兩種

  1. 配置文件中指定的
  2. 自動注入模式下,需要去容器中查找的

在上面的代碼中,Spring就是將配置文件中指定的參數做了一次解析,對應方法就是resolveConstructorArguments

在查看這個方法的源碼前,我們先看看ConstructorArgumentValues這個類

public class ConstructorArgumentValues {
	// 通過下標方式指定的參數
	private final Map<Integer, ValueHolder> indexedArgumentValues = new LinkedHashMap<>();
	// 沒有指定下標
	private final List<ValueHolder> genericArgumentValues = new ArrayList<>();
	// 省略無關代碼.....
}

在前文的註釋中我們也說過了,它主要的作用就是封裝解析constructor-arg標籤得到的屬性,解析標籤對應的方法就是org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parseConstructorArgElement,這個方法我就不帶大家看了,有興趣的可以自行閱讀。

它主要有兩個屬性

  1. indexedArgumentValues
  2. genericArgumentValues

對應的就是我們兩種指定參數的方法,如下:

<bean id="dmzService" factory-bean="factoryObject" factory-method="getDmz" scope="prototype">
    <constructor-arg name="name" value="dmz"/>
    <constructor-arg name="age" value="18"/>
    <constructor-arg index="2"  value="2020-05-23"/>
    <!--		<constructor-arg name="birthDay" value="2020-05-23"/>-->
</bean>

其中的name跟age屬性會被解析為genericArgumentValues,而index=2會被解析為indexedArgumentValues

在對ConstructorArgumentValues有一定認知之後,我們再來看看resolveConstructorArguments的代碼:

// 方法目的:解析配置文件中指定的方法參數
// beanName:bean名稱
// mbd:beanName對應的beanDefinition
// bw:通過它進行類型轉換
// ConstructorArgumentValues cargs:解析標籤得到的屬性,還沒有經過解析(類型轉換)
// ConstructorArgumentValues resolvedValues:已經經過解析的參數
// 返回值:返回方法需要的最小參數個數
private int resolveConstructorArguments(String beanName, RootBeanDefinition mbd, BeanWrapper bw,
                                        ConstructorArgumentValues cargs, ConstructorArgumentValues resolvedValues) {
	
    // 是否有定製的類型轉換器,沒有的話直接使用BeanWrapper進行類型轉換
    TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
    TypeConverter converter = (customConverter != null ? customConverter : bw);
    
    // 構造一個BeanDefinitionValueResolver,專門用於解析constructor-arg中的value屬性,實際上還包括ref屬性,內嵌bean標籤等等
    BeanDefinitionValueResolver valueResolver =
        new BeanDefinitionValueResolver(this.beanFactory, beanName, mbd, converter);
	
    // minNrOfArgs 記錄執行方法要求的最小參數個數,一般情況下就是等於constructor-arg標籤指定的參數數量
    int minNrOfArgs = cargs.getArgumentCount();

    for (Map.Entry<Integer, ConstructorArgumentValues.ValueHolder> entry : cargs.getIndexedArgumentValues().entrySet()) {
        int index = entry.getKey();
        if (index < 0) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                            "Invalid constructor argument index: " + index);
        }
        // 這是啥意思呢?
        // 這個代碼我認為是有問題的,並且我給Spring官方已經提了一個issue,官方將會在5.2.7版本中修復
        // 暫且你先這樣理解
        // 假設A方法直接在配置文件中指定了index=3上要使用的參數,那麼這個時候A方法至少需要4個參數
        // 但是其餘的3個參數可能不是通過constructor-arg標籤指定的,而是直接自動注入進來的,那麼在配置文件中我們就只配置了index=3上的參數,也就是說 int minNrOfArgs = cargs.getArgumentCount()=1,這個時候 index=3,minNrOfArgs=1, 所以 minNrOfArgs = 3+1
        if (index > minNrOfArgs) {
            minNrOfArgs = index + 1;
        }
        ConstructorArgumentValues.ValueHolder valueHolder = entry.getValue();
        // 如果已經轉換過了,直接添加到resolvedValues集合中
        if (valueHolder.isConverted()) {
            resolvedValues.addIndexedArgumentValue(index, valueHolder);
        }
        else {
            // 解析value/ref/內嵌bean標籤等
            Object resolvedValue =
                valueResolver.resolveValueIfNecessary("constructor argument", valueHolder.getValue());
            // 將解析后的resolvedValue封裝成一個新的ValueHolder,並將其source設置為解析constructor-arg得到的那個ValueHolder,後期會用到這個屬性進行判斷
            ConstructorArgumentValues.ValueHolder resolvedValueHolder =
                new ConstructorArgumentValues.ValueHolder(resolvedValue, valueHolder.getType(), valueHolder.getName());
            resolvedValueHolder.setSource(valueHolder);
            resolvedValues.addIndexedArgumentValue(index, resolvedValueHolder);
        }
    }
   // 對getGenericArgumentValues進行解析,代碼基本一樣,不再贅述
    return minNrOfArgs;
}

可以看到,最終的解析邏輯就在resolveValueIfNecessary這個方法中,那麼這個方法又做了什麼呢?

// 這個方法的目的就是將解析constructor-arg標籤得到的value值進行一次解析
// 在解析標籤時ref屬性會被封裝為RuntimeBeanReference,那麼在這裏進行解析時就會去調用getBean
// 在解析value屬性會會被封裝為TypedStringValue,那麼這裡會嘗試去進行一個轉換
// 關於標籤的解析大家有興趣的話可以去看看org.springframework.beans.factory.xml.BeanDefinitionParserDelegate#parsePropertyValue
// 這裏不再贅述了
public Object resolveValueIfNecessary(Object argName, @Nullable Object value) {
	
    // 解析constructor-arg標籤中的ref屬性,實際就是調用了getBean
    if (value instanceof RuntimeBeanReference) {
        RuntimeBeanReference ref = (RuntimeBeanReference) value;
        return resolveReference(argName, ref);
    }
    
    // ......
    
       /**  
		 * <constructor-arg>
		 * 			<set value-type="java.lang.String">
		 * 				<value>1</value>
		 * 			</set>
		 * </constructor-arg>
		 * 通過上面set標籤中的value-type屬性對value進行類型轉換,
		 * 如果value-type屬性為空,那麼這裏不會進行類型轉換
		 */
   else if (value instanceof TypedStringValue) {
			TypedStringValue typedStringValue = (TypedStringValue) value;
			Object valueObject = evaluate(typedStringValue);
			try {
				Class<?> resolvedTargetType = resolveTargetType(typedStringValue);
				if (resolvedTargetType != null) {
					return this.typeConverter.convertIfNecessary(valueObject, resolvedTargetType);
				}
				else {
					return valueObject;
				}
			}
			catch (Throwable ex) {
				// Improve the message by showing the context.
				throw new BeanCreationException(
						this.beanDefinition.getResourceDescription(), this.beanName,
						"Error converting typed String value for " + argName, ex);
			}
		}
    // 省略後續代碼....
}

就我們上面的例子而言,經過resolveValueIfNecessary方法並不能產生實際的影響,因為在XML中我們沒有配置ref屬性或者value-type屬性。

畫圖如下:

第五段
public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    //  第一段代碼:到這裏已經得到了一個BeanWrapper,並對這個BeanWrapper做了初始化
    //  第二段代碼:明確了實例化當前這個Bean到底是靜態工廠還是實例工廠
    //	第三段代碼:以及從緩存中取過了對應了方法以及參數
    //  第四段代碼:明確了方法需要的最小的參數數量並對配置文件中的標籤屬性進行了一次解析

    // 進入第五段代碼分析
    
    // 保存在創建方法參數數組過程中發生的異常,如果最終沒有找到合適的方法,那麼將這個異常信息封裝后拋出
    LinkedList<UnsatisfiedDependencyException> causes = null;
    
    // 開始遍歷所有在第四段代碼中查詢到的符合要求的方法
    for (Method candidate : candidates) {
        // 方法的參數類型
        Class<?>[] paramTypes = candidate.getParameterTypes();
        // 候選的方法的參數必須要大於在第四段這推斷出來的最小參數個數
        if (paramTypes.length >= minNrOfArgs) {
            ArgumentsHolder argsHolder;
            // 必定為null,不考慮
            if (explicitArgs != null) {
                // Explicit arguments given -> arguments length must match exactly.
                if (paramTypes.length != explicitArgs.length) {
                    continue;
                }
                argsHolder = new ArgumentsHolder(explicitArgs);
            }
            else {
                // Resolved constructor arguments: type conversion and/or autowiring necessary.
                try {
                    // 獲取參數的具體名稱
                    String[] paramNames = null;
                    ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
                    if (pnd != null) {
                        paramNames = pnd.getParameterNames(candidate);
                    }
                    // 根據方法的參數名稱以及配置文件中配置的參數創建一個參數數組用於執行工廠方法
                    argsHolder = createArgumentArray(
                        beanName, mbd, resolvedValues, bw, paramTypes, paramNames, candidate, autowiring);
                }
                // 在創建參數數組的時候可能發生異常,這個時候的異常不能直接拋出,要確保所有的候選方法遍歷完成,只要有一個方法符合要求即可,但是如果遍歷完所有方法還是沒找到合適的構造器,那麼直接拋出這些異常
                catch (UnsatisfiedDependencyException ex) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Ignoring factory method [" + candidate + "] of bean '" + beanName + "': " + ex);
                    }
                    // Swallow and try next overloaded factory method.
                    if (causes == null) {
                        causes = new LinkedList<>();
                    }
                    causes.add(ex);
                    continue;
                }
                // 計算類型差異
                // 首先判斷bd中是寬鬆模式還是嚴格模式,目前看來只有@Bean標註的方法解析得到的Bean會使用嚴格模式來計算類型差異,其餘都是使用寬鬆模式
                // 嚴格模式下,
                int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
                                      argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
                // 選擇一個類型差異最小的方法
                if (typeDiffWeight < minTypeDiffWeight) {
                    factoryMethodToUse = candidate;
                    argsHolderToUse = argsHolder;
                    argsToUse = argsHolder.arguments;
                    minTypeDiffWeight = typeDiffWeight;
                    ambiguousFactoryMethods = null;
                }
   	// 省略後續代碼.......
    }

小總結:這段代碼的核心思想就是根據第四段代碼從配置文件中解析出來的參數構造方法執行所需要的實際參數數組。如果構建成功就代表這個方法可以用於實例化Bean,然後計算實際使用的參數跟方法上申明的參數的”差異值“,並在所有符合要求的方法中選擇一個差異值最小的方法

接下來,我們來分析方法實現的細節

  1. 構建方法使用的參數數組,也就是createArgumentArray方法,其源碼如下:
/* beanName:要實例化的Bean的名稱
 * mbd:對應Bean的BeanDefinition
 * resolvedValues:從配置文件中解析出來的並嘗試過類型轉換的參數
 * bw:在這裏主要就是用作類型轉換器
 * paramTypes:當前遍歷到的候選的方法的參數類型數組
 * paramNames:當前遍歷到的候選的方法的參數名稱
 * executable:當前遍歷到的候選的方法
 * autowiring:是否時自動注入
 */
private ArgumentsHolder createArgumentArray(
    String beanName, RootBeanDefinition mbd, @Nullable ConstructorArgumentValues resolvedValues,
			BeanWrapper bw, Class<?>[] paramTypes, @Nullable String[] paramNames, Executable executable,
			boolean autowiring) throws UnsatisfiedDependencyException {

		TypeConverter customConverter = this.beanFactory.getCustomTypeConverter();
		TypeConverter converter = (customConverter != null ? customConverter : bw);

		ArgumentsHolder args = new ArgumentsHolder(paramTypes.length);
		Set<ConstructorArgumentValues.ValueHolder> usedValueHolders = new HashSet<>(paramTypes.length);
		Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
		// 遍歷候選方法的參數,跟據方法實際需要的類型到resolvedValues中去匹配
		for (int paramIndex = 0; paramIndex < paramTypes.length; paramIndex++) {
			Class<?> paramType = paramTypes[paramIndex];
			String paramName = (paramNames != null ? paramNames[paramIndex] : "");
			
			ConstructorArgumentValues.ValueHolder valueHolder = null;
			if (resolvedValues != null) {
                // 首先,根據方法參數的下標到resolvedValues中找對應的下標的屬性
                // 如果沒找到再根據方法的參數名/類型去resolvedValues查找
				valueHolder = resolvedValues.getArgumentValue(paramIndex, paramType, paramName, usedValueHolders);
				// 如果都沒找到
                // 1.是自動注入並且方法的參數長度正好跟配置中的參數數量相等
                // 2.不是自動注入
                // 那麼按照順序一次選取
				if (valueHolder == null && (!autowiring || paramTypes.length == resolvedValues.getArgumentCount())) {
					valueHolder = resolvedValues.getGenericArgumentValue(null, null, usedValueHolders);
				}
			}
			// 也就是說在配置的參數中找到了合適的值可以應用於這個方法上
			if (valueHolder != null) {
				// 防止同一個參數被應用了多次
				usedValueHolders.add(valueHolder);
				Object originalValue = valueHolder.getValue();
				Object convertedValue;
                // 已經進行過類型轉換就不會需要再次進行類型轉換
				if (valueHolder.isConverted()) {
					convertedValue = valueHolder.getConvertedValue();
					args.preparedArguments[paramIndex] = convertedValue;
				}
				else {
					// 嘗試將配置的值轉換成方法參數需要的類型
					MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
					try {
                        // 進行類型轉換
						convertedValue = converter.convertIfNecessary(originalValue, paramType, methodParam);
					}
					catch (TypeMismatchException ex) {
						// 拋出UnsatisfiedDependencyException,在調用該方法處會被捕獲
					}
					Object sourceHolder = valueHolder.getSource();
                    // 只要是valueHolder存在,到這裏這個判斷必定成立
					if (sourceHolder instanceof ConstructorArgumentValues.ValueHolder) {
						Object sourceValue = ((ConstructorArgumentValues.ValueHolder) sourceHolder).getValue();
						args.resolveNecessary = true;
						args.preparedArguments[paramIndex] = sourceValue;
					}
				}

				args.arguments[paramIndex] = convertedValue;
				args.rawArguments[paramIndex] = originalValue;
			}
			else {
                // 方法執行需要參數,但是resolvedValues中沒有提供這個參數,也就是說這個參數是要自動注入到Bean中的
				MethodParameter methodParam = MethodParameter.forExecutable(executable, paramIndex);
				// 不是自動注入,直接拋出異常
				if (!autowiring) {
					// 拋出UnsatisfiedDependencyException,在調用該方法處會被捕獲
				}
				try {
                    // 自動注入的情況下,調用getBean獲取需要注入的Bean
					Object autowiredArgument =
							resolveAutowiredArgument(methodParam, beanName, autowiredBeanNames, converter);
                    // 把getBean返回的Bean封裝到本次方法執行時需要的參數數組中去
					args.rawArguments[paramIndex] = autowiredArgument;
					args.arguments[paramIndex] = autowiredArgument;
                    // 標誌這個參數是自動注入的
					args.preparedArguments[paramIndex] = new AutowiredArgumentMarker();
                    // 自動注入的情況下,在第二次調用時,需要重新處理,不能直接緩存
					args.resolveNecessary = true;
				}
				catch (BeansException ex) {
					// 拋出UnsatisfiedDependencyException,在調用該方法處會被捕獲
				}
			}
		}
		
    	// 註冊Bean之間的依賴關係
		for (String autowiredBeanName : autowiredBeanNames) {
			this.beanFactory.registerDependentBean(autowiredBeanName, beanName);
			if (logger.isDebugEnabled()) {
				logger.debug("Autowiring by type from bean name '" + beanName +
						"' via " + (executable instanceof Constructor ? "constructor" : "factory method") +
						" to bean named '" + autowiredBeanName + "'");
			}
		}

		return args;
	}

上面這段代碼說難也難,說簡單也簡單,如果要徹底看懂它到底幹了什麼還是很有難度的。簡單來說,它就是從第四段代碼解析出來的參數中查找當前的這個候選方法需要的參數。如果找到了,那麼嘗試對其進行類型轉換,將其轉換成符合方法要求的類型,如果沒有找到那麼還需要判斷當前方法的這個參數能不能進行自動注入,如果可以自動注入的話,那麼調用getBean得到需要的Bean,並將其注入到方法需要的參數中。

第六段
public BeanWrapper instantiateUsingFactoryMethod(
    String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
    //  第一段代碼:到這裏已經得到了一個BeanWrapper,並對這個BeanWrapper做了初始化
    //  第二段代碼:明確了實例化當前這個Bean到底是靜態工廠還是實例工廠
    //	第三段代碼:以及從緩存中取過了對應了方法以及參數
    //  第四段代碼:明確了方法需要的最小的參數數量並對配置文件中的標籤屬性進行了一次解析
    //  第五段代碼:到這裏已經確定了可以使用來實例化Bean的方法是哪個
    
	// 省略拋出異常的代碼,就是在對推斷出來的方法做驗證
    // 1.推斷出來的方法不能為null
    // 2.推斷出來的方法返回值不能為void
    // 3.推斷出來的方法不能有多個
		
    // 對參數進行緩存
      if (explicitArgs == null && argsHolderToUse != null) {
         argsHolderToUse.storeCache(mbd, factoryMethodToUse);
      }
   }

   try {
      Object beanInstance;

      if (System.getSecurityManager() != null) {
         final Object fb = factoryBean;
         final Method factoryMethod = factoryMethodToUse;
         final Object[] args = argsToUse;
         beanInstance = AccessController.doPrivileged((PrivilegedAction<Object>) () ->
               beanFactory.getInstantiationStrategy().instantiate(mbd, beanName, beanFactory, fb, factoryMethod, args),
               beanFactory.getAccessControlContext());
      }
      else {
          // 反射調用對應方法進行實例化
          // 1.獲取InstantiationStrategy,主要就是SimpleInstantiationStrategy跟CglibSubclassingInstantiationStrategy,其中CglibSubclassingInstantiationStrategy主要是用來處理beanDefinition中的lookupMethod跟replaceMethod。通常來說我們使用的就是SimpleInstantiationStrateg
          // 2.SimpleInstantiationStrateg就是單純的通過反射調用方法
         beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(
               mbd, beanName, this.beanFactory, factoryBean, factoryMethodToUse, argsToUse);
      }
		// beanWrapper在這裏對Bean進行了包裝
      bw.setBeanInstance(beanInstance);
      return bw;
   }
   catch (Throwable ex) {
      throw new BeanCreationException(mbd.getResourceDescription(), beanName,
            "Bean instantiation via factory method failed", ex);
   }
}

上面這段代碼的主要目的就是

  1. 緩存參數,原型可能多次創建同一個對象
  2. 反射調用推斷出來的factoryMethod

》通過構造函數實例化對象

如果上面你對使用factoryMethd進行實例化對象已經足夠了解的話,那麼下面的源碼分析基本沒有什麼很大區別,我們接着看看代碼。

首先,我們回到createBeanInstance方法中,

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    // 上面的代碼已經分析過了
    // 1.使用supplier來得到一個對象
    // 2.通過factotryMethod方法實例化一個對象
	
    // 看起來是不是有點熟悉,在使用factotryMethod創建對象時也有差不多這樣的一段代碼,看起來就是使用緩存好的方法直接創建一個對象
    boolean resolved = false;
    boolean autowireNecessary = false;
    
    // 不對這個參數進行討論,就認為一直為null
    if (args == null) {
        synchronized (mbd.constructorArgumentLock) {
            // bd中的resolvedConstructorOrFactoryMethod不為空,說明已經解析過構造方法了
            if (mbd.resolvedConstructorOrFactoryMethod != null) {
                // resolved標誌是否解析過構造方法
                resolved = true;
                autowireNecessary = mbd.constructorArgumentsResolved;
            }
        }
    }
    if (resolved) {
        // 構造函數已經解析過了,並且這個構造函數在調用時需要自動注入參數
        if (autowireNecessary) {
            // 此時部分解析好的參數已經存在了beanDefinition中,並且構造函數也在bd中
            // 那麼在這裏只會從緩存中去取構造函數以及參數然後反射調用
            return autowireConstructor(beanName, mbd, null, null);
        }
        else {
            // 這裏就是直接反射調用空參構造
            return instantiateBean(beanName, mbd);
        }
    }
	// 推斷出能夠使用的需要參數的構造函數
    Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
    // 在推斷出來的構造函數中選取一個合適的方法來進行Bean的實例化
    // ctors不為null:說明存在1個或多個@Autowired標註的方法
    // mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR:說明是自動注入
    // mbd.hasConstructorArgumentValues():配置文件中配置了構造函數要使用的參數
    // !ObjectUtils.isEmpty(args):外部傳入的參數,必定為null,不多考慮
    // 上面的條件只要滿足一個就會進入到autowireConstructor方法
    // 第一個條件滿足,那麼通過autowireConstructor在推斷出來的構造函數中再進一步選擇一個差異值最小的,參數最長的構造函數
    // 第二個條件滿足,說明沒有@Autowired標註的方法,但是需要進行自動注入,那麼通過autowireConstructor會去遍歷類中申明的所有構造函數,並查找一個差異值最小的,參數最長的構造函數
    // 第三個條件滿足,說明不是自動注入,那麼要通過配置中的參數去類中申明的所有構造函數中匹配
    // 第四個必定為null,不考慮
    if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
        mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
        return autowireConstructor(beanName, mbd, ctors, args);
    }
	
    // 反射調用空參構造
    return instantiateBean(beanName, mbd);
}

因為autowireConstructor方法的執行邏輯跟instantiateUsingFactoryMethod方法的執行邏輯基本一致,只是將Method對象換成了Constructor對象,所以對這個方法我不再做詳細的分析。

我們主要就看看determineConstructorsFromBeanPostProcessors這個方法吧,這個方法的主要目的就是推斷出候選的構造方法。

determineConstructorsFromBeanPostProcessors方法做了什麼?

// 實際調用的就是AutowiredAnnotationBeanPostProcessor中的determineCandidateConstructors方法
// 這個方法看起來很長,但實際確很簡單,就是通過@Autowired註解確定哪些構造方法可以作為候選方法,其實在使用factoryMethod來實例化對象的時候也有這種邏輯在其中,後續在總結的時候我們對比一下
public Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, final String beanName)
			throws BeanCreationException {

		// 這裏做的事情很簡單,就是將@Lookup註解標註的方法封裝成LookupOverride添加到BeanDefinition中的methodOverrides屬性中,如果這個屬性不為空,在實例化對象的時候不能選用SimpleInstantiationStrateg,而要使用CglibSubclassingInstantiationStrategy,通過cglib代理給方法加一層攔截了邏輯
    	// 避免重複檢查
		if (!this.lookupMethodsChecked.contains(beanName)) {
			try {
				ReflectionUtils.doWithMethods(beanClass, method -> {
					Lookup lookup = method.getAnnotation(Lookup.class);
					if (lookup != null) {
						Assert.state(this.beanFactory != null, "No BeanFactory available");				// 將@Lookup註解標註的方法封裝成LookupOverride
						LookupOverride override = new LookupOverride(method, lookup.value());
						try {
                            // 添加到BeanDefinition中的methodOverrides屬性中
							RootBeanDefinition mbd = (RootBeanDefinition)
									this.beanFactory.getMergedBeanDefinition(beanName);
							mbd.getMethodOverrides().addOverride(override);
						}
						catch (NoSuchBeanDefinitionException ex) {
							throw new BeanCreationException(beanName,
									"Cannot apply @Lookup to beans without corresponding bean definition");
						}
					}
				});
			}
			catch (IllegalStateException ex) {
				throw new BeanCreationException(beanName, "Lookup method resolution failed", ex);
			}
			this.lookupMethodsChecked.add(beanName);
		}
		
    // 接下來要開始確定到底哪些構造函數能被作為候選者
		
    // 先嘗試從緩存中獲取
		Constructor<?>[] candidateConstructors = this.candidateConstructorsCache.get(beanClass);
		if (candidateConstructors == null) {
			// Fully synchronized resolution now...
			synchronized (this.candidateConstructorsCache) {
				candidateConstructors = this.candidateConstructorsCache.get(beanClass);、
                    // 緩存中無法獲取到,進入正式的推斷過程
				if (candidateConstructors == null) {
					Constructor<?>[] rawCandidates;
					try {
                        // 第一步:先查詢這個類所有的構造函數,包括私有的
						rawCandidates = beanClass.getDeclaredConstructors();
					}
					catch (Throwable ex) {
						// 省略異常信息
					}
					List<Constructor<?>> candidates = new ArrayList<>(rawCandidates.length);
                    // 保存添加了Autowired註解並且required屬性為true的構造方法
					Constructor<?> requiredConstructor = null;
                    // 空參構造
					Constructor<?> defaultConstructor = null;
                    // 看方法註釋上說明的,這裏除非是kotlin的類,否則必定為null,不做過多考慮,我們就將其當作null
					Constructor<?> primaryConstructor = BeanUtils.findPrimaryConstructor(beanClass);
					int nonSyntheticConstructors = 0;
                    // 對類中的所有構造方法進行遍歷
					for (Constructor<?> candidate : rawCandidates) {
                        // 非合成方法
						if (!candidate.isSynthetic()) {
							nonSyntheticConstructors++;
						}
						else if (primaryConstructor != null) {
							continue;
						}
                        // 查詢方法上是否有Autowired註解
						AnnotationAttributes ann = findAutowiredAnnotation(candidate);
						if (ann == null) {
                            // userClass != beanClass說明這個類進行了cglib代理
							Class<?> userClass = ClassUtils.getUserClass(beanClass);
							if (userClass != beanClass) {
								try {
                                    // 如果進行了cglib代理,那麼在父類上再次查找Autowired註解
									Constructor<?> superCtor =
											userClass.getDeclaredConstructor(candidate.getParameterTypes());
									ann = findAutowiredAnnotation(superCtor);
								}
								catch (NoSuchMethodException ex) {
									// Simply proceed, no equivalent superclass constructor found...
								}
							}
						}
                        // 說明當前的這個構造函數上有Autowired註解
						if (ann != null) {
							if (requiredConstructor != null) {
								// 省略異常拋出
							}
                            // 獲取Autowired註解中的required屬性
							boolean required = determineRequiredStatus(ann);
							if (required) {
                                // 類中存在多個@Autowired標註的方法,並且某個方法的@Autowired註解上被申明了required屬性要為true,那麼直接報錯
								if (!candidates.isEmpty()) {
								// 省略異常拋出
								}
								requiredConstructor = candidate;
							}
                            // 添加到集合中,這個集合存儲的都是被@Autowired註解標註的方法
							candidates.add(candidate);
						}
                        // 空參構造函數
						else if (candidate.getParameterCount() == 0) {
							defaultConstructor = candidate;
						}
					}
					if (!candidates.isEmpty()) {
						// 存在多個被@Autowired標註的方法
                        // 並且所有的required屬性被設置成了false (默認為true)
						if (requiredConstructor == null) {
                            // 存在空參構造函數,注意,空參構造函數可以不被@Autowired註解標註
							if (defaultConstructor != null) {
                                // 將空參構造函數也加入到候選的方法中去
								candidates.add(defaultConstructor);
							}
							// 省略日誌打印
						}
                        
						candidateConstructors = candidates.toArray(new Constructor<?>[0]);
					}
                    // 也就是說,類中只提供了一個構造函數,並且這個構造函數不是空參構造函數
					else if (rawCandidates.length == 1 && rawCandidates[0].getParameterCount() > 0) {
						candidateConstructors = new Constructor<?>[] {rawCandidates[0]};
					}
                    // 省略中間兩個判斷,primaryConstructor必定為null,不考慮
					// .....
					}
					else {
                        // 說明無法推斷出來
						candidateConstructors = new Constructor<?>[0];
					}
					this.candidateConstructorsCache.put(beanClass, candidateConstructors);
				}
			}
		}
		return (candidateConstructors.length > 0 ? candidateConstructors : null);
	}

這裏我簡單總結下這個方法的作用

  1. 獲取到類中的所有構造函數

  2. 查找到被@Autowired註解標註的構造函數

    • 如果存在多個被@Autowired標註的構造函數,並且其required屬性沒有被設置為true,那麼返回這些被標註的函數的集合(空參構造即使沒有添加@Autowired也會被添加到集合中)
    • 如果存在多個被@Autowired標註的構造函數,並且其中一個的required屬性被設置成了true,那麼直接報錯
    • 如果只有一個構造函數被@Autowired標註,並且其required屬性被設置成了true,那麼直接返回這個構造函數
  3. 如果沒有被@Autowired標註標註的構造函數,但是類中有且只有一個構造函數,並且這個構造函數不是空參構造函數,那麼返回這個構造函數

  4. 上面的條件都不滿足,那麼determineCandidateConstructors這個方法就無法推斷出合適的構造函數了

可以看到,通過AutowiredAnnotationBeanPostProcessordetermineCandidateConstructors方法可以處理構造函數上的@Autowired註解。

但是,請注意,這個方法並不能決定到底使用哪個構造函數來創建對象(即使它只推斷出來一個,也不一定能夠使用),它只是通過@Autowired註解來確定構造函數的候選者,在構造函數都沒有添加@Autowired註解的情況下,這個方法推斷不出來任何方法。真正確定到底使用哪個構造函數是交由autowireConstructor方法來決定的。前文已經分析過了instantiateUsingFactoryMethod方法,autowireConstructor的邏輯基本跟它一致,所以這裏不再做詳細的分析。

factoryMethod跟構造函數的比較

整體邏輯比較

從上圖中可以看到,整體邏輯上它們並沒有什麼區別,只是查找的對象從factoryMethod換成了構造函數

執行細節比較

細節的差異主要體現在推斷方法上

  • 推斷factoryMethod
  • 推斷構造函數

它們之間的差異我已經在圖中標識出來了,主要就是兩點

  1. 通過構造函數實例化對象,多了一層處理,就是要處理構造函數上的@Autowired註解以及方法上的@LookUp註解(要決定選取哪一種實例化策略,SimpleInstantiationStrategy/CglibSubclassingInstantiationStrategy
  2. 在最終的選取也存在差異,對於facotyMehod而言,在寬鬆模式下(除ConfigurationClassBeanDefinition外,也就是掃描@Bean得到的BeanDefinition,都是寬鬆模式),會選取一個最精準的方法,在嚴格模式下,會選取一個參數最長的方法
  3. 對於構造函數而言,會必定會選取一個參數最長的方法

關於計算類型差異的補充內容

思考了很久,我還是決定再補充一些內容,就是關於上面兩幅圖的最後一步,對應的核心代碼如下:

int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
                      argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));

if (typeDiffWeight < minTypeDiffWeight) {
    factoryMethodToUse = candidate;
    argsHolderToUse = argsHolder;
    argsToUse = argsHolder.arguments;
    minTypeDiffWeight = typeDiffWeight;
    ambiguousFactoryMethods = null;
}
  1. 判斷bd是嚴格模式還是寬鬆模式,上面說過很多次了,bd默認就是寬鬆模式,只要在ConfigurationClassBeanDefinition中使用嚴格模式,也就是掃描@Bean標註的方法註冊的bd(對應的代碼可以參考:org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod方法)

    我們再看看嚴格模式跟寬鬆模式在計算差異值時的區別

    • 寬鬆模式
    public int getTypeDifferenceWeight(Class<?>[] paramTypes) {
        // 計算實際使用的參數跟方法申明的參數的差異值
    			int typeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.arguments);
        // 計算沒有經過類型轉換的參數跟方法申明的參數的差異值
    			int rawTypeDiffWeight = MethodInvoker.getTypeDifferenceWeight(paramTypes, this.rawArguments) - 1024;
    			return (rawTypeDiffWeight < typeDiffWeight ? rawTypeDiffWeight : typeDiffWeight);
    		}
    
    public static int getTypeDifferenceWeight(Class<?>[] paramTypes, Object[] args) {
        int result = 0;
        for (int i = 0; i < paramTypes.length; i++) {
            // 在出現類型轉換時,下面這個判斷才會成立,也就是在比較rawArguments跟paramTypes的差異時才可能滿足這個條件
            if (!ClassUtils.isAssignableValue(paramTypes[i], args[i])) {
                return Integer.MAX_VALUE;
            }
            if (args[i] != null) {
                Class<?> paramType = paramTypes[i];
                Class<?> superClass = args[i].getClass().getSuperclass();
                while (superClass != null) {
                    // 如果我們傳入的值是方法上申明的參數的子類,那麼每多一層繼承關係,差異值加2
                    if (paramType.equals(superClass)) {
                        result = result + 2;
                        superClass = null;
                    }
                    else if (ClassUtils.isAssignable(paramType, superClass)) {
                        result = result + 2;
                        superClass = superClass.getSuperclass();
                    }
                    else {
                        superClass = null;
                    }
                }
                // 判斷方法的參數是不是一個接口,如果是,那麼差異值加1
                if (paramType.isInterface()) {
                    result = result + 1;
                }
            }
        }
        return result;
    }
    
    • 嚴格模式(主要應用於@Bean標註的方法對應的BeanDefinition)
    public int getAssignabilityWeight(Class<?>[] paramTypes) {
        // 嚴格模式下,只有三種返回值
        // 1.Integer.MAX_VALUE,經過類型轉換后還是不符合要求,返回最大的類型差異
        // 因為解析后的參數可能返回一個NullBean(創建對象的方法返回了null,Spring會將其包裝成一個NullBean),不過一般不會出現這種情況,所以我們可以當這種情況不存在
    			for (int i = 0; i < paramTypes.length; i++) {
    				if (!ClassUtils.isAssignableValue(paramTypes[i], this.arguments[i])) {
    					return Integer.MAX_VALUE;
    				}
    			}
        // 2.Integer.MAX_VALUE - 512,進行過了類型轉換才符合要求
    			for (int i = 0; i < paramTypes.length; i++) {
    				if (!ClassUtils.isAssignableValue(paramTypes[i], this.rawArguments[i])) {
    					return Integer.MAX_VALUE - 512;
    				}
    			}
        // 3.Integer.MAX_VALUE - 1024,沒有經過類型轉換就已經符合要求了,返回最小的類型差異
    			return Integer.MAX_VALUE - 1024;
    		}
    

    首先,不管是factoryMethod還是constructor,都是採用上面的兩個方法來計算類型差異,但是正常來說,只有factoryMethod會採用到嚴格模式(除非程序員手動干預,比如通過Bean工廠後置處理器修改了bd中的屬性,這樣通常來說沒有很大意義)

    所以我們分為三種情況討論

    1、factoryMethod+寬鬆模式

    這種情況下,會選取一個最精確的方法,同時方法的參數要盡量長

    測試代碼:

    public class FactoryObject {
    
    	public DmzService getDmz() {
    		System.out.println(0);
    		return new DmzService();
    	}
    
    	public DmzService getDmz(OrderService indexService) {
    		System.out.println(1);
    		return new DmzService();
    	}
    
    	public DmzService getDmz(OrderService orderService, IndexService indexService) {
    		System.out.println(2);
    		return new DmzService();
    	}
    
        public DmzService getDmz(OrderService orderService, IndexService indexService,IA ia) {
            System.out.println(3);
            return new DmzService();
        }
    }
    
    public class ServiceImpl implements IService {
    }
    
    public class IAImpl implements IA {
    }
    
    
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"
    default-autowire="constructor"><!--必須要開啟自動注入,並且是通過構造函數進行自動注入,否則選用無參構造-->
    
    	<!--factoryObject 提供了創建對象的方法-->
    	<bean id="factoryObject" class="com.dmz.spring.instantiation.service.FactoryObject"/>
    
    	<bean class="com.dmz.spring.instantiation.service.OrderService" id="orderService"/>
    
    	<bean id="dmzService" factory-bean="factoryObject" factory-method="getDmz" />
    
    	<bean class="com.dmz.spring.instantiation.service.ServiceImpl" id="iService"/>
    
    	<bean class="com.dmz.spring.instantiation.service.IndexService" id="indexService"/>
    
    </beans>
    
    /**
     * @author 程序員DMZ
     * @Date Create in 23:59 2020/6/1
     * @Blog https://daimingzhi.blog.csdn.net/
     */
    public class XMLMain {
    	public static void main(String[] args) {
    		ClassPathXmlApplicationContext cc =
    				new ClassPathXmlApplicationContext("application.xml");
    	}
    }
    

    運行程序發現,選用了第三個(getDmz(OrderService orderService, IndexService indexService))構造方法。雖然最後一個方法的參數更長,但是因為其方法申明的參數上存在接口,所以它的差異值會大於第三個方法,因為不會被選用

    2、factoryMethod+嚴格模式

    這種情況下,會選取一個參數盡量長的方法

    測試代碼:

    /**
     * @author 程序員DMZ
     * @Date Create in 6:28 2020/6/1
     * @Blog https://daimingzhi.blog.csdn.net/
     */
    @ComponentScan("com.dmz.spring.instantiation")
    @Configuration
    public class Config {
    
    	@Bean
    	public DmzService dmzService() {
    		System.out.println(0);
    		return new DmzService();
    	}
    	@Bean
    	public DmzService dmzService(OrderService indexService) {
    		System.out.println(1);
    		return new DmzService();
    	}
    	@Bean
    	public DmzService dmzService(OrderService orderService, IndexService indexService) {
    		System.out.println(2);
    		return new DmzService();
    	}
    	@Bean
    	public DmzService dmzService(OrderService orderService, IndexService indexService, IA ia) {
    		System.out.println("config " +3);
    		return new DmzService();
    	}
    
    	@Bean
    	public DmzService dmzService(OrderService orderService, IndexService indexService, IA ia, IService iService) {
    		System.out.println("config " +4);
    		return new DmzService();
    	}
    }
    
    /**
     * @author 程序員DMZ
     * @Date Create in 6:29 2020/6/1
     * @Blog https://daimingzhi.blog.csdn.net/
     */
    public class Main {
    	public static void main(String[] args) {
    		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    		ac.register(Config.class);
    		ac.refresh();
    	}
    }
    

    運行程序,發現選用了最後一個構造函數,這是因為在遍歷候選方法時,會先遍歷參數最長的,而在計算類型差異時,因為嚴格模式下,上面所有方法的差異值都是一樣的,都會返回Integer.MAX_VALUE - 1024。實際上,在不進行手動干預的情況下,都會返滬這個值。

    3、構造函數+寬鬆模式

    這種情況下,也會選取一個參數盡量長的方法

    之所以會這樣,主要是因為在autowireConstructor方法中進行了一次短路判斷,如下所示:

    在上圖中,如果已經找到了合適的方法,那麼直接就不會再找了,而在遍歷的時候是從參數最長的方法開始遍歷的,測試代碼如下:

    @Component
    public class DmzService {
    	
        // 沒有添加@Autowired註解,也會被當作候選方法
    	public DmzService(){
    		System.out.println(0);
    	}
    
    	@Autowired(required = false)
    	public DmzService(OrderService orderService) {
    		System.out.println(1);
    	}
    
    	@Autowired(required = false)
    	public DmzService(OrderService orderService, IService iService) {
    		System.out.println(2);
    	}
    
    	@Autowired(required = false)
    	public DmzService(OrderService orderService, IndexService indexService, IService iService,IA ia) {
    		System.out.println("DmzService "+3);
    	}
    }
    
    /**
     * @author 程序員DMZ
     * @Date Create in 6:29 2020/6/1
     * @Blog https://daimingzhi.blog.csdn.net/
     */
    public class Main {
    	public static void main(String[] args) {
    		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
    		ac.register(Config.class);
    		ac.refresh();
    	}
    }
    

    這篇文章就到這裏啦~~!

    文章很長,希望你耐心看完,碼字不易,如果有幫助到你的話點個贊吧~!

掃描下方二維碼,關注我的公眾號,更多精彩文章在等您!~~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

日本防疫、核安難兩全 民眾與科學家齊倡停止核電運轉

文:宋瑞文(媽媽監督核電廠聯盟特約撰述)

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

RocketMQ系列(五)廣播與延遲消息

今天要給大家介紹RocketMQ中的兩個功能,一個是“廣播”,這個功能是比較基礎的,幾乎所有的mq產品都是支持這個功能的;另外一個是“延遲消費”,這個應該算是RocketMQ的特色功能之一了吧。接下來,我們就分別看一下這兩個功能。

廣播

廣播是把消息發送給訂閱了這個主題的所有消費者。這個定義很清楚,但是這裏邊的知識點你都掌握了嗎?咱們接着說“廣播”的機會,把消費者這端的內容好好和大家說說。

  • 首先,消費者端的概念中,最大的應該是消費者組,一個消費者組中可以有多個消費者,這些消費者必須訂閱同一個Topic。
  • 那麼什麼算是一個消費者呢?我們在寫消費端程序時,看到了setConsumeThreadMax這個方法,設置消費者的線程數,難道一個線程就是一個消費者?錯!這裏的一個消費者是一個進程,你可以理解為ip+端口。如果在同一個應用中,你實例化了兩個消費者,這兩個消費者配置了相同的消費者組名稱,那麼應用程序啟動時會報錯的,這裏不給大家演示了,感興趣的小夥伴私下里試一下吧。
  • 同一個消息,可以被不同的消費者組同時消費。假設,我有兩個消費者組cg-1和cg-2,這兩個消費者組訂閱了同一個Topic,那麼這個Topic的消息會被cg-1和cg-2同時消費。那這是不是廣播呢?錯!當然不是廣播,廣播是同一個消費者組中的多個消費者都消費這個消息。如果配置的不是廣播,像前幾個章節中的那樣,一個消息只能被一個消費者組消費一次。

好了,說了這麼多,我們實驗一下吧,先把消費者配置成廣播,如下:

@Bean(name = "broadcast", initMethod = "start",destroyMethod = "shutdown")
public DefaultMQPushConsumer broadcast() throws MQClientException {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("broadcast");
    consumer.setNamesrvAddr("192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876;");
    consumer.subscribe("cluster-topic","*");
    consumer.setMessageModel(MessageModel.BROADCASTING);
    consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
        for (MessageExt msg : msgs) {
            System.out.println(new String(msg.getBody()));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    });
    return consumer;
}

  • 其中,NameServer,訂閱的Topic都沒有變化。
  • 注意其中consumer.setMessageModel(MessageModel.BROADCASTING);這段代碼,設置消費者為廣播。咱們可以看一下,MessageModel枚舉中只有兩個值,BROADCASTINGCLUSTERING,默認為CLUSTERING

因為要測試廣播,所以我們要啟動多個消費者,還記得什麼是消費者嗎?對了,一個ip+端口算是一個消費者,在這裏我們啟動兩個應用,端口分別是8080和8081。發送端的程序不變,如下:

@Test
public void producerTest() throws Exception {

    for (int i = 0;i<5;i++) {
        MessageExt message = new MessageExt();
        message.setTopic("cluster-topic");
        message.setKeys("key-"+i);
        message.setBody(("this is simpleMQ,my NO is "+i+"---"+new Date()).getBytes());
        SendResult sendResult = defaultMQProducer.send(message);
        System.out.println("i=" + i);
        System.out.println("BrokerName:" + sendResult.getMessageQueue().getBrokerName());
    }
}

我們執行一下發送端的程序,日誌如下:

i=0
BrokerName:broker-a
i=1
BrokerName:broker-a
i=2
BrokerName:broker-b
i=3
BrokerName:broker-b
i=4
BrokerName:broker-b

再來看看8080端口的應用後台打印出來的日誌:

消費了5個消息,再看看8081的後台打印的日誌,

也消費了5個。兩個消費者同時消費了消息,這就是廣播。有的小夥伴可能會有疑問了,如果不設置廣播,會怎麼樣呢?私下里實驗一下吧,上面的程序中,只要把設置廣播的那段代碼註釋掉就可以了。運行的結果當然是只有一個消費者可以消費消息。

延遲消息

延遲消息是指消費者過了一個指定的時間后,才去消費這個消息。大家想象一個電商中場景,一個訂單超過30分鐘未支付,將自動取消。這個功能怎麼實現呢?一般情況下,都是寫一個定時任務,一分鐘掃描一下超過30分鐘未支付的訂單,如果有則被取消。這種方式由於每分鐘查詢一下訂單,一是時間不精確,二是查庫效率比較低。這個場景使用RocketMQ的延遲消息最合適不過了,我們看看怎麼發送延遲消息吧,發送端代碼如下:

@Test
public void producerTest() throws Exception {

    for (int i = 0;i<1;i++) {
        MessageExt message = new MessageExt();
        message.setTopic("cluster-topic");
        message.setKeys("key-"+i);
        message.setBody(("this is simpleMQ,my NO is "+i+"---"+new Date()).getBytes());
        message.setDelayTimeLevel(2);
        SendResult sendResult = defaultMQProducer.send(message);
        System.out.println("i=" + i);
        System.out.println("BrokerName:" + sendResult.getMessageQueue().getBrokerName());
    }
}
  • 我們只是增加了一句message.setDelayTimeLevel(2);
  • 為了方便,這次我們只發送一個消息。

setDelayTimeLevel是什麼意思,設置的是2,難道是2s后消費嗎?怎麼參數也沒有時間單位呢?如果我要自定義延遲時間怎麼辦?我相信很多小夥伴都有這樣的疑問,我也是帶着這樣的疑問查了很多資料,最後在RocketMQ的Github官網上看到了說明,

  • 在RocketMQ的源碼中,有一個MessageStoreConfig類,這個類中定義了延遲的時間,我們看一下,
// org/apache/rocketmq/store/config/MessageStoreConfig.java
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
  • 我們在程序中設置的是2,那麼這個消息將在5s以後被消費。
  • 目前RocketMQ還不支持自定義延遲時間,延遲時間只能從上面的時間中選。如果你非要定義一個時間怎麼辦呢?RocketMQ是開源的,下載代碼,把上面的時間改一下,再打包部署,就OK了。

再看看消費端的代碼,

@Bean(name = "broadcast", initMethod = "start",destroyMethod = "shutdown")
public DefaultMQPushConsumer broadcast() throws MQClientException {
    DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("broadcast");
    consumer.setNamesrvAddr("192.168.73.130:9876;192.168.73.131:9876;192.168.73.132:9876;");
    consumer.subscribe("cluster-topic","*");
    consumer.setMessageModel(MessageModel.BROADCASTING);
    consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
        for (MessageExt msg : msgs) {
            Date now = new Date();
            System.out.println("消費時間:"+now);

            Date msgTime = new Date();
            msgTime.setTime(msg.getBornTimestamp());
            System.out.println("消息生成時間:"+msgTime);

            System.out.println(new String(msg.getBody()));
        }
        return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
    });
    return consumer;
}
  • 我們還是使用廣播的模式,沒有變。
  • 打印出了當前的時間,這個時間就是消費的時間。
  • 通過msg.getBornTimestamp()方法,獲得了消息的生成時間,也打印出來,看看是不是延遲5s。

啟動兩個消費者8080和8081,發送消息,再看看消費者的後台日誌,

消費時間:Thu Jun 11 14:45:53 CST 2020
消息生成時間:Thu Jun 11 14:45:48 CST 2020
this is simpleMQ,my NO is 0---Thu Jun 11 14:45:47 CST 2020

我們看到消費時間比生成時間晚5s,符合我們的預期。這個功能還是比較實用的,如果能夠自定義延遲時間就更好了。

總結

RocketMQ的這兩個知識點還是比較簡單的,大家要分清楚什麼是消費者組,什麼是消費者,什麼是消費者線程。另外就是延遲消息是不支持自定義的,大家可以在Github上看一下源碼。好了~今天就到這裏了。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

加拿大最後完整冰棚崩塌 面積縮 43% 、分裂出冰山面積比曼哈頓大

摘錄自2020年8月11日立場新聞報導

今年7月的最後兩日,加拿大北極群島中最北的埃爾斯米爾島(Ellesmere Island)米爾恩冰棚(Milne Ice Shelf)崩塌,使加拿大最後一個完整的冰棚面積減少了 43%,該冰塊其後漂入北冰洋,進一步分成兩大塊,並被歐洲太空總署哥白尼計劃哨兵衛星所拍攝到。

塌下的冰棚約有 80 平方公里,比 60 平方公里的曼哈頓更大。根據加拿大極冰局,高於正常的空氣溫度、離岸風和冰棚前的開揚水域,都是造成冰棚破裂的原因。由於冰棚破裂,北極最後一個已知的棚外湖 (epishelf lake) 可能消失。

冰棚可像活塞一樣,減緩與阻擋冰蓋與融冰水流入海洋的速度,有助於限制全球海水水位上升。另外,冰棚崩塌,造成的冰山可能會危害當地的航運業。

棚外湖是困於浮在海水上冰棚的淡水水體。當米爾恩冰棚崩塌時,可能將棚外湖以及其所含的淡水送進北冰洋,影響其鹹度,不過專家不確定其影響程度,情況將取決於米爾恩冰棚剩餘部分的完整性。

氣候變遷
國際新聞
加拿大
北極
冰棚
冰山融化

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

磚頭也能變電池?儲能研究揭最新奈米科技 關鍵因素在「多孔」

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

Asp.Net Core入門之自定義服務註冊

談到服務註冊,首先我們先了解一下服務註冊時使用的三種方式,也代表了不同的服務生命周期

1  AddTransient
 
2  AddScoped
 
3  AddSingleton
AddSingleton生命周期最長,其生命周期範圍描述為:從應用程序啟動到應用程序結束。在第一次請求時會創建一個實例,之後的每次請求都會使用同一個實例。
AddTransient生命周期最短,在服務請求時會創建一個實例,服務請求結束生命周期即結束,之後的每一次請求都會創建不同的實例。
AddSingleton生命周期介於上述兩者之間,這裏用客戶端請求會話的概念來描述比較清晰一點,它也是在服務請求時創建實例,但是在同一個會話周期內,之後的每次請求都會使用同一個實例,直至會話結束才會創建新的實例。
 

ASP.Net Core框架支持我們以如下方式註冊我們自己的服務。

services.AddScoped<ITest, Test>();

其中第一個泛型類型(如:ITest)表示將要從容器中請求的類型(通常是一個接口)。第二個泛型類型(如:Test)表示將由容器實例化並且用於完成這些請求的具體實現類。

具體我們一起看下面的例子:

首先,我們創建一個需要實現查詢功能的服務接口ITest

   public interface ITest
    {
        Task<string> Get();
    }

然後,我們創建功能類Test實現這個接口

 1     public class Test : ITest
 2     {
 3         private readonly ILogger logger;
 4         public Test(ILogger<Test> _logger)
 5         {
 6             logger = _logger;
 7         }
 8         public Task<string> Get()
 9         {
10             logger.LogInformation("自定義服務查詢");
11             return Task.FromResult("Hello World");
12         }
13     }

最後,我們需要我們自己的服務註冊到容器中。

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<ITest, Test>();
        }

以上我們便簡單完成了自定義服務的註冊。

隨後我這裏創建了一個Controller用以使用該服務。

 1     [Route("api/[controller]")]
 2     [ApiController]
 3     public class ValuesController : ControllerBase
 4     {
 5         //聲明服務
 6         private readonly ITest service;
 7 
 8         /// <summary>
 9         /// 通過構造函數的方式注入自定義服務類
10         /// </summary>
11         /// <param name="_service"></param>
12         public ValuesController(ITest _service)
13         {
14             service = _service;
15         }
16 
17         /// <summary>
18         /// 調用服務中實現的Get方法
19         /// </summary>
20         /// <returns></returns>
21         [HttpGet]
22         public Task<string> Get()
23         {
24             return service.Get();
25         }
26      }

ASP.Net Core框架默認支持我們以構造函數的方式注入我們的服務以使用。

我想寫到這裏,大家也會有疑問,如果我們有很多service,這樣一個個註冊寫起來代碼很低效,這裏我們簡單給大家介紹一種批量註冊的方式:

這裏我們創建了一個批量註冊服務派生類:

 1 public static class ServiceExtensions
 2     {
 3         /// <summary>
 4         /// 批量註冊程序集下的服務類
 5         /// </summary>
 6         /// <param name="services"></param>
 7         public static IServiceCollection AddBatchServices(this IServiceCollection services)
 8         {
 9             //根據指定程序集名稱獲取待註冊服務
10             var batchServices = GetConfigureClass("WebApiApplication");
11             foreach (var type in batchServices)
12             {
13                 type.Value.ToList().ForEach(i =>
14                 {
15                     //註冊服務類
16                     services.AddScoped(i, type.Key);
17                 });
18             }
19             return services;
20         }
21 
22         /// <summary>
23         /// 根據程序集名稱獲取自定義服務
24         /// </summary>
25         /// <param name="assembly"></param>
26         /// <returns></returns>
27         public static Dictionary<Type, Type[]> GetConfigureClass(string assembly)
28         {
29             Dictionary<Type, Type[]> dic = new Dictionary<Type, Type[]>();
30             if (!string.IsNullOrEmpty(assembly))
31             {
32                 //獲取程序集對應的類型
33                 Assembly dll = Assembly.LoadFrom(assembly);
34                 List<Type> lstType = dll.GetTypes().ToList();
35                 lstType.ForEach(x =>
36                 {
37                     //篩選滿足條件的服務類
38                     if (x.IsClass && x.GetInterfaces().Length > 0)
39                     {
40                         dic.Add(x, x.GetInterfaces());
41                     }
42                 });
43             }
44             return dic;
45         }
46    }

然後我們ConfigureServices方法中註冊:

        public void ConfigureServices(IServiceCollection services)
        {
            //批量註冊
            services.AddBatchServices();
        }

對於批量註冊,ASP.Net Core允許我們更換默認的IOC容器,感興趣的同學可以試試AutoFac容器支持的程序集掃描式註冊。

註冊我們自己的服務,往往在項目開發過程中是必要的,希望以上簡單的分享能給需要的小夥伴們帶來一點收貨。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

野火包圍 舊金山灣區空污嚴重危害居民健康

摘錄自2020年8月25日中央社報導

舊金山灣區四周被野火包圍,有害的煙塵讓許多民眾呼吸困難、眼睛發炎,原本有呼吸系統宿疾者病情加劇。舊金山灣區空氣品質管理區(Bay Area Air Quality Management District)指出,野火將有害的煙塵送入天空,讓今天的空氣品質盤旋在不健康的指數。

舊金山多個城市的細懸浮微粒(PM2.5)都達到對健康有威脅的程度,並讓許多民眾難以呼吸。舊金山紀事報網站今(25日)指出,許多居住在火災區附近的醫生通報,有呼吸系統宿疾的患者在上週末病情加劇,也有醫生認為未來幾週內都將陸續出現更多呼吸系統疾病相關的病人。

公衛專家警告,惡化的空氣品質伴隨著武漢肺炎(COVID-19)疫情,會讓原本就有氣喘與肺部疾病的患者更不舒服,同時可能讓染疫的民眾更虛弱。

污染治理
國際新聞
美國
加州
舊金山
空氣污染
空氣品質
火災

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心

國產SUV新貴GS8大熱 那麼更便宜的GS7會怎樣呢?

GS7的側面造型和他大哥在C柱之前相似度較高,但是因為GS7的軸距較短,所以GS7看起來顯得緊湊很多。不過GS7的側窗並沒有沿襲GS8隱藏式的D柱設計,而是被銀色的鍍鉻裝飾條完整的包圍起來。對於這種設計,小編覺得GS7前臉都和大哥相似了,如果這部分再在用相同的設計,那麼設計師肯定會被吐槽偷懶的,其實這樣的設計也好,畢竟國人也很喜歡鍍鉻裝飾。

GS8無疑是2016年一款極其重要的車型,但是GS8全係為七座車型,有些家庭用不上那麼多的座位,買來之後便成了擺設。這一點讓喜歡GS8的人有點拿不定主意了。

所以大家都在議論着,要是GS8有個五座車型就好了。其實這點不用擔心的,因為傳祺在推出GS8的時候就已經有了一些關於GS7的傳聞,只不過,這次的GS7,終於要以真面目示眾了。

說到GS7那麼必須要提到北美車展,北美車展是世界五大車展之一,同時也是北美洲規模最大的國際車展,此車展每年一月份在美國底特律舉辦。因為是舉辦的日期比較特殊(新年的第一個大規模車展),所以北美車展具有很強的市場前瞻性,當然,我們很期待的傳祺GS7,也在此時亮相了。

廣汽傳祺GS7在北美車展全球首發,看點十足,當然不能錯過。GS7和GS8關係非常密切,你可以看作是短軸距版的GS8,GS7定位中型SUV,全係為五座車型。

GS7的車身長度和軸距要比GS8短80mm,但是寬度和高度與GS8一致。要說外觀,尤其是車頭的部分,看了之後你的第一反應肯定會是這個車頭真是很霸氣,但是隨後你可能會想,怎麼感覺這麼面熟呢?沒錯,因為GS7的車頭部分的設計基本和GS8一致,凌雲翼式的造型是很大氣,因為見過了GS8,所以當你看到GS7前臉的時候就不會覺得那麼驚艷了。這就是所謂的家族式設計,雖然依舊好看,但是不會讓消費者感到會有大的新鮮感。

小編最喜歡大燈組的設計,看起來感覺很高級,但是又不會顯得很臃腫。至於大家期待的LED大燈應該不會全系標配了(可以參考GS8)。

GS7的側面造型和他大哥在C柱之前相似度較高,但是因為GS7的軸距較短,所以GS7看起來顯得緊湊很多。

不過GS7的側窗並沒有沿襲GS8隱藏式的D柱設計,而是被銀色的鍍鉻裝飾條完整的包圍起來。對於這種設計,小編覺得GS7前臉都和大哥相似了,如果這部分再在用相同的設計,那麼設計師肯定會被吐槽偷懶的,其實這樣的設計也好,畢竟國人也很喜歡鍍鉻裝飾。

剛說過上面的不同點,這下子又被打臉了。因為尾部造型和大哥幾乎完全一致,肉眼很難看出有什麼太大的差別,畢竟,這兩個車子關係很親密么!

內飾還是那一句話,參考GS8,GS7的內飾造型大致和GS8相同。

後排地板中央凸起不是很高,同時後排空間的表現也比較寬裕,家用足夠。

至於動力系統,GS7將會搭載代號為4B20M1的2.0T發動機(和GS8是同款發動機)。最大功率201馬力,最大扭矩320,變速箱為6速手自一體,同時有4種變速箱模式可以切換。

其實看到這裏,GS7的身份已經很明確了,大致上就是一款縮小的GS8,但是可以肯定的是GS7的入門價肯定會更加地氣,對於那些不需要7座車型,同時覺得GS8有點貴的消費者,可以看一下GS7。

競爭對手

哈弗H7

指導價:14.98-19.38萬

H7是一款性價比很高的SUV,H7和H7L互相配合交出了不錯的銷售業績,不過GS7的帶來,肯定會帶給H7一些壓力的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

※推薦評價好的iphone維修中心