Spring爲IOC容器注入Bean的方式

      Spring提供的主要功能就是對於bean的管理,提供了多種方式可以向容器中注入bean,下面來總結一下向IOC容器注入bean的幾種方式(以下注入bean的方式都是基於註解完成的):

      1、@ComponentScan+@Component方式

       @ComponentScan可以掃描指定包下的類,如果該包下的類標有@Component、@Service、@Repository、@Controller、@RestController和@Configuration,都會被注入到IOC容器中,這種方式也是我們寫代碼最常用的,一般針對自己寫的類。

       我們寫的配置類,在上面標有@ComponentScan,制定掃描的的包,這時被掃描的類需要提供無參構造方法,不然會報錯。

@ComponentScan(basePackages = {"it.cast.componentScan"})
public class Config {

}

       這個person類在it.cast.componentScan包下面,會被掃描並注入到容器中

package it.cast.componentScan.config;

import org.springframework.stereotype.Component;

//使用這種方式需要替換無參構造的方法,因爲spring是調用無參構造方法創建類的
@Component
public class Person {
    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

}

        2、使用@Configuration + @Bean註解

        該方法一般用於導入的第三方包裏面的組件,因爲第三方包裏面沒有添加Spring相關的註解,所以使用第一種方式就不行了。

@Configuration
public class Config1 {

    /**
     * 使用@Bean方式向容器注入bean,適用於導入的第三方包裏面的組件
     * 在@Bean後面不跟其他屬性時,bean的名稱默認使用方法名
     * 在@Bean("person"), 如指定方法名,則使用定製的方法名
     * 在@bean中還有initMethod屬性和destroyMethod屬性,可以指定初始話方法和銷燬方法
     */

    @Bean("person")
    public Person person(){
        return new Person();
    }
}

      3、使用@import註解

         該方法注入的bean的id默認是組件的全類名 ,使用@import就是將類注入到容器中,如果要注入的類沒有被標註@Component也能被注入進來,一般注入的都是標註了@Configuration的配置類。

/**
 * 該方式會將Bike類注入到容器中
 *
 * */
@Configuration
@Import({Bike.class})
public class Config1 {

    @Bean("person")
    public Person person(){
        return new Person();
    }
}

public class Bike {

}

      4.實現ImportSelector接口來向容器注入bean

      注意:使用這種方式的話返回值不能爲null,不然會出現空指針異常

/**
*使用@Import註解,是將MyImportSelector類注入到IOC容器中,至於它是不是ImportSelector的實現類,
*這個@Import註解是不進行判斷的,在注入這個bean後,有其他的組件會找到ImportSelector的實現類,並調
*用selectImports方法進行註冊bean
* */

@Configuration
@Import({MyImportSelector.class})
public class ImportConfig {

}

class MyImportSelector implements ImportSelector {
    /**
     *返回值,就是到導入到容器中的組件全類名,返回值時一個字符串數組,可以導入多個bean
     *AnnotationMetadata:當前標註@Import註解的類的所有註解信息
     * */
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"it.cast.Bike"};
    }
}

       5.實現ImportBeanDefinitionRegistrar接口來向容器注入bean

       這裏的代碼來自於DataSourceInitializedPublisher$Registrar類

/**
 * {@link ImportBeanDefinitionRegistrar} to register the
 * {@link DataSourceInitializedPublisher} without causing early bean instantiation
 * issues.
 */
static class Registrar implements ImportBeanDefinitionRegistrar {

    private static final String BEAN_NAME = "dataSourceInitializedPublisher";

      /**
       * AnnotationMetadata:當前類的註解信息
       * BeanDefinitionRegistry:BeanDefinition註冊類;
       *      把所有需要添加到容器中的bean;調用
       *      BeanDefinitionRegistry.registerBeanDefinition手工註冊進來
       */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {
        if (!registry.containsBeanDefinition(BEAN_NAME)) {
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(DataSourceInitializedPublisher.class);
            beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
            // We don't need this one to be post processed otherwise it can cause a
            // cascade of bean instantiation that we would rather avoid.
            beanDefinition.setSynthetic(true);
            registry.registerBeanDefinition(BEAN_NAME, beanDefinition);
        }
    }

}

        該方法可以有選擇性的注入bean,傳遞的參數可以獲取到IOC容器中的bean定義,使用這樣方式比較靈活,在查看Spring源碼時,大量使用了這種方式。

       下面這段代碼是我在沒學習Spring源碼前錯誤的理解:

     只要把ImportBeanDefinitionRegistrar的實現類放到容器後,Spring會在合適的使用收集到所有的ImportBeanDefinitionRegistrar的實現類,然後挨個調用registerBeanDefinitions方法。(這句話的錯誤的)

      正確的理解:

      ImportBeanDefinitionRegistrar的實現類,必須是被@Import進行導入的,如@Import(Registrar .class),如果是我們不使用@Import註解導入Registrar類,而是使用一個@Component註解,將Registrar類通過掃描的方式放入到容器中,那麼registerBeanDefinitions方法就不會被執行。

 

@Import導入的原理:

       處理這個@Import是在ConfigurationClassPostProcessor類中進行的,ConfigurationClassPostProcessor類會掃描出所有的對象,封裝成beanDefinition對象,然後判斷是否對象中是否加了@Import註解,加了的話判斷是否爲ImportBeanDefinitionRegistrar的實現類,或者是ImportBeanDefinitionRegistrar的實現類,如果是,則執行接口對象的方法(這只是見簡單的說一下,其實步驟複雜的多,會有遞歸調用什麼的,這裏簡單理解一下就行)

      所以,在看Spring源碼前的理解是錯誤的,ImportBeanDefinitionRegistrar和ImportBeanDefinitionRegistrar只能在@Import中實現,而且是一個類一個類的判斷有沒有@Import註解,不是找到所有的之後再進行調用,後面有時間我會寫一篇關於處理@import的博客。

        6.實現FactoryBean接口來向容器注入bean

        使用Spring提供的 FactoryBean(工廠Bean),默認獲取到的是工廠bean調用getObject創建的對象,要獲取工廠Bean本身,我們需要給id前面加一個&

@Configuration
public class FactoryBeanConfig {
    /**
     * 在容器裏面注入FactoryBeanTest
     * 在獲取factoryBeanTest名稱的bean時,得到的是Pig類型的bean,如想要得到FactoryBeanTest類型的bean,需要使用
     * AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FactoryBeanConfig.class);
     * String[] beanDefinitionNames = context.getBeanDefinitionNames("&factoryBeanTest");  這種獲取的是FactoryBeanTest類型的bean
     * String[] beanDefinitionNames = context.getBeanDefinitionNames("factoryBeanTest");  這種獲取的是Pig類型的bean
     * */
    @Bean
    public FactoryBeanTest factoryBeanTest(){
        return new FactoryBeanTest();
    }
}

public class FactoryBeanTest implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        return new Pig();
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public Class<?> getObjectType() {
        return Pig.class;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章