Spring注解之@Configration定義配置類
- 之前我們都是通過xml的方式定義bean,里面會寫很多bean元素,然后spring啟動的時候,就會讀取bean xml配置文件,然后解析這些配置,然后會將這些bean注冊到spring容器中,供使用者使用。
- jdk1.5里面有了注解的功能,spring也沒閑着,覺得注解挺好用的,就將注解加了進來,讓我們通過注解的方式來定義bean,用起來能達到xml中定義bean一樣的效果,並且更簡潔一些,這里面需要用到的注解就有@Configuration注解和@Bean注解。
Spring @Configuration注解
Spring3.0開始,@Configuration用於定義配置類,定義的配置類可以替換xml文件,一般和@Bean注解聯合使用。
@Configuration注解主要標注在某個類上,相當於xml配置文件中的<beans>
@Bean注解主要標注在某個方法上,相當於xml配置文件中的<bean>
1、@Configuration注解
1.用法
注意:@Configuration注解的配置類有如下要求:
- @Configuration必須是非本地的(即不能將配置類定義在其他類的方法內部,不能是private)。
- @Configuration必須有一個無參構造函數。
- @Configuration不可以是final類型(沒法動態代理);
- @Configuration不可以是匿名類;
- 嵌套的configuration必須是靜態類。
@Configuration這個注解可以加在類上,讓這個類的功能等同於一個bean xml配置文件,如下:
@Configuration public class ConfigBean { }
上面代碼類似於下面的xml:
<?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-4.3.xsd"> </beans>
通過AnnotationConfigApplicationContext
來加載@Configuration
修飾的類,如下:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);
此時ConfigBean類中沒有任何內容,相當於一個空的xml配置文件,此時我們要在ConfigBean類中注冊bean,那么我們就要用到@Bean注解了。
總結一下
- @Configuration使用步驟:
- 在類上使用@Configuration注解
- 通過AnnotationConfigApplicationContext容器來加@Configuration注解修飾的類
2、@Bean注解
1.用法
這個注解類似於bean xml配置文件中的bean元素,用來在spring容器中注冊一個bean。
- @Bean默認是單例模式,並且沒有提供指定作用域的屬性,可以通過@Scope來實現該功能。
- @Bean注解用在方法上,表示通過方法來定義一個bean,默認將方法名稱作為bean名稱,將方法返回值作為bean對象,注冊到spring容器中。
如:
@Bean public User user1() { return new User(); }
2.舉幾個簡單例子回顧下,XML跟config配置方式的區別
表達形式層面
基於XML配置的方式是這樣:
<?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-3.0.xsd" default-lazy-init="true"> <!--bean定義--> </beans>
而基於JavaConfig的配置方式是這樣:
@Configuration public class MockConfiguration{ //bean定義 }
任何一個標注了@Configuration的Java類定義都是一個JavaConfig配置類。
注冊bean定義層面
基於XML的配置形式是這樣:
<bean id="mockService" class="..MockServiceImpl"> ... </bean>
而基於JavaConfig的配置形式是這樣的:
@Configuration public class MockConfiguration{ @Bean public MockService mockService(){ return new MockServiceImpl(); } }
任何一個標注了@Bean的方法,其返回值將作為一個bean定義注冊到Spring的IoC容器,方法名將默認成該bean定義的id。
表達依賴注入關系層面
為了表達bean與bean之間的依賴關系,在XML形式中一般是這樣:
<bean id="mockService" class="..MockServiceImpl"> <propery name ="dependencyService" ref="dependencyService" /> </bean> <bean id="dependencyService" class="DependencyServiceImpl"></bean>
而基於JavaConfig的配置形式是這樣的:
@Configuration public class MockConfiguration{ @Bean public MockService mockService(){ return new MockServiceImpl(dependencyService()); } @Bean public DependencyService dependencyService(){ return new DependencyServiceImpl(); } }
如果一個bean的定義依賴其他bean,則直接調用對應的JavaConfig類中依賴bean的創建方法就可以了。
圖片例子
等價於
3.案例
User類
package com.javacode2018.lesson001.demo20; public class User { }
Bean配置類:ConfigBean
package com.javacode2018.lesson001.demo20; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ConfigBean { //bean名稱為方法默認值:user1 @Bean public User user1() { return new User(); } //bean名稱通過value指定了:user2Bean @Bean("user2Bean") public User user2() { return new User(); } //bean名稱為:user3Bean,2個別名:[user3BeanAlias1,user3BeanAlias2] @Bean({"user3Bean", "user3BeanAlias1", "user3BeanAlias2"}) public User user3() { return new User(); } }
上面通過@Bean注解定義了3個bean
來個測試類
package com.javacode2018.lesson001.demo20; import org.junit.Test; import org.springframework.context.annotation.AnnotationConfigApplicationContext; import java.util.Arrays; public class ConfigurationTest { @Test public void test1() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean.class);//@1 for (String beanName : context.getBeanDefinitionNames()) { //別名 String[] aliases = context.getAliases(beanName); System.out.println(String.format("bean名稱:%s,別名:%s,bean對象:%s", beanName, Arrays.asList(aliases), context.getBean(beanName))); } } }
通過
AnnotationConfigApplicationContext
來加載配置類ConfigBean
,會將配置類中所有的bean注冊到spring容器中for循環中輸出了bean名稱、別名、bean對象
運行test1方法輸出
bean名稱:org.springframework.context.annotation.internalConfigurationAnnotationProcessor,別名:[],bean對象:org.springframework.context.annotation.ConfigurationClassPostProcessor@3bd82cf5 bean名稱:org.springframework.context.annotation.internalAutowiredAnnotationProcessor,別名:[],bean對象:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@544fa968 bean名稱:org.springframework.context.annotation.internalCommonAnnotationProcessor,別名:[],bean對象:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@247bddad bean名稱:org.springframework.context.event.internalEventListenerProcessor,別名:[],bean對象:org.springframework.context.event.EventListenerMethodProcessor@d35dea7 bean名稱:org.springframework.context.event.internalEventListenerFactory,別名:[],bean對象:org.springframework.context.event.DefaultEventListenerFactory@7770f470 bean名稱:configBean,別名:[],bean對象:com.javacode2018.lesson001.demo20.ConfigBean$$EnhancerBySpringCGLIB$$dde45976@5e5d171f bean名稱:user1,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@24313fcc bean名稱:user2Bean,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@7d20d0b bean名稱:user3Bean,別名:[user3BeanAlias2, user3BeanAlias1],bean對象:com.javacode2018.lesson001.demo20.User@77f1baf5
上面的輸出,我們主要關注與最后4行,前面的可以先忽略。
從輸出中可以看出,有個名稱為configBean的bean,正是ConfigBean這個類型,可以得出,被@Configuration修飾的類,也被注冊到spring容器中了
最后3行輸出就是幾個User的bean對象了。
上面的用法應該很多人都比較熟悉,下面的屬於重點了。
3、去掉@Configuration會怎樣
我們來看一下沒有@Configuration的時候,什么效果。
新建一個ConfigBean1類
內容和ConfigBean類一樣,只是將@Configuration注解去掉了,如下:
public class ConfigBean1 { //bean名稱為方法默認值:user1 @Bean public User user1() { return new User(); } //bean名稱通過value指定了:user2Bean @Bean("user2Bean") public User user2() { return new User(); } //bean名稱為:user3Bean,2個別名:[user3BeanAlias1,user3BeanAlias2] @Bean({"user3Bean", "user3BeanAlias1", "user3BeanAlias2"}) public User user3() { return new User(); } }
來個測試用例test2
代碼類似於test1,給spring容器傳遞ConfigBean1
@Test public void test2() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean1.class); for (String beanName : context.getBeanDefinitionNames()) { //別名 String[] aliases = context.getAliases(beanName); System.out.println(String.format("bean名稱:%s,別名:%s,bean對象:%s", beanName, Arrays.asList(aliases), context.getBean(beanName))); } }
運行輸出
bean名稱:org.springframework.context.annotation.internalConfigurationAnnotationProcessor,別名:[],bean對象:org.springframework.context.annotation.ConfigurationClassPostProcessor@333291e3 bean名稱:org.springframework.context.annotation.internalAutowiredAnnotationProcessor,別名:[],bean對象:org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor@479d31f3 bean名稱:org.springframework.context.annotation.internalCommonAnnotationProcessor,別名:[],bean對象:org.springframework.context.annotation.CommonAnnotationBeanPostProcessor@40ef3420 bean名稱:org.springframework.context.event.internalEventListenerProcessor,別名:[],bean對象:org.springframework.context.event.EventListenerMethodProcessor@498d318c bean名稱:org.springframework.context.event.internalEventListenerFactory,別名:[],bean對象:org.springframework.context.event.DefaultEventListenerFactory@6e171cd7 bean名稱:configBean1,別名:[],bean對象:com.javacode2018.lesson001.demo20.ConfigBean1@402bba4f bean名稱:user1,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@795cd85e bean名稱:user2Bean,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@59fd97a8 bean名稱:user3Bean,別名:[user3BeanAlias2, user3BeanAlias1],bean對象:com.javacode2018.lesson001.demo20.User@f5ac9e4
分析結果
我們將2個輸出的最后4行拿來對比一下:
有@Configuration注解的
bean名稱:configBean,別名:[],bean對象:com.javacode2018.lesson001.demo20.ConfigBean$$EnhancerBySpringCGLIB$$dde45976@5e5d171f
bean名稱:user1,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@24313fcc
bean名稱:user2Bean,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@7d20d0b
bean名稱:user3Bean,別名:[user3BeanAlias2, user3BeanAlias1],bean對象:com.javacode2018.lesson001.demo20.User@77f1baf5
沒有@Configuration注解的
bean名稱:configBean1,別名:[],bean對象:com.javacode2018.lesson001.demo20.ConfigBean1@402bba4f
bean名稱:user1,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@795cd85e
bean名稱:user2Bean,別名:[],bean對象:com.javacode2018.lesson001.demo20.User@59fd97a8
bean名稱:user3Bean,別名:[user3BeanAlias2, user3BeanAlias1],bean對象:com.javacode2018.lesson001.demo20.User@f5ac9e4
對比得出
-
對比最后3行,可以看出:有沒有@Configuration注解,@Bean都會起效,都會將@Bean修飾的方法作為bean注冊到容器中
-
兩個內容的第一行有點不一樣,被@Configuration修飾的bean最后輸出的時候帶有
EnhancerBySpringCGLIB
的字樣,而沒有@Configuration注解的bean沒有Cglib的字樣;有EnhancerBySpringCGLIB
字樣的說明這個bean被cglib處理過的,變成了一個代理對象。
目前為止我們還是看不出二者本質上的區別,繼續向下看。
4、@Configuration加不加到底區別在哪
通常情況下,bean之間是有依賴關系的,我們來創建個有依賴關系的bean,通過這個案例你就可以看出根本的區別了。
1.再來一個加@Configuration的案例
定義2個類。
ServiceA
package com.javacode2018.lesson001.demo20; public class ServiceA { }
ServiceB
package com.javacode2018.lesson001.demo20; public class ServiceB { private ServiceA serviceA; public ServiceB(ServiceA serviceA) { this.serviceA = serviceA; } @Override public String toString() { return "ServiceB{" + "serviceA=" + serviceA + '}'; } }
上面定義了2個類,ServiceB依賴於ServiceA,ServiceB通過構造器注入ServiceA。
來個@Configuration類管理上面對象。
ConfigBean2
package com.javacode2018.lesson001.demo20; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ConfigBean2 { @Bean public ServiceA serviceA() { System.out.println("調用serviceA()方法"); //@0 return new ServiceA(); } @Bean ServiceB serviceB1() { System.out.println("調用serviceB1()方法"); ServiceA serviceA = this.serviceA(); //@1 return new ServiceB(serviceA); } @Bean ServiceB serviceB2() { System.out.println("調用serviceB2()方法"); ServiceA serviceA = this.serviceA(); //@2 return new ServiceB(serviceA); } }
上面通過@Bean注解,向容器中注冊了3個bean
注意@1和@2,通過this.serviceA()獲取需要注入的ServiceA對象。
上面每個方法第一行都輸出了一行日志。
重點關注一下@0這行日志會輸出幾次,大家先思考一下1次還是3次?
測試用例
@Test public void test3() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConfigBean2.class); for (String beanName : context.getBeanDefinitionNames()) { //別名 String[] aliases = context.getAliases(beanName); System.out.println(String.format("bean名稱:%s,別名:%s,bean對象:%s", beanName, Arrays.asList(aliases), context.getBean(beanName))); } }
運行輸出
截取了幾行輸出如下:
調用serviceA()方法 調用serviceB1()方法 調用serviceB2()方法 bean名稱:configBean2,別名:[],bean對象:com.javacode2018.lesson001.demo20.ConfigBean2$$EnhancerBySpringCGLIB$$ffa0178@77f1baf5 bean名稱:serviceA,別名:[],bean對象:com.javacode2018.lesson001.demo20.ServiceA@41a2befb bean名稱:serviceB1,別名:[],bean對象:ServiceB{serviceA=com.javacode2018.lesson001.demo20.ServiceA@41a2befb} bean名稱:serviceB2,別名:[],bean對象:ServiceB{serviceA=com.javacode2018.lesson001.demo20.ServiceA@41a2befb}
分析結果
從輸出中可以看出
-
前三行可以看出,被@Bean修飾的方法都只被調用了一次,這個很關鍵
-
最后三行中可以看出都是同一個ServiceA對象,都是`ServiceA@41a2befb`這個實例
這是為什么?
被@Configuration修飾的類,spring容器中會通過cglib給這個類創建一個代理,代理會攔截所有被@Bean
修飾的方法,默認情況(bean為單例)下確保這些方法只被調用一次,從而確保這些bean是同一個bean,即單例的。
2.不加的案例
我們再來看看將ConfigBean2上的的@Configuration去掉,效果如何,代碼就不寫了,直接上輸出結果:
調用serviceA()方法 調用serviceB1()方法 調用serviceA()方法 調用serviceB2()方法 調用serviceA()方法 bean名稱:configBean2,別名:[],bean對象:com.javacode2018.lesson001.demo20.ConfigBean2@6e171cd7 bean名稱:serviceA,別名:[],bean對象:com.javacode2018.lesson001.demo20.ServiceA@402bba4f bean名稱:serviceB1,別名:[],bean對象:ServiceB{serviceA=com.javacode2018.lesson001.demo20.ServiceA@795cd85e} bean名稱:serviceB2,別名:[],bean對象:ServiceB{serviceA=com.javacode2018.lesson001.demo20.ServiceA@59fd97a8}
3.結果分析
-
serviceA()方法被調用了3次
-
configBean2這個bean沒有代理效果了
-
最后3行可以看出,幾個ServiceA對象都是不一樣的
參考:
https://mp.weixin.qq.com/s/1Ym5gIRMvhJ0nCJlNjqfOg
https://www.jb51.net/article/184822.htm