Spring系列之@Scope、@DependsOn、@ImportResource、@Lazy 詳解

語言: CN / TW / HK

面試問題

  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 @interface Scope {

@AliasFor("scopeName")     String value() default "";

@AliasFor("value")     String scopeName() default "";

ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;

} ```

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

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

常見2種用法

  1. 和@Compontent一起使用在類上
  2. 和@Bean一起標註在方法上

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

@Component @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)//@1 public class ServiceA { }

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

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

String SCOPE_SINGLETON = "singleton";

String SCOPE_PROTOTYPE = "prototype";

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

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

@Configurable public class MainConfig2 {     @Bean     @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)     public ServiceA serviceA() {         return new ServiceA();     } }

@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 @interface DependsOn {

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

``` package com.javacode2018.lesson001.demo27.test3;

import org.springframework.stereotype.Component;

@Component public class Service2 {     public Service2() {         System.out.println("create Service2");     } } ```

Service3

``` package com.javacode2018.lesson001.demo27.test3;

import org.springframework.stereotype.Component;

@Component public class Service3 {     public Service3() {         System.out.println("create Service3");     } } ```

Service1

``` package com.javacode2018.lesson001.demo27.test3;

import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component;

@DependsOn({"service2", "service3"}) //@1 @Component public class Service1 {     public Service1() {         System.out.println("create Service1");     } } ```

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

來個配置類

``` package com.javacode2018.lesson001.demo27.test3;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan public class MainConfig3 { } ```

測試用例

@Test public void test3() {     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig3.class);     System.out.println(context.getBean(Service1.class)); }

執行輸出

create Service2 create Service3 create Service1 com.javacode2018.lesson001.demo27.test3.Service1@9f116cc

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

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

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

``` package com.javacode2018.lesson001.demo27.test4;

import org.springframework.beans.factory.annotation.Configurable; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.DependsOn;

@Configurable public class MainConfig4 {

@Bean     @DependsOn({"service2", "service3"})//@1     public Service1 service1() {         return new Service1();     }

@Bean     public Service2 service2() {         return new Service2();     }

@Bean     public Service3 service3() {         return new Service3();     }

} ```

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

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

來個測試用例

@Test public void test4() {     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig4.class);     System.out.println(context.getBean(com.javacode2018.lesson001.demo27.test4.Service1.class)); }

執行輸出

create Service2 create Service3 create Service1 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 @interface ImportResource {

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

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

Class<? extends BeanDefinitionReader> reader() default BeanDefinitionReader.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

``` package com.javacode2018.lesson001.demo27.test5;

public class ServiceA { } ```

ServiceB

``` package com.javacode2018.lesson001.demo27.test5;

public class ServiceB { } ```

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

```

```

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

```

```

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

``` package com.javacode2018.lesson001.demo27.test5;

import org.springframework.beans.factory.annotation.Configurable; import org.springframework.context.annotation.ImportResource;

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

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

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

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

@Test public void test5() {     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig5.class);     for (String beanName : 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 @interface Lazy {

boolean value() default true;

} ```

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

引數:

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

常用3種方式

  1. 和@Compontent一起標註在類上,可以是這個類延遲初始化
  2. 和@Configuration一起標註在配置類中,可以讓當前配置類中通過@Bean註冊的bean延遲初始化
  3. 和@Bean一起使用,可以使當前bean延遲初始化

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

案例1:和@Compontent一起使用

Service1

``` package com.javacode2018.lesson001.demo27.test6;

import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component;

@Component @Lazy //@1 public class Service1 {     public Service1() {         System.out.println("建立Service1");     } } ```

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

配置類

``` package com.javacode2018.lesson001.demo27.test6;

import org.springframework.context.annotation.ComponentScan;

@ComponentScan public class MainConfig6 { } ```

測試用例

@Test public void test6() {     System.out.println("準備啟動spring容器");     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(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

``` package com.javacode2018.lesson001.demo27.test7;

import org.springframework.beans.factory.annotation.Configurable; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Lazy;

@Lazy //@1 @Configurable public class MainConfig7 {

@Bean     public String name() {         System.out.println("create bean:name");         return "路人甲Java";     }

@Bean     public String address() {         System.out.println("create bean:address");         return "上海市";     }

@Bean     @Lazy(false) //@2     public Integer age() {         System.out.println("create bean:age");         return 30;     } } ```

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

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

測試用例

``` @Test public void test7() {     System.out.println("準備啟動spring容器");     AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig7.class);     System.out.println("spring容器啟動完畢");

for (String beanName : 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容器 create bean:age spring容器啟動完畢


getBean:name,start create bean:name name->路人甲Java getBean:name,end


getBean:age,start age->30 getBean:age,end


getBean:address,start create bean: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一起標註在方法上