亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Spring解讀@Component和@Configuration的區(qū)別以及源碼分析

 更新時(shí)間:2024年10月25日 10:38:27   作者:魔道不誤砍柴功  
通過(guò)實(shí)例分析@Component和@Configuration注解的區(qū)別,核心在于@Configuration會(huì)通過(guò)CGLIB代理確保Bean的單例,而@Component不會(huì),在Spring容器中,使用@Configuration注解的類會(huì)被CGLIB增強(qiáng),保證了即使在同一個(gè)類中多次調(diào)用@Bean方法

之前一直搞不清 @Component 和 @Configuration 這兩個(gè)注解到底有啥區(qū)別,一直認(rèn)為被這兩修飾的類可以被 Spring 實(shí)例化嘛,不,還是見(jiàn)識(shí)太短,直到今天才發(fā)現(xiàn)這兩玩意有這么大區(qū)別。

很幸運(yùn)能夠及時(shí)發(fā)現(xiàn),后面可以少走點(diǎn)坑,下面就直接通過(guò)最簡(jiǎn)單的案例來(lái)說(shuō)明它兩的區(qū)別,顛覆你的認(rèn)知。

1、案例演示

定義一個(gè) Apple 實(shí)體類,通過(guò) @Bean 的方式交給 Spring 管理,如下:

public class Apple {

}

在定義一個(gè) AppleFactory 工廠類也可以獲取到 Apple 類實(shí)例,如下:

public class AppleFactory {

	private Apple apple;

	public Apple getApple() {
		return apple;
	}

	public void setApple(Apple apple) {
		this.apple = apple;
	}
}

在定義一個(gè) AppleAutoConfiguration 類,此時(shí)先用 @Configuration 注解修飾該類,如下:

@Configuration
public class AppleAutoConfiguration {

	@Bean
	public Apple apple() {
		return new Apple();
	}

	@Bean
	public AppleFactory appleFactory() {
		AppleFactory appleFactory = new AppleFactory();

		appleFactory.setApple(apple());
		return appleFactory;
	}
}

先來(lái)分析下 AppleAutoConfiguration 類中的方法,apple() 方法中直接通過(guò) new 關(guān)鍵字創(chuàng)建了一個(gè) Apple 對(duì)象,這個(gè)沒(méi)啥問(wèn)題

繼續(xù)看到 appleFactory() 方法內(nèi)部,又調(diào)用了 apple() 方法,此時(shí)仔細(xì)想想,Spring 是不是調(diào)用了兩次 apple() ,第一次是 Spring 掃描到 @Bean 注解調(diào)用一次,第二次是在 Spring 掃描到 @Bean 注解調(diào)用 appleFactory() 方法,appleFactory() 方法中又調(diào)用一次 apple() 方法,調(diào)兩次 new 創(chuàng)建對(duì)象,必然會(huì)出現(xiàn)兩個(gè)不一樣的 Apple 對(duì)象,這樣必然違背了 Spring 單例設(shè)計(jì)思想。

接下來(lái)測(cè)試下結(jié)果,看下是不是和我們想象的結(jié)果一樣呢?

測(cè)試如下:

@ComponentScan
public class ComponentConfigurationTest {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentConfigurationTest.class);


		AppleFactory appleFactory = context.getBean(AppleFactory.class);
		Apple apple = appleFactory.getApple();
		System.out.println("從 AppleFactory 獲取的 apple = " + apple);


		Apple bean = context.getBean(Apple.class);
		System.out.println("從 Spring 容器中獲取的 apple = " + bean);
	}
}

最終輸出結(jié)果如下:

從 AppleFactory 獲取的 apple = com.gwm.configurationanno.Apple@2f465398
從 Spring 容器中獲取的 apple = com.gwm.configurationanno.Apple@2f465398

發(fā)現(xiàn)這結(jié)果和我們剛剛的猜想不一樣啊,其實(shí)大家隱約應(yīng)該猜到,是因?yàn)檫@里使用的是 @Configuration 注解,所以這里最終你調(diào)用多少次最終都是同一個(gè)對(duì)象,原理稍后分析。

