Example
如果我們的類有如下成員變量:
@Component
public class A {
@Autowired
public B b; // B is a bean
public static C c; // C is also a bean
public static int count;
public float version;
public A() {
System.out.println("This is A constructor.");
}
@Autowired
public A(C c) {
A.c = c;
System.out.println("This is A constructor with c argument.");
}
@PostConstruct
public void init() {
count = 5;
System.out.println("This is A post construct.");
}
}
下面的結(jié)論可以通過在構(gòu)造函數(shù)里打斷點Debug來觀察。
- 首先初始化的是static成員變量, 此處的
count采用默認(rèn)值0。 - 然后初始化的是非static成員變量,此處的
version采用默認(rèn)值0.0。 - 然后Spring在實例化A時選擇的構(gòu)造函數(shù)的原則是:如果有構(gòu)造函數(shù)被
@Autowired所修飾,則采用該構(gòu)造函數(shù)(注意,@Autowired(required = true)只能修飾一個構(gòu)造函數(shù)),否則采用默認(rèn)的無參構(gòu)造函數(shù)。此處采用的構(gòu)造函數(shù)為
注意執(zhí)行完該構(gòu)造函數(shù)后,此時的成員變量B并沒有被注入,值還是null。@Autowired public A(C c) { this.c = c; System.out.println("This is A constructor with c argument."); } - Spring容器選擇合適的Bean注入b (DI階段)。
- 執(zhí)行被
@PostConstruct修飾的init()函數(shù)。
總之,在上面這個例子中,各成員變量的初始化執(zhí)行順序為:“static 成員變量 ”--> “非static成員變量” --> “被@Autowired修飾的構(gòu)造函數(shù)” --> “被@Autowired修飾的成員變量b” --> “被@PostConstruct修飾的init()函數(shù)”。
上述過程也是一個Bean完整生成的過程,因此要注意不同于一般類的實例化,Bean在構(gòu)造函數(shù)完成后還有DI(Dependency Injection)階段,通過Spring容器注入它所依賴的其他Bean,想要在一個Bean生成完后進行其他操作,可以使用@PostConstruct。
Tips
1. 靜態(tài)成員依賴注入
有時我們想要對靜態(tài)成員進行依賴注入(通常是Field dependency injection,即直接在成員上加@Autowired,此種做法不推薦),直接在靜態(tài)成員上加@Autowired是無效的(其值總為null),這是因為靜態(tài)成員變量是類的屬性,不屬于任何對象,而Spring實現(xiàn)Field dependency injection 是要依靠基于實例的reflection(反射)進行的。在這個例子中,Spring通過反射生成bean a, 并且發(fā)現(xiàn)a使用了bean b,然后去生成bean b,再次利用反射生成setter方法將b注入進a,這樣就實現(xiàn)了Field dependency injection。通過上述過程我們可以知道static成員由于不屬于任何實例,所以無法實現(xiàn)這樣的依賴注入,但是我們可以通過Constructor dependency injection(構(gòu)造函數(shù)依賴注入)來實現(xiàn)。以上面的例子為例,Spring在生成bean a(調(diào)用A的構(gòu)造函數(shù))時,由于A的構(gòu)造函數(shù)帶有參數(shù)c,Spring將在容器里尋找是否有符合c類型的bean,找到后將bean c賦值給構(gòu)造函數(shù)的參數(shù)c,然后當(dāng)執(zhí)行到A.c = c時成員變量c就被“注入”成功了。
2. Bean的延遲生成與注入
如果我們希望某個Bean不要在Spring容器啟動時初始化(這樣可以加快應(yīng)用的啟動速度),而是在用到時才實例化,可以用@Lazy這個注解。將這個注解加在@Bean、@Component、@Service、@Configuration等注解上時,這些注解所修飾的Bean將在第一次引用時才實例化。我們舉個簡單的例子:
存在一個普通Bean
@Component
public class CommonBean {
public CommonBean () {
System.out.println("This is CommonBean constructor.");
}
}
以及加了@Lazy的“LazyBean”
@Lazy
@Component
public class LazyBean {
public LazyBean() {
System.out.println("This is LazyBean constructor.");
}
}
假設(shè)由于某種原因,Spring掃描路徑的順序是LazyBean先于CommonBean,如果LazyBean不加@Lazy注解,則Bean的生成順序永遠是LazyBean先于CommonBean;若是加上@Lazy,則CommonBean先生成,并且如果沒有其他地方引用LazyBean(例如沒有其他Bean @Autowired LazyBean),那么LazyBean將一直不進行初始化(直觀表現(xiàn)為它的構(gòu)造函數(shù)一直未被調(diào)用)。在實際應(yīng)用中,如果Bean的數(shù)目比較多,無法立即理清依賴關(guān)系,但確定自己的Bean需要在其他Bean生成之后才生成時,可以通過此方法簡單的控制Bean的生成順序。
注意,若是在CommonBean里引用了LazyBean ,則不論LazyBean有沒有使用@Lazy,它都會先完成實例化,即Bean之間引用的作用會大于@Lazy的作用。
@Lazy同樣可以加在@Autowired注解上,比如
@Component
public class CommonBean {
@Lazy
@Autowired
private LazyBean lazyBean;
}
在CommonBean生成過程中的DI階段,它不關(guān)心LazyBean是否存在于Spring容器中(一般情況下,如果沒有添加@Lazy注解并且沒有類型為LazyBean的Bean在容器中的情況下, 會拋找不到Bean的異常),它會生成一個有同樣接口的代理(由于是代理,沒有真正的功能,但是LazyBean的構(gòu)造函數(shù)及@PostConstruct所修飾的方法都會被調(diào)用),只有真正要調(diào)用LazyBean的方法時,該代理才會在自身內(nèi)部生成LazyBean實例(此時不會再次調(diào)用構(gòu)造函數(shù)及@PostConstruct所修飾的方法,調(diào)用代理的各種方法將會轉(zhuǎn)發(fā)到生成的真正的LazyBean中),這樣也相當(dāng)于實現(xiàn)了延遲加載Bean的功能。