天天看點

淺談Spring注入模型

作者:馬士兵教育CTO

Spring注入bean的方式

從Spring的官網中可以得知,Spring注入bean的方式由兩種,一種是通過構造方法進行注入,另外一種是通過setter方法進行注入。

但熟悉Spring開發的同學在實際的開發中經常使用的注入方式是通過@Autowired以及@Resource的方式來注入bean的。那麼通過注解的方式注入是跟上面兩種注入方式是一樣的嗎?在研究這個問題之前,我們需要來了解Spring的注入模型。

Spring的注入模型

如果不深入到Spring的源碼,是很少有機會了解到Spring的注入模型(AutowireMode)。但是為了掃清我們學習Spring源碼的障礙,我們有必要了解下Spring的注入模型。

Spring的注入模型有四種,分别是:

  • autowire_no(0):預設的注入模型,如果在beanA中注入beanB,如果沒有提供注解或者xml的注入方式,beanB是無法注入到beanA中的。
  • autowire_name(1):通過set方法注入,并且set的參數名必須和注入的bean名稱一樣,在xml中是通過byName
  • autowire_type(2):通過set方法注入,set的參數名可以随意命名,但是類型必須和注入的bean的類型一樣,在xml中是通過byType的方式配置
  • autowire_constructor(3):通過構造器注入

代碼示例

多說無益,我們直接上代碼來加深對這一概念的了解。

定義bean對象

我們簡單定義兩個類,分别是Student和Address,并且将他們注入到Spring容器中。

Student.java如下

@Component
@Slf4j
public class Student {

    private Address address;

    public Student(){
        log.info("default constructor...");
    }

    public Student(Address address){
        log.info("constructor inject...{}", address);
        this.address = address;
    }

    public void setAddress(Address address){
        log.info("setter...{}", address);
        this.address = address;
    }

    public void showAddress(){
        this.address.info();
    }
}           

Address.java如下

@Component
public class Address {

    public void info(){
        System.out.println("廣東省廣州市白雲區");
    }
}           

我們可以看到在Student中有一個屬性是Address,并且提供了預設的構造方法以及帶參數的構造方法,同時還有一個 set方法。

定義配置類

我們再定義一個config類,用來掃描這兩個類所在的包路徑,把這兩個類注入到Spring容器中。

@ComponentScan("org.example.autoModel.model")
public class MyBeanConfig {
}           

定義後置處理器

在Spring中,我們可以在自己定義的後置處理器BeanFactoryPostProcessor,擷取到對應的BeanDefinition。

相信大家在學習Spring的時候有了解過BeanDefinition,不熟悉的可以搜尋下BeanDefinition的概念,如果閱讀過Spring的源碼的話,對BeanDefinition應該就更加熟悉了。這裡就不做過多的解釋了,後面在解讀Spring源碼的時候應該也會涉及到這部分的知識。

簡單的來說,就是一個對象被注入到Spring中,實際是被解析成BeanDefinition對象,裡面儲存了各種需要用到的資訊,并且可以在後置處理器中擷取到對應的BeanDefinition對象,然後對其做一些操作。

@Slf4j(topic = "e")
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition("student");
//        beanDefinition.setAutowireMode(3);
        log.debug("mode:{}",beanDefinition.getAutowireMode());
    }
}           

上面代碼所示,我們可以擷取這個beanDefinition在Spring容器中對應的autowireMode的值,并且可以修改這個autowireMode的值,來觀察下注入模型的改變,對bean的注入方式有什麼影響。

定義測試方法

@Test
public void modelTest(){
   AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
   context.register(MyBeanConfig.class);
   context.register(MyBeanFactoryPostProcessor.class);
   context.refresh();
   Student student = context.getBean(Student.class);
   student.showAddress();
}           

執行測試方法,得出如下的結果。

淺談Spring注入模型

如上面所示,我們可以得到對應的autowireMode的值是0,并且是執行了Student的預設構造方法,由于沒有将address屬性注入到student中,所在調用address對應的方法的時候,抛出了空指針的異常。

針對上面的對象,我們可以簡單分析下:

  1. Spring預設的注入模型是0
  2. 注入模型是0,意味着執行預設的構造方法,并且不會執行set方法去進行注入address這個對象,是以抛出了空指針異常

我們可以在後置處理器中,修改對應autowioreMode的值,将autowireMode的值修改成1。

beanDefinition.setAutowireMode(1);           

然後執行該測試方法,得到下面的結果。

淺談Spring注入模型

可以看到我們将注入模型的值修改成1,之後,spring就會通過set方法,将address注入到student中,就可以成功調用address的方法了。

當然,我們将注入模型的值修改成2,也是注入成功的,并且也是通過set方法,隻不過是byType和byName的差別。這個同學們可以自己嘗試下。

下面,我們将注入模型的值修改成3來看下執行的結果。

beanDefinition.setAutowireMode(3);           

結果如下:

淺談Spring注入模型

可以看到,如果将注入模型的值修改成了3,那麼Spring是通過帶參的構造方法來注入給student的。

通過上面簡單的例子,我們就可以了解到注入模型autowireMode對注入方式的影響,這為我們以後閱讀Spring的源碼打下了基礎。

自動注入和手動注入

上面我們将autowireMode值修改成1、2、3,就可以完成屬性的自動注入。那如果我們不去修改autowireMode的值,而是使用注解來将屬性注入到student中,會是怎樣的結果呢?

我們來看下面的代碼:

@Component
@Slf4j
public class Student {

    @Autowired
    private Address address;

    public Student(){
        log.info("default constructor...");
    }

    public Student(Address address){
        log.info("constructor inject...{}", address);
        this.address = address;
    }

    public void setAddress(Address address){
        log.info("setter...{}", address);
        this.address = address;
    }

    public void showAddress(){
        this.address.info();
    }
}           
@Slf4j(topic = "e")
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        AbstractBeanDefinition beanDefinition = (AbstractBeanDefinition) beanFactory.getBeanDefinition("student");
//        beanDefinition.setAutowireMode(3);
        log.debug("mode:{}",beanDefinition.getAutowireMode());
    }
}           

檢視結果

淺談Spring注入模型

我們發現使用@Autowired注解注入屬性的時候,注入模型的值是0,也就是預設的值。

對于@Autowired的注解,我的看法是,使用注解注入屬性是手動注入的方式,Spring提供自動注入的方式隻有set方法以及帶參的構造方法。而注解的注入方式,隻不過是xml手動注入的一種簡化,Spring内部處理的機制都是類似的。

了解過xml配置的同學大概有些印象,我們需要指定bean中屬性所對應的類的全路徑。而使用自動注入,我們可以指定byType或者byName。由于xml方式太久沒用了,大概就是這麼個意思吧。

是以我認為@Autowired注入的方式,不是自動注入,而是手動注入,隻不過Spring容器内部幫我們處理了。當然,關于Spring容器如何處理@Autowired注解的方式注入屬性的,同學們可以通過閱讀源碼來大緻了解下。後面大概也會解讀下這部分的源碼。

以上便是鄙人對Spring注入模型的淺薄認識,如果有錯誤的話,還請同學們多多包涵。