那么接下來(lái)肯定是要把 @Configuration 注解替換成 @Component 試試,修改之后的代碼如下:

@Component
public class AppleAutoConfiguration {

	@Bean
	public Apple apple() {
		return new Apple();
	}

	@Bean
	public AppleFactory appleFactory() {
		AppleFactory appleFactory = new AppleFactory();

		appleFactory.setApple(apple());
		return appleFactory;
	}
}

??????

測(cè)試結(jié)果如下:

從 AppleFactory 獲取的 apple = com.gwm.configurationanno.Apple@2f465398
從 Spring 容器中獲取的 apple = com.gwm.configurationanno.Apple@2f465353

最終發(fā)現(xiàn)這個(gè)結(jié)果和我們?cè)谇懊嫦胂蟮慕Y(jié)果一模一樣,果然就創(chuàng)建了兩個(gè) Apple 對(duì)象,違背了 Spring 單例設(shè)計(jì)思想,這個(gè)區(qū)別非常非常的重要,因?yàn)樵?SpringBoot 中為什么配置類都是使用的 @Configuration 注解,并不是直接使用 @Component 注解,這個(gè)原因想必大家應(yīng)該也知道了。

上面這個(gè)例子在 SpringBoot 中有非常多的應(yīng)用,在來(lái)看下另一個(gè)例子,如下:

定義一個(gè) Orange 實(shí)體類,通過(guò) FactoryBean 接口將 Orange 類交給 Spring 管理,如下:

public class Orange {

}

再定義個(gè) OrangeFactoryBean 類實(shí)現(xiàn) FactoryBean 接口,調(diào)用 getObject() 方法可以創(chuàng)建 Orange 對(duì)象,如下:

public class OrangeFactoryBean implements FactoryBean<Orange> {
	@Override
	public Orange getObject() throws Exception {
		return new Orange();
	}

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

大家都知道實(shí)現(xiàn)了 FactoryBean 接口的類最終會(huì)去調(diào)用 getObject() 方法然后創(chuàng)建對(duì)象。然后再在 AppleAutoConfiguration 類中調(diào)用 getObject() 方法,這里直接使用 @Component 注解演示,因?yàn)?@Configuration 注解肯定是沒(méi)問(wèn)題的,如下:

@Component
public class AppleAutoConfiguration {

	@Bean
	public Apple apple() {
		return new Apple();
	}

	@Bean
	public AppleFactory appleFactory() throws Exception {
		AppleFactory appleFactory = new AppleFactory();

		appleFactory.setApple(apple());
		
		OrangeFactoryBean orangeFactoryBean = orangeFactoryBean();
		System.out.println("appleFactory orangeFactoryBean = " + orangeFactoryBean);
		Orange orange = orangeFactoryBean.getObject();
		System.out.println("appleFactory orange="+orange);
		return appleFactory;
	}

	@Bean
	public OrangeFactoryBean orangeFactoryBean() {
		return new OrangeFactoryBean();
	}
}

這里通過(guò) @Bean 注入 OrangeFactoryBean 實(shí)例,因?yàn)?AppleAutoConfiguration 類被 @Component 修飾,所以這里 Spring 和 手動(dòng)調(diào)用兩次創(chuàng)建的 OrangeFactoryBean 不一樣,導(dǎo)致 getObject() 其實(shí)也是不一樣的。

測(cè)試代碼如下:

@ComponentScan
public class ComponentConfigurationTest {
	public static void main(String[] args) throws Exception {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ComponentConfigurationTest.class);


		AppleFactory appleFactory = context.getBean(AppleFactory.class);
		Apple apple = appleFactory.getApple();
		System.out.println("從 AppleFactory 獲取的 apple = " + apple);


		Apple bean = context.getBean(Apple.class);
		System.out.println("從 Spring 容器中獲取的 apple = " + bean);


