一、@Bean的“full”模式和“lite”模式
在一般常見情況下,@Bean註解在@Configuration類中聲明,稱之爲“full”模式;當@Bean註解和@Component註解組合使用時,稱之爲“lite”模式。這兩種組合使用的情況,初次看文檔時沒看明白,多看了幾次又跑了測試代碼,才大致理解了區別。
二、兩種模式的差異
如果只是把@Bean註解用在方法上,並且各個@Bean註解的方法之間沒有調用,上述兩種模式達到的效果基本相同,都可以把@Bean註解方法返回的對象作爲bean註冊到容器中。如果各個@Bean註解的方法之間有相互調用,那麼兩種模式就會有很大的區別-與full模式下的@Configuration不同,lite模式下 @Bean方法互相調用無法聲明Bean之間的依賴關係。這點在@Bean註解源碼註釋上也有說明。
1、“full”模式下@Bean方法互相調用
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Configuration
public class Config {
@Bean()
public C c(){
return new C();
}
@Bean
public B b(){
return new B(c());
}
}
類B:
public class B {
public C c;
public B(C a){this.c=a;}
public void shutdown(){
System.out.println("b close...");
}
}
類C:
public class C {
private String name;
@PostConstruct
public void init(){
this.name = "I am a bean";
}
@Override
public String toString(){
return "c say:" + name;
}
}
如上述所示代碼,我們有兩個類B和C,在@Configuration類中,使用兩個@Bean方法分別定義bean b 和bean c,但是需要注意的是new B(c()) 所在的@Bean方法調用了另一個@Bean方法。
測試主程序類:
import com.dxc.opentalk.springtest.config.B;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy
@ComponentScan("com.dxc.opentalk.springtest")
public class BootStrap {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(BootStrap.class);
B b = (B)applicationContext.getBean("b");
System.out.println(b.c);
System.out.println(applicationContext.getBean("c"));
System.out.println(applicationContext.getBean("c").equals(b.c));
}
}
程序輸出結果:
c say:I am a bean
c say:I am a bean
true
可以看出bean c 被注入到了bean b 中,bean b中的成員 c 確實是Spring容器中的bean。(其實,debug程序跟蹤Spring中的單例池更清晰,這裏採用輸出結果說明)
2、“lite”模式下@Bean方法互相調用
還是上面的代碼,我們只把Config類上的註解更換成@Component,其餘代碼保持不變:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Component
public class Config {
@Bean()
public C c(){
return new C();
}
@Bean
public B b(){
return new B(c());
}
}
再看一下測試主程序類輸出結果:
c say:null
c say:I am a bean
false
可以看到bean b中的c只是一個普通的c對象,並不是Spring容器中的bean(b中的c沒有執行bean的初始化回調方法也和單例池中的c bean不相等)。所以這種模式下,@Bean方法互相調用不能完成bean之間相互依賴關係的注入。
三、總結
綜上所述,在使用@Bean註解時需要注意兩種模式的區別,一般情況下@Bean都是和@Configuration註解搭配使用的。但是@Configuration註解的類會被Spring使用CGLIB子類化,所以@Configuration註解的類不能用 final 修飾,而@Component註解沒有此限制。如果使用@Bean註解和@Component註解組合需要完成bean之間相互依賴注入的話,官方推薦採用構造方法或者方法級別的依賴注入,如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Component
public final class Config {
@Bean()
public C c(){
return new C();
}
@Bean
public B b(C c){//構造方法注入
return new B(c);
}
}