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;
}
}