		Orange bean1 = context.getBean(Orange.class);
		System.out.println("從 Spring 容器中獲取的 orange = "+bean1);

		Object bean2 = context.getBean("orangeFactoryBean");
		System.out.println("=>從 Spring 容器中獲取的 orange = " + bean2);

		OrangeFactoryBean bean3 = context.getBean(OrangeFactoryBean.class);
		System.out.println("bean3 = " + bean3);
	}
}

輸出結(jié)果如下:

appleFactory orangeFactoryBean = com.gwm.configurationanno.OrangeFactoryBean@c0c2f8d
appleFactory orange=com.gwm.configurationanno.Orange@305b7c14
從 AppleFactory 獲取的 apple = com.gwm.configurationanno.Apple@484970b0
從 Spring 容器中獲取的 apple = com.gwm.configurationanno.Apple@4470f8a6
從 Spring 容器中獲取的 orange = com.gwm.configurationanno.Orange@7c83dc97
=>從 Spring 容器中獲取的 orange = com.gwm.configurationanno.Orange@7c83dc97
Spring 容器中的 orangeFactoryBean = com.gwm.configurationanno.OrangeFactoryBean@7748410a

通過(guò)以上兩個(gè)案例可以清楚的知道 @Configuration 和 @Component 注解的區(qū)別,那么這個(gè)底層是怎么實(shí)現(xiàn)的呢?

2、源碼解析

2.1、@Configuration 簡(jiǎn)單思路分析

這里我們可以大致的思考下,產(chǎn)生多個(gè)實(shí)例無(wú)非就是沒(méi)有從 Spring 緩存中取值嘛,如果都是從 Spring 緩存中取值,那么必然就不會(huì)出現(xiàn)那么多對(duì)象。

對(duì)于 apple() 方法因?yàn)楸?@Bean 修飾,所以在 Spring 實(shí)例化過(guò)程中會(huì)被調(diào)用 ,然后創(chuàng)建完實(shí)例將其放到 Spring 單例緩沖池中,那么下次就可以直接從緩存中獲取到 Apple 實(shí)例(@Bean 注入 bean 流程看另一篇文章)。

對(duì)于 appleFactory() 方法中去調(diào)用 apple() 方法,apple() 方法又會(huì) new 一個(gè)新的 Apple 實(shí)例,那么怎么樣避免它重復(fù)創(chuàng)建對(duì)象呢?是不是可以通過(guò)代理改變 apple() 方法內(nèi)部的邏輯,改成讓它直接從 Spring 緩沖池中獲取 Apple 的實(shí)例,這樣就避免了重復(fù)創(chuàng)建對(duì)象。

如果要把 apple() 方法改成代理方法,是不是需要將所在的類 AppleAutoConfiguration 變成代理對(duì)象即可,Spring 就是這樣干的,加上 @Configuration 注解標(biāo)識(shí)之后,Spring 就會(huì)通過(guò) cglib 代理創(chuàng)建 AppleAutoConfiguration 實(shí)例,所以你在 Spring 容器中獲取 AppleAutoConfiguration 類是一個(gè)代理類,并不是真正的實(shí)體類。

跟著上述思路再去看源碼,就簡(jiǎn)單多了。

2.2、@Configuration 源碼分析之普通類型方法調(diào)用

首先看 ConfigurationClassPostProcessor 類解析 @Configuration 注解的地方,會(huì)先做個(gè) full 標(biāo)記,源碼如下:


標(biāo)記做好了之后,后面就可以通過(guò)判斷是否有這個(gè)標(biāo)記,然后要不要用代理創(chuàng)建實(shí)例,進(jìn)入到 ConfigurationClassPostProcessor 類的 postProcessBeanFactory() 方法,源碼如下:

這里會(huì)去判斷當(dāng)前 beanClass 是否有 full 標(biāo)記,有的話就加入到 configBeanDefs 容器中,準(zhǔn)備要用代理方式生成實(shí)例 bean。

