挖一挖@Bean這個東西

不該相遇在秋天發表於2019-01-25

有Bean得治

  任何一個正常程式的訪問都會在記憶體中建立非常多的物件,物件與物件之間還會出現很多依賴關係(一個處理業務邏輯的類中幾乎都會使用到別的類的例項),一般的做法都是使用new關鍵字來建立物件,對於多次重複使用的物件會採用單例模式來設計。

  但在Spring中卻不是這樣,Spring框架使用了一個容器對這些物件進行管理,每一個需要被管理的物件被稱為Bean,而管理這些Bean的容器,被稱為IoC容器。

  控制反轉(IoC)在是一種通過描述來生成或者獲取物件的技術,在Spring Boot中,我們經常是通過註解來建立物件,這裡只談註解,無視XML。

  Spring Boot在啟動的過程中,會去掃描需要被管理的Bean,將Bean裝載到IoC容器中,根據依賴關係進行例項化物件,最後進行依賴注入。也就是說,在專案啟動完成後,所有的Bean都已經例項化完成,並已在相應的地方注入完成,訪問程式使用到的這些物件都只是在直接呼叫物件例項,並不會出現一個new的過程,因為這些物件在專案啟動的時候就已經都被new出來了。

  Bean的建立方式有很多種,最常用的是@Component,@Service,@Repository等等直接放在類頭上的註解,都是一些標記,用於被收集後反射再例項化最終賦值達到注入的目的,可瞭解的應該就是其中的一些附帶規則:條件裝配,就是如果存在某個Bean時(也可以判斷不存在時)再裝配當前這個Bean,不過條件裝配我覺得應用場景並不常見。

  這裡主要是要挖一挖@Bean這個註解的使用。

用@Bean建立Bean

  @Bean註解是隻能用在方法上,標誌該方法需要建立一個Bean,方法返回的物件就是建立Bean的目標物件。

建一個User實體類:

public class User {
    private int id = 2;
    private String name = "我是User類的小光";
    private String sex = "我是難的";

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name=`" + name + ``` +
                ", sex=`" + sex + ``` +
                `}`;
    }
}

 

示例:

@Configuration  
public class Config {
    @Bean
    public String aaa(){
        return "你大爺";
    }

    @Bean
    public User bbb(){
        return new User();
    }
}  

  這個Config類,建立了兩個Bean,建立的物件一個是String物件一個是User物件,可以是任何物件,我這裡為了更加直白的表達“Bean”是什麼東西,特意使用了String來做例子。

當然了  bean的名字是可以定的 以下三種定義方式都可以:

    @Bean(name = "aaa")
    @Bean("aaa")
    @Bean

第三種方式沒有指定名字,那麼框架會將方法名作為bean的名字,注意:是方法原名,不會是小駝峰,只有應用在類上的註解建立的bean才會是小駝峰,@Bean是方法上的註解。

你的腦補絕對有用

  此時,你可以腦補一下框架是怎麼建立Bean的:

        HashMap map = new HashMap();
        map.put("aaa",new String("你大爺"));
        map.put("bbb",new User());

  所謂IoC容器,就是一個集合,裝了很多物件,你要用哪個物件,Spring就會從集合裡取給你。

—–>當然了,雖然你的腦補不一定完全正確,但是你的腦補絕對有用。

@Bean註解受不受@ComponentScan的影響?

  @ComponentScan註解定義掃描路徑,也就是說,Spring Boot只會掃描指定路徑之內的類,遇到特殊註解就會開始進行Bean的收集工作。

  在Spring Boot的啟動類main方法上有@SpringBootApplication註解,點進去之後可以看到它裡面包含的有@ComponentScan,所以儘管我們沒有手動指定掃描範圍,但是預設是以啟動類所在的包作為一個掃描範圍。

  但這裡要說明的是,@ComponentScan這個註解,它是用來約束@Configuration,@Controller,@Service,@Repository,@Component等作用在類上註解,但是它管不了@Bean註解,只要專案中有@Bean註解,即使身處啟動類的外層,不在@ComponentScan的掃描範圍之內,該Bean也會存在,只是沒有被掃描裝配,也就是可以獲取,但無法注入。

是否一定要和@Configuration一起用? 

  我的答案是:是的!

  如果你沒有加@Configuration註解,那麼Spring將不會掃描這個類,但是你後期獲取該Bean的時候,Spring可以找到它,也就是說,如果沒有加@Configuration註解,那麼專案啟動後是沒有該Bean的,只有當你獲取該Bean的時候,才會進行例項化。

獲取你的Bean

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
        Object aaa = context.getBean("aaa");
        System.out.println(aaa.toString());

        Object bbb = context.getBean("bb");
        System.out.println(bbb.toString()); 

該示例程式碼獲取的是Config類中的aaa和bbb兩個Bean。

  注意,它並不是獲取容器中的Bean,它是根據你提供的class和Bean名稱跑去執行了一下方法,新new了一個物件給你,再有,這個Bean名稱必須用原名,大小寫不打折的。

  小提示:無論類的頭上有沒有@Configuration註解,該方式都可以成功,當然,如果類的頭上有@Configuration註解,那麼該Bean的返回物件可以直接在別處進行注入。

 

如果你要獲取類上的Bean,可以採用下面這個工具類來獲取:

@Component
public class SpringUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext = null;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        if (null == SpringUtil.applicationContext) {
            SpringUtil.applicationContext = applicationContext;
        }
    }

    // 獲取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    // 通過name獲取 Bean.
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    // 通過class獲取Bean.
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    // 通過name,以及Clazz返回指定的Bean
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}  

 

相關文章