1. 程式人生 > 其它 >spring成神之路第二十二篇:@Scope、@DependsOn、@ImportResource、@Lazy 詳解

spring成神之路第二十二篇:@Scope、@DependsOn、@ImportResource、@Lazy 詳解

面試問題

  1. @Scope是做什麼的?常見的用法有幾種?

  2. @DependsOn是做什麼的?常見的用法有幾種?

  3. @ImportResource幹什麼的?通常用在什麼地方?

  4. @Lazy做什麼的,通常用在哪些地方?常見的用法有幾種?

上面幾個問題中涉及到了4個註解,都是比較常用的,下面我們來一一介紹。

@Scope:指定bean的作用域

用法

關於什麼是bean的作用域,可以去看一下之前的一篇文章:Spring系列第6篇:玩轉bean scope,避免跳坑裡!

@Scope用來配置bean的作用域,等效於bean xml中的bean元素中的scope屬性。

看一下其原始碼:

@Target
({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceScope{

@AliasFor("scopeName")
Stringvalue()default"";

@AliasFor("value")
StringscopeName()default"";

ScopedProxyModeproxyMode()defaultScopedProxyMode.DEFAULT;

}

@Scope可以用在類上和方法上

引數:value和scopeName效果一樣,用來指定bean作用域名稱,如:singleton、prototype

常見2種用法

  1. 和@Compontent一起使用在類上

  2. 和@Bean一起標註在方法上

案例1:和@Compontent一起使用在類上

@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)//@1
publicclassServiceA{
}

上面定義了一個bean,作用域為單例的。

@1:ConfigurableBeanFactory介面中定義了幾個作用域相關的常量,可以直接拿來使用,如:

String SCOPE_SINGLETON = "singleton";

String SCOPE_PROTOTYPE = "prototype";

案例2:和@Bean一起標註在方法上

@Bean標註在方法上,可以通過這個方法來向spring容器中註冊一個bean,在此方法上加上@Scope可以指定這個bean的作用域,如:

@Configurable
publicclassMainConfig2{
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
publicServiceAserviceA(){
returnnewServiceA();
}
}

@DependsOn:指定當前bean依賴的bean

用法

前面有篇文章中介紹了bean xml中depend-on的使用,建議先看一下:Spring系列第9篇:depend-on到底是幹什麼的?

@DependsOn等效於bean xml中的bean元素中的depend-on屬性。

spring在建立bean的時候,如果bean之間沒有依賴關係,那麼spring容器很難保證bean例項建立的順序,如果想確保容器在建立某些bean之前,需要先建立好一些其他的bean,可以通過@DependsOn來實現,@DependsOn可以指定當前bean依賴的bean,通過這個可以確保@DependsOn指定的bean在當前bean建立之前先建立好

看一下其原始碼:

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceDependsOn{

String[]value()default{};

}

可以用在任意型別和方法上。

value:string型別的陣列,用來指定當前bean需要依賴的bean名稱,可以確保當前容器在建立被@DependsOn標註的bean之前,先將value指定的多個bean先建立好。

常見2種用法

  1. 和@Compontent一起使用在類上

  2. 和@Bean一起標註在方法上

案例1:和@Compontent一起使用在類上

下面定義3個bean:service1、service2、service3;service1需要依賴於其他2個service,需要確保容器在建立service1之前需要先將其他2個bean先建立好。

看程式碼:

Service2

packagecom.javacode2018.lesson001.demo27.test3;

importorg.springframework.stereotype.Component;

@Component
publicclassService2{
publicService2(){
System.out.println("createService2");
}
}

Service3

packagecom.javacode2018.lesson001.demo27.test3;

importorg.springframework.stereotype.Component;

@Component
publicclassService3{
publicService3(){
System.out.println("createService3");
}
}

Service1

packagecom.javacode2018.lesson001.demo27.test3;

importorg.springframework.context.annotation.DependsOn;
importorg.springframework.stereotype.Component;

@DependsOn({"service2","service3"})//@1
@Component
publicclassService1{
publicService1(){
System.out.println("createService1");
}
}

@1:使用了@DependsOn,指定了2個bean:service2和service3,那麼spring容器在建立上面這個service1的時候會先將@DependsOn中指定的2個bean先建立好

來個配置類

packagecom.javacode2018.lesson001.demo27.test3;

importorg.springframework.context.annotation.ComponentScan;

@ComponentScan
publicclassMainConfig3{
}

測試用例

@Test
publicvoidtest3(){
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig3.class);
System.out.println(context.getBean(Service1.class));
}

執行輸出

createService2
createService3
createService1
com.javacode2018.lesson001.demo27.test3.Service1@9f116cc

從輸出中可以看到,spring容器在建立service1之前,先將service2和service3建立好了。

案例2:和@Bean一起標註在方法上

下面通過配置檔案的方式來建立bean,如下:

packagecom.javacode2018.lesson001.demo27.test4;

importorg.springframework.beans.factory.annotation.Configurable;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.DependsOn;

@Configurable
publicclassMainConfig4{

@Bean
@DependsOn({"service2","service3"})//@1
publicService1service1(){
returnnewService1();
}

@Bean
publicService2service2(){
returnnewService2();
}

@Bean
publicService3service3(){
returnnewService3();
}

}

上面是一個spring的配置類,類中3個方法定義了3個bean

@1:這個地方使用了@DependsOn,表示service1這個bean建立之前,會先建立好service2和service3

來個測試用例

@Test
publicvoidtest4(){
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig4.class);
System.out.println(context.getBean(com.javacode2018.lesson001.demo27.test4.Service1.class));
}

執行輸出

createService2
createService3
createService1
com.javacode2018.lesson001.demo27.test4.Service1@6e20b53a

@ImportResource:配置類中匯入bean定義的配置檔案

用法

有些專案,前期可能採用xml的方式配置bean,後期可能想採用spring註解的方式來重構專案,但是有些老的模組可能還是xml的方式,spring為了方便在註解方式中相容老的xml的方式,提供了@ImportResource註解來引入bean定義的配置檔案。

bean定義配置檔案:目前我們主要介紹了xml的方式,還有一種properties檔案的方式,以後我們會介紹,此時我們還是以引入bean xml來做說明。

看一下這個註解的定義:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
public@interfaceImportResource{

@AliasFor("locations")
String[]value()default{};

@AliasFor("value")
String[]locations()default{};

Class<?extendsBeanDefinitionReader>reader()defaultBeanDefinitionReader.class;

}

通常將其用在配置類上。

有3個引數:

  • value和locations效果一樣,只能配置其中一個,是一個string型別的陣列,用來指定需要匯入的配置檔案的路徑。

  • reader:用來指定bean定義的讀取器,目前我們知道的配置bean的方式有xml檔案的方式,註解的方式,其實還有其他的方式,比如properties檔案的方式,如果用其他的方式,你得告訴spring具體要用那種解析器去解析這個bean配置檔案,這個解析器就是BeanDefinitionReader,以後我們講BeanDefinition的時候再細說。

資原始檔路徑的寫法

通常我們的項是採用maven來組織的,配置檔案一般會放在resources目錄,這個目錄中的檔案被編譯之後會在target/classes目錄中。

spring中資原始檔路徑最常用的有2種寫法:

  1. 以classpath:開頭:檢索目標為當前專案的classes目錄

  2. 以classpath*:開頭:檢索目標為當前專案的classes目錄,以及專案中所有jar包中的目錄,如果你確定jar不是檢索目標,就不要用這種方式,由於需要掃描所有jar包,所以速度相對於第一種會慢一些

那我們再來說classpath:和classpath*:後面的部分,後面的部分是確定資原始檔的位置地方,幾種常見的如下:

相對路徑的方式

classpath:com/javacode2018/lesson001/demo27/test5/beans.xml
或者
classpath*:com/javacode2018/lesson001/demo27/test5/beans.xml

/:絕對路徑的方式

classpath:/com/javacode2018/lesson001/demo27/test5/beans.xml

*:檔案萬用字元的方式

classpath:/com/javacode2018/lesson001/demo27/test5/beans-*.xml

會匹配test5目錄中所有以beans-開頭的xml結尾的檔案

*:目錄萬用字元的方式

classpath:/com/javacode2018/lesson001/demo27/*/beans-*.xml

會匹配demo27中所有子目錄中所有以beans-開頭的xml結尾的檔案,注意這個地方只包含demo27的子目錄,不包含子目錄的子目錄,不會進行遞迴

**:遞迴任意子目錄的方式

classpath:/com/javacode2018/**/beans-*.xml

**會遞迴當前目錄以及下面任意級的子目錄

ok,繼續回到@ImportResource上來,來看案例

案例程式碼

來2個類,這兩個類我們分別用2個xml來定義bean

ServiceA

packagecom.javacode2018.lesson001.demo27.test5;

publicclassServiceA{
}

ServiceB

packagecom.javacode2018.lesson001.demo27.test5;

publicclassServiceB{
}

beans1.xml來定義serviceA這個bean,如下

<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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"
>

<beanid="serviceA"class="com.javacode2018.lesson001.demo27.test5.ServiceA"/>

</beans>

beans2.xml來定義serviceB這個bean,如下

<?xmlversion="1.0"encoding="UTF-8"?>
<beansxmlns="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"
>

<beanid="serviceB"class="com.javacode2018.lesson001.demo27.test5.ServiceB"/>

</beans>

下面來個配置類,來引入上面2個配置檔案

packagecom.javacode2018.lesson001.demo27.test5;

importorg.springframework.beans.factory.annotation.Configurable;
importorg.springframework.context.annotation.ImportResource;

@Configurable
@ImportResource("classpath:/com/javacode2018/lesson001/demo27/test5/beans*.xml")
publicclassMainConfig5{
}

這個類上使用了@Configurable表示這是個配置類

並且使用了@ImportResource註解來匯入上面2個配置檔案

來個測試用例載入上面這個配置類

@Test
publicvoidtest5(){
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig5.class);
for(StringbeanName:context.getBeanDefinitionNames()){
System.out.println(String.format("%s->%s",beanName,context.getBean(beanName)));
}
}

上面會輸出MainConfig5配置類中所有定義的bean

執行輸出

mainConfig5->com.javacode2018.lesson001.demo27.test5.MainConfig5@4ec4f3a0
serviceA->com.javacode2018.lesson001.demo27.test5.ServiceA@223191a6
serviceB->com.javacode2018.lesson001.demo27.test5.ServiceB@49139829

從輸出中可以看出2個xml中定義的bean也被註冊了

@Lazy:延遲初始化

用法

@Lazy等效於bean xml中bean元素的lazy-init屬性,可以實現bean的延遲初始化。

所謂延遲初始化:就是使用到的時候才會去進行初始化。

來看一下其定義:

@Target({ElementType.TYPE,ElementType.METHOD,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public@interfaceLazy{

booleanvalue()defaulttrue;

}

可以用在任意型別、方法、構造器、引數、欄位上面

引數:

value:boolean型別,用來配置是否應發生延遲初始化,預設為true。

常用3種方式

  1. 和@Compontent一起標註在類上,可以是這個類延遲初始化

  2. 和@Configuration一起標註在配置類中,可以讓當前配置類中通過@Bean註冊的bean延遲初始化

  3. 和@Bean一起使用,可以使當前bean延遲初始化

來看一下這3種方式案例程式碼。

案例1:和@Compontent一起使用

Service1

packagecom.javacode2018.lesson001.demo27.test6;

importorg.springframework.context.annotation.Lazy;
importorg.springframework.stereotype.Component;

@Component
@Lazy//@1
publicclassService1{
publicService1(){
System.out.println("建立Service1");
}
}

@1:使用到了@Lazy,預設值為true,表示會被延遲初始化,在容器啟動過程中不會被初始化,當從容器中查詢這個bean的時候才會被初始化。

配置類

packagecom.javacode2018.lesson001.demo27.test6;

importorg.springframework.context.annotation.ComponentScan;

@ComponentScan
publicclassMainConfig6{
}

測試用例

@Test
publicvoidtest6(){
System.out.println("準備啟動spring容器");
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig6.class);
System.out.println("spring容器啟動完畢");
System.out.println(context.getBean(com.javacode2018.lesson001.demo27.test6.Service1.class));
}

執行輸出

準備啟動spring容器
spring容器啟動完畢
建立Service1
com.javacode2018.lesson001.demo27.test6.Service1@4fb61f4a

可以看出service1這個bean在spring容器啟動過程中並沒有被建立,而是在我們呼叫getBean進行查詢的時候才進行建立的,此時起到了延遲建立的效果。

案例2:和@Configuration一起使用加在配置類上

@Lazy和@Configuration一起使用,此時配置類中所有通過@Bean方式註冊的bean都會被延遲初始化,不過也可以在@Bean標註的方法上使用@Lazy來覆蓋配置類上的@Lazy配置,看下面程式碼:

配置類MainConfig7

packagecom.javacode2018.lesson001.demo27.test7;

importorg.springframework.beans.factory.annotation.Configurable;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Lazy;

@Lazy//@1
@Configurable
publicclassMainConfig7{

@Bean
publicStringname(){
System.out.println("createbean:name");
return"路人甲Java";
}

@Bean
publicStringaddress(){
System.out.println("createbean:address");
return"上海市";
}

@Bean
@Lazy(false)//@2
publicIntegerage(){
System.out.println("createbean:age");
return30;
}
}

@1:配置類上使用了@Lazy,此時會對當前類中所有@Bean標註的方法生效

@2:這個方法上面使用到了@Lazy(false),此時age這個bean不會被延遲初始化。其他2個bean會被延遲初始化。

測試用例

@Test
publicvoidtest7(){
System.out.println("準備啟動spring容器");
AnnotationConfigApplicationContextcontext=newAnnotationConfigApplicationContext(MainConfig7.class);
System.out.println("spring容器啟動完畢");

for(StringbeanName:Arrays.asList("name","age","address")){
System.out.println("----------");
System.out.println("getBean:"+beanName+",start");
System.out.println(String.format("%s->%s",beanName,context.getBean(beanName)));
System.out.println("getBean:"+beanName+",end");
}
}

上面會輸出配置類中定義的3個bean的資訊。

執行輸出

準備啟動spring容器
createbean:age
spring容器啟動完畢
----------
getBean:name,start
createbean:name
name->路人甲Java
getBean:name,end
----------
getBean:age,start
age->30
getBean:age,end
----------
getBean:address,start
createbean:address
address->上海市
getBean:address,end

輸出中可以看到age是在容器啟動過程中建立的,其他2個是在通過getBean查詢的時候才建立的。

總結

  1. 本文介紹的幾個註解也算是比較常用的,大家一定要熟悉他們的用法

  2. @Scope:用來定義bean 的作用域;2種用法:第1種:標註在類上;第2種:和@Bean一起標註在方法上

  3. @DependsOn:用來指定當前bean依賴的bean,可以確保在建立當前bean之前,先將依賴的bean建立好;2種用法:第1種:標註在類上;第2種:和@Bean一起標註在方法上

  4. @ImportResource:標註在配置類上,用來引入bean定義的配置檔案

  5. @Lazy:讓bean延遲初始化;常見3種用法:第1種:標註在類上;第2種:標註在配置類上,會對配置類中所有的@Bean標註的方法有效;第3種:和@Bean一起標註在方法上

案例原始碼

https://gitee.com/javacode2018/spring-series

路人甲java所有案例程式碼以後都會放到這個上面,大家watch一下,可以持續關注動態。

來源:https://mp.weixin.qq.com/s?__biz=MzA5MTkxMDQ4MQ==&mid=2648934284&idx=1&sn=00126ad4b435cb31726a5ef10c31af25&chksm=88621fb2bf1596a41563db5c474873c62d552ec9a440037d913704f018742ffca9be9b598680&token=887127000&lang=zh_CN&scene=21#wechat_redirect