然后開(kāi)始遍歷 configBeanDefs 容器,通過(guò) ConfigurationClassEnhancer 對(duì)象挨個(gè)創(chuàng)建代理類,這里增強(qiáng)的是 Class 字節(jié)碼文件,其實(shí)工作中這種方法也是可以借鑒的。

其中增強(qiáng)邏輯都放在了攔截器中(BeanMethodInterceptor、BeanFactoryAwareMethodInterceptor) 源碼如下:

從這兩個(gè)攔截器側(cè)面說(shuō)明 @Configuration 和 @Bean 才是老搭檔,干活不累,緊密相連,相輔相成,所以你在使用 @Bean 的時(shí)候,最好在外層類上標(biāo)注 @Configuration,不要使用 @Component 注解。

也就是說(shuō)當(dāng)你觸發(fā)了目標(biāo)方法的調(diào)用時(shí),就會(huì)回調(diào)到這兩個(gè)攔截器鏈,但是具體執(zhí)行哪個(gè)攔截器是需要條件的,BeanMethodInterceptor 攔截器的條件需要,如下所示:

BeanFactoryAwareMethodInterceptor 攔截器需要條件,源碼如下:

我們這里滿足 BeanMethodInterceptor 攔截器的執(zhí)行條件,所以 Spring 在調(diào)用 apple() 方法的時(shí)候,會(huì)觸發(fā) BeanMethodInterceptor 攔截器增強(qiáng)邏輯的執(zhí)行,增強(qiáng)邏輯如下:

這里有一個(gè)判斷 isCurrentlyInvokedFactoryMethod() 非常關(guān)鍵,因?yàn)檫@個(gè)開(kāi)關(guān)控制著你是否會(huì)多次創(chuàng)建實(shí)例,進(jìn)入該方法內(nèi)部,源碼如下:

可以發(fā)現(xiàn)這里有一個(gè) ThreadLocal 類型的容器,那么這個(gè)容器什么時(shí)候會(huì)有值呢?這個(gè)就要需要你對(duì) @Bean 的實(shí)例化流程非常了解了,這里簡(jiǎn)單摘取核心部分,源碼如下:

在 @Bean 實(shí)例化流程的時(shí)候就用到了這個(gè) currentlyInvokedFactoryMethod 容器,先把值放進(jìn)去,然后反射調(diào)用完方法之后又刪除。其實(shí)這只是一個(gè)標(biāo)記作用。

Spring 在調(diào)用 @Bean 修飾的 apple() 方法時(shí),currentlyInvokedFactoryMethod 容器中放的就是 apple,然后通過(guò) set() 反射調(diào)用 apple() 方法,注意此時(shí)的 AppleAutoConfiguration 是一個(gè)代理對(duì)象,所以調(diào)用 apple() 方法就會(huì)觸發(fā)走切面邏輯,因?yàn)槭?@Bean 修飾的,所以走的是 BeanMethodInterceptor 這個(gè)類的增強(qiáng)邏輯,源碼如下:

注意此時(shí)的判斷邏輯 isCurrentlyInvokedFactoryMethod() 是有值的哦,存的就是 apple,所以這里就直接走 if 邏輯,直接調(diào)用目標(biāo)方法邏輯,直接 new Apple() 對(duì)象,然后返回到 set() 反射調(diào)用處,在刪除 currentlyInvokedFactoryMethod 容器中的值 apple,然后就是 Spring 實(shí)例化 @Bean 的后續(xù)流程,最終會(huì)將這個(gè) new 出來(lái)的實(shí)例放到 Spring 一級(jí)緩存中,源碼如下:

那么重點(diǎn)來(lái)了,Spring 在執(zhí)行 @Bean 修飾的 appleFactory() 方法時(shí), isCurrentlyInvokedFactoryMethod 容器中那么就是存的 appleFactory,然后通過(guò)反射 set() 方法去調(diào)用 appleFactory() 方法,然后再 appleFactory() 方法中執(zhí)行邏輯時(shí)發(fā)現(xiàn)又調(diào)用了 apple() 方法,那么又會(huì)觸發(fā)進(jìn)入 apple() 方法的增強(qiáng)邏輯 BeanMethodInterceptor,源碼如下:

注意此時(shí)的 isCurrentlyInvokedFactoryMethod() 判斷邏輯,當(dāng)前入?yún)?beanMethod 是 apple,但是 isCurrentlyInvokedFactoryMethod 容器中剛剛存放的是外面方法 appleFactory() 的值,所以這里 isCurrentlyInvokedFactoryMethod() 方法判斷條件不成立,走 resolveBeanReference() 邏輯,源碼如下:

這里面這段邏輯會(huì)觸發(fā) getBean() 流程,此時(shí) getBean() 流程去獲取 Apple 類實(shí)例,肯定是從單例緩沖池中獲取得,因?yàn)橹霸趫?zhí)行 @Bean 修飾的 apple() 方法就已將實(shí)例存入到了 Spring 的一級(jí)緩存中,所以在 appleFactory() 方法中,不管你調(diào)用多少次,都不會(huì)重復(fù)創(chuàng)建 Apple 類實(shí)例,因?yàn)樽罱K都是通過(guò)切面邏輯去調(diào)用 getBean() 從緩存中獲取得,必然是同一個(gè)實(shí)例。

2.3、@Configuration 源碼分析之 FactoryBean 類型方法調(diào)用

Apple 是一個(gè)普通類,上面已經(jīng)解析完,現(xiàn)在來(lái)解析一下實(shí)現(xiàn) FactoryBean 接口的 OrangeFactoryBean 特殊一點(diǎn)類型的看 Spring 又是如何處理的。

public class Orange {

}

public class OrangeFactoryBean implements FactoryBean<Orange> {
	@Override
	public Orange getObject() throws Exception {
		return new Orange();
	}

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


@Configuration
public class AppleAutoConfiguration {

	@Bean
	public AppleFactory appleFactory() throws Exception {
		OrangeFactoryBean orangeFactoryBean = orangeFactoryBean();
		System.out.println("appleFactory orangeFactoryBean = " + orangeFactoryBean);
		Orange orange = orangeFactoryBean.getObject();
		System.out.println("appleFactory orange="+orange);

		return appleFactory;
	}

	@Bean
	public OrangeFactoryBean orangeFactoryBean() {
		return new OrangeFactoryBean();
	}
}

其實(shí)只需要看切面邏輯就可以,AppleAutoConfiguration 類的實(shí)例是一個(gè)代理對(duì)象,在調(diào)用 appleFactory() 方法里面調(diào)用 orangeFactoryBean() 方法時(shí)會(huì)觸發(fā)進(jìn)入切面邏輯(BeanMethodInterceptor),因?yàn)?OrangeFactoryBean 這個(gè)類有點(diǎn)特殊,實(shí)現(xiàn)了 FactoryBean 接口,所以在切面邏輯(BeanMethodInterceptor)實(shí)現(xiàn)會(huì)有一點(diǎn)不一樣,源碼如下:

從上面源碼分析,當(dāng)在 appleFactory() 方法中調(diào)用 orangeFactoryBean() 方法時(shí)會(huì)觸發(fā)進(jìn)入 BeanMethodInterceptor 切面邏輯,然后在切面中會(huì)去判斷是否是 FactoryBean 接口類型,恰好 OrangeFactoryBean 就是 FactoryBean 類型,所以會(huì)直接調(diào)用 getBean() 流程,此時(shí)注意,beanName 是包含了 & 符號(hào),表示是需要實(shí)例化 OrangeFactoryBean 類,這個(gè)特別注意。因?yàn)椴粠?& 符號(hào)的話會(huì)調(diào)用 getObject() 方法創(chuàng)建 Orange 實(shí)例。注意這里是包含了 & 符號(hào),是要去創(chuàng)建 OrangeFactoryBean 實(shí)例。

當(dāng)調(diào)用 getBean() 去創(chuàng)建 OrangeFactoryBean 實(shí)例時(shí),因?yàn)?AppleAutoConfiguration 類是一個(gè)代理類,所以在調(diào)用 AppleAutoConfiguration 類中調(diào)用 orangeFactoryBean() 方法創(chuàng)建 OrangeFactoryBean 實(shí)例時(shí)會(huì)又會(huì)觸發(fā)切面邏輯,又會(huì)走上面的邏輯,但是注意注意注意注意此時(shí)的 beanName 是不帶 & 符號(hào)的哦,此時(shí)的 beanName 就是方法名稱,所以此時(shí)就會(huì)進(jìn)入 else 邏輯進(jìn)入 enhanceFactoryBean() 方法中,源碼如下:

從源碼中可以看到又是通過(guò) cglib 創(chuàng)建了一個(gè) OrangeFactoryBean 的代理對(duì)象,注意這里的攔截器邏輯,只有當(dāng)你調(diào)用了 OrangeFactoryBean 類中的 getObject() 方法才會(huì)做特殊增強(qiáng),去調(diào)用 getBean() 邏輯,同時(shí) 注意 beanName 是不帶 & 符號(hào)的,也就是去創(chuàng)建 Orange 類實(shí)例,除了 getObject() 方法之外的所有方法不做任何處理,直接進(jìn)回調(diào)即可。至此 OrangeFactoryBean 類實(shí)例已經(jīng)創(chuàng)建好了,在 Spring 容器中是一個(gè)代理類。

然后再看到我們自己的代碼如下:

@Configuration
public class AppleAutoConfiguration {

	@Bean
	public AppleFactory appleFactory() throws Exception {
		OrangeFactoryBean orangeFactoryBean = orangeFactoryBean();
		System.out.println("appleFactory orangeFactoryBean = " + orangeFactoryBean);
		Orange orange = orangeFactoryBean.getObject();
		System.out.println("appleFactory orange="+orange);

		return appleFactory;
	}

	@Bean
	public OrangeFactoryBean orangeFactoryBean() {
		return new OrangeFactoryBean();
	}
}

剛才已執(zhí)行到第一行代碼,執(zhí)行完后,獲取到一個(gè) OrangeFactoryBean 代理對(duì)象,然后開(kāi)始執(zhí)行第二行代碼,注意這里隱式調(diào)用了 toString() 方法,會(huì)觸發(fā) OrangeFactoryBean 代理類的切面邏輯,而 OrangeFactoryBean 代理類只對(duì) getObject() 方法有特殊處理,其他的方法都不做處理,就是直接回調(diào)而已,所以 toString() 的切面邏輯不用太在乎。

接下來(lái)執(zhí)行第三行代碼,調(diào)用 getObject() 方法,getObject() 方法是 OrangeFactoryBean 代理類非常關(guān)心的方法,在源碼中寫(xiě)死要對(duì) getObject() 方法進(jìn)行處理。最終會(huì)調(diào)用到 getBean(orange) 流程,實(shí)例化 Orange bean,最終將 Orange 實(shí)例放入到 Spring 一級(jí)緩存。

所以最終在 appleFactory() 方法中執(zhí)行 Orange orange = orangeFactoryBean.getObject() 代碼和在測(cè)試類中執(zhí)行 getBean(Orange.class) 或者 getBean(“orangeFactoryBean”) 代碼都是從同一個(gè)地方獲取到的值(Spring 中的一級(jí)緩存中),雖然多個(gè)地方調(diào)用,表面上給人的感覺(jué)是調(diào)用了多次,會(huì)出現(xiàn)多個(gè)實(shí)例,但是 Spring 中用代理的方式從底層幫我們解決了這個(gè)問(wèn)題。但是前提是要使用 @Configuration 注解才會(huì)生效。

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論