朱曄和你聊Spring系列S1E3:Spring咖啡罐裏的豆子

標題中的咖啡罐指的是Spring容器,容器裏裝的固然就是被稱做Bean的豆子。本文咱們會以一個最基本的例子來熟悉Spring的容器管理和擴展點。
閱讀PDF版本java

爲何要讓容器來管理對象?

首先咱們來聊聊這個問題,爲何咱們要用Spring來管理對象(的生命週期和對象之間的關係)而不是本身new一個對象呢?你們可能會回答是方便,爲了解耦。我我的以爲除了這兩個緣由以外,還有就是給予了咱們更多可能性。若是咱們以容器爲依託來管理全部的框架、業務對象,那麼不只僅咱們能夠無侵入調整對象的關係,還有可能無侵入隨時調整對象的屬性甚至悄悄進行對象的替換。這就給了咱們無限多的可能性,大大方便了框架的開發者在程序背後實現一些擴展。不只僅Spring Core自己以及Spring Boot大量依賴Spring這套容器體系,一些外部框架也由於這個緣由能夠和Spring進行無縫整合。
Spring能夠有三種方式來配置Bean,分別是最先期的XML方式、後來的註解方式以及如今最流行的Java代碼配置方式。spring

Bean的回調事件

在前文parent模塊(空的一個SpringBoot應用程序)的基礎上,咱們先來建立一個beans模塊:apache

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>me.josephzhu</groupId>
    <artifactId>spring101-beans</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring101-beans</name>
    <description></description>

    <parent>
        <groupId>me.josephzhu</groupId>
        <artifactId>spring101</artifactId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

而後來建立咱們的豆子:c#

package me.josephzhu.spring101beans;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Component
public class MyService implements InitializingBean, DisposableBean {

    public int increaseCounter() {
        this.counter++;
        return counter;
    }

    public int getCounter() {
        return counter;
    }

    public void setCounter(int counter) {
        this.counter = counter;
    }

    private int counter=0;

    public MyService(){
        counter++;
        System.out.println(this + "#constructor:" + counter);
    }

    public String hello(){
        return this + "#hello:" + counter;
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println(this + "#preDestroy:" + counter);

    }

    @Override
    public void afterPropertiesSet() {
        counter++;
        System.out.println(this + "#afterPropertiesSet:" + counter);
    }

    @PostConstruct
    public void postConstruct(){
        counter++;
        System.out.println(this + "#postConstruct:" + counter);
    }

    @Override
    public void destroy() {
        System.out.println(this + "#destroy:" + counter);

    }
}

這裏能夠看到,咱們的服務中有一個counter字段,默認是0。這個類咱們實現了InitializingBean接口和DisposableBean接口,同時還建立了兩個方法分別加上了@PostConstruct和@PreDestroy註解。這兩套實現方式均可以在對象的額外初始化功能和釋放功能,註解的實現不依賴Spring的接口,侵入性弱一點。
接下去,咱們建立一個Main類來測試一下:app

package me.josephzhu.spring101beans;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import javax.annotation.Resource;

@SpringBootApplication
public class Spring101BeansApplication implements CommandLineRunner {

    @Autowired
    private ApplicationContext applicationContext;
    @Resource
    private MyService helloService;
    @Autowired
    private MyService service;

    public static void main(String[] args) {
        SpringApplication.run(Spring101BeansApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println("====================");
        applicationContext.getBeansOfType(MyService.class).forEach((name, service)->{
            System.out.println(name + ":" + service);
        });

        System.out.println("====================");
        System.out.println(helloService.hello());
        System.out.println(service.hello());
    }
}

ApplicationContext直接注入便可,不必定須要用ApplicationContextAware方式來獲取。執行程序後能夠看到輸出以下:框架

me.josephzhu.spring101beans.MyService@7fb4f2a9#constructor:1
me.josephzhu.spring101beans.MyService@7fb4f2a9#postConstruct:2
me.josephzhu.spring101beans.MyService@7fb4f2a9#afterPropertiesSet:3
====================
myService:me.josephzhu.spring101beans.MyService@7fb4f2a9
====================
me.josephzhu.spring101beans.MyService@7fb4f2a9#hello:3
me.josephzhu.spring101beans.MyService@7fb4f2a9#hello:3
me.josephzhu.spring101beans.MyService@7fb4f2a9#preDestroy:3
me.josephzhu.spring101beans.MyService@7fb4f2a9#destroy:3

這裏咱們使用@Resource註解和@Autowired註解分別引用了兩次對象,能夠看到因爲Bean默認配置爲singleton單例,因此容器中MyService類型的對象只有一份,代碼輸出也能夠證實這點。此外,咱們也經過輸出看到了構造方法以及兩套Bean回調的次序是:maven

  1. 類本身的構造方法
  2. @PostConstruct註釋的方法
  3. InitializingBean接口實現的方法
  4. @PreDestroy註釋的方法
  5. DisposableBean接口實現的方法

Java 代碼方式建立Bean

從剛纔的輸出中能夠看到,在剛纔的例子中,咱們爲Bean打上了@Component註解,容器爲咱們建立了名爲myService的MyService類型的Bean。如今咱們再來用Java代碼方式來建立相同類型的Bean,建立以下的文件:ide

package me.josephzhu.spring101beans;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class ApplicationConfig {

    @Bean(initMethod = "init")
    public MyService helloService(){
        MyService myService = new MyService();
        myService.increaseCounter();
        return myService;
    }

}

這裏能夠看到在定義Bean的時候咱們關聯了一個initMethod,所以咱們須要修改Bean加上這個方法:spring-boot

public void init() {
    counter++;
    System.out.println(this + "#init:" + counter);

}

如今咱們運行代碼看看結果,獲得了以下錯誤:post

Field service in me.josephzhu.spring101beans.Spring101BeansApplication required a single bean, but 2 were found:
    - myService: defined in file [/Users/zyhome/IdeaProjects/spring101/spring101-beans/target/classes/me/josephzhu/spring101beans/MyService.class]
    - helloService: defined by method 'helloService' in class path resource [me/josephzhu/spring101beans/ApplicationConfig.class]

出現錯誤的緣由是@Autowired了一個MyService,@Resource註解由於使用Bean的名稱來查找Bean,因此並不會出錯,而@Autowired由於根據Bean的類型來查抄Bean找到了兩個匹配全部出錯了,解決方式很簡單,咱們在多個Bean裏選一個做爲主Bean。咱們修改一下MyService加上註解:

@Component
@Primary
public class MyService implements InitializingBean, DisposableBean

這樣,咱們的@Resource根據名字匹配到的是咱們@Configuration出來的Bean,而@Autowired根據類型+Primary匹配到了@Component註解定義的Bean,從新運行代碼來看看是否是這樣:

me.josephzhu.spring101beans.MyService@6cd24612#constructor:1
me.josephzhu.spring101beans.MyService@6cd24612#postConstruct:3
me.josephzhu.spring101beans.MyService@6cd24612#afterPropertiesSet:4
me.josephzhu.spring101beans.MyService@6cd24612#init:5
me.josephzhu.spring101beans.MyService@7486b455#constructor:1
me.josephzhu.spring101beans.MyService@7486b455#postConstruct:2
me.josephzhu.spring101beans.MyService@7486b455#afterPropertiesSet:3
====================
myService:me.josephzhu.spring101beans.MyService@7486b455
helloService:me.josephzhu.spring101beans.MyService@6cd24612
====================
me.josephzhu.spring101beans.MyService@6cd24612#hello:5
me.josephzhu.spring101beans.MyService@7486b455#hello:3
me.josephzhu.spring101beans.MyService@7486b455#preDestroy:3
me.josephzhu.spring101beans.MyService@7486b455#destroy:3
me.josephzhu.spring101beans.MyService@6cd24612#preDestroy:5
me.josephzhu.spring101beans.MyService@6cd24612#destroy:5

從輸出中咱們注意到幾點:

  1. 先輸出的的確是helloService,說明@Resource引入的是咱們Java代碼配置的MyService,helloService因爲在咱們配置的多調用了一次increaseCounter()以及關聯的initMethod,因此counter的值是5
  2. initMethod執行的順序在@PostConstruct註釋的方法和InitializingBean接口實現的方法以後
  3. 雖然咱們的MySerive的兩種Bean的定義都是單例,可是這不表明咱們的Bean就是一套,在這裏咱們經過代碼配置和註解方式在容器內建立了兩套MyService類型的Bean,它們都經歷了本身的初始化過程。經過@Resource和@Autowired引入到了是不一樣的Bean,固然也就是不一樣的對象
    你還能夠試試在使用@Autowired引入MyService的時候直接指定須要的Bean:
@Autowired
@Qualifier("helloService")
private MyService service;

兩個重要的擴展點

咱們來繼續探索Spring容器提供給咱們的兩個有關Bean的重要擴展點。

  • 用於修改Bean定義的BeanFactoryPostProcessor。所謂修改定義就是修改Bean的元數據,元數據有哪些呢?以下圖所示,類型、名字、實例化方式、構造參數、屬性、Autowire模式、懶初始化模式、初始析構方法。實現了這個接口後,咱們就能夠修改這些已經定義的元數據,實現真正的動態配置。這裏須要注意,咱們不該該在這個接口的實現中去實例化Bean,不然這至關於提早進行了實例化會破壞Bean的生命週期。

  • 用於修改Bean實例的BeanPostProcessor。在這個階段其實Bean已經實例化了,咱們能夠進行一些額外的操做對Bean進行修改。以下圖,咱們能夠清晰的看到Bean的生命週期以下(BeanPostProcessor縮寫爲BPP):
  1. Bean定義加載
  2. BeanFactoryPostProcessor來修改Bean定義
  3. Bean逐一實例化
  4. BeanPostProcessor預處理
  5. Bean初始化
  6. BeanPostProcessor後處理


好,咱們如今來實現這兩種類型的處理器,首先是用於修改Bean定義的處理器:

package me.josephzhu.spring101beans;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition("helloService");
        if (beanDefinition != null) {
            beanDefinition.setScope("prototype");
            beanDefinition.getPropertyValues().add("counter", 10);
        }
        System.out.println("MyBeanFactoryPostProcessor");
    }
}

這裏,咱們首先找到了咱們的helloService(Java代碼配置的那個Bean),而後修改了它的屬性和Scope(還記得嗎,在以前的圖中咱們能夠看到,這兩項都是Bean的定義,定義至關於類描述,實例固然就是類實例了)。
而後,咱們再來建立一個修改Bean實例的處理器:

package me.josephzhu.spring101beans;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof MyService) {
            System.out.println(bean + "#postProcessAfterInitialization:" + ((MyService)bean).increaseCounter());
        }
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof MyService) {
            System.out.println(bean + "#postProcessBeforeInitialization:" + ((MyService)bean).increaseCounter());
        }
        return bean;
    }
}

實現比較簡單,在這個處理器的兩個接口咱們都調用了一次增長計數器的操做。咱們運行代碼來看一下這兩個處理器執行的順序是否符合剛纔那個圖的預期:

MyBeanFactoryPostProcessor
me.josephzhu.spring101beans.MyService@41330d4f#constructor:1
me.josephzhu.spring101beans.MyService@41330d4f#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@41330d4f#postConstruct:12
me.josephzhu.spring101beans.MyService@41330d4f#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@41330d4f#init:14
me.josephzhu.spring101beans.MyService@41330d4f#postProcessAfterInitialization:15
me.josephzhu.spring101beans.MyService@6f36c2f0#constructor:1
me.josephzhu.spring101beans.MyService@6f36c2f0#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@6f36c2f0#postConstruct:12
me.josephzhu.spring101beans.MyService@6f36c2f0#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@6f36c2f0#init:14
me.josephzhu.spring101beans.MyService@6f36c2f0#postProcessAfterInitialization:15
me.josephzhu.spring101beans.MyService@3b35a229#constructor:1
me.josephzhu.spring101beans.MyService@3b35a229#postProcessBeforeInitialization:2
me.josephzhu.spring101beans.MyService@3b35a229#postConstruct:3
me.josephzhu.spring101beans.MyService@3b35a229#afterPropertiesSet:4
me.josephzhu.spring101beans.MyService@3b35a229#postProcessAfterInitialization:5
====================
me.josephzhu.spring101beans.MyService@6692b6c6#constructor:1
me.josephzhu.spring101beans.MyService@6692b6c6#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@6692b6c6#postConstruct:12
me.josephzhu.spring101beans.MyService@6692b6c6#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@6692b6c6#init:14
me.josephzhu.spring101beans.MyService@6692b6c6#postProcessAfterInitialization:15
myService:me.josephzhu.spring101beans.MyService@3b35a229
helloService:me.josephzhu.spring101beans.MyService@6692b6c6
====================
me.josephzhu.spring101beans.MyService@41330d4f#hello:15
me.josephzhu.spring101beans.MyService@6f36c2f0#hello:15
me.josephzhu.spring101beans.MyService@3b35a229#preDestroy:5
me.josephzhu.spring101beans.MyService@3b35a229#destroy:5

這個輸出結果有點長,第一行就輸出了MyBeanFactoryPostProcessor這是預料之中,Bean定義的修改確定是最早發生的。咱們看下輸出的規律,一、十一、十二、1三、1四、15出現了三次,之因此從1跳到了11是由於咱們的BeanFactoryPostProcessor修改了其中的counter屬性的值爲10。這說明了,咱們的helloService的初始化進行了三次:

  • 第一套指針地址是5a7fe64f,對應輸出第一個hello(),這是咱們@Resource引入的

  • 第二套指針地址是69ee81fc,對應輸出第二個hello(),這是咱們@Autowird+@Qualifier引入的(剛纔一節最後咱們指定了helloService)

  • 第三套指針地址是29f7cefd,這是咱們getBeansOfType的時候建立的,對應下面Key-Value的輸出:


這裏的輸出說明了幾點:

  • 咱們的BeanFactoryPostProcessor生效了,不但修改了helloService的Scope爲prototype並且修改了它的counter屬性
  • 對於Scope=ptototype的Bean,顯然在每次使用Bean的時候都會新建一個實例
  • BeanPostProcessor兩個方法的順序結合一開始說的Bean事件回調的順序總體以下:
  1. 類本身的構造方法
  2. BeanFactoryPostProcessor接口實現的postProcessBeforeInitialization()方法
  3. @PostConstruct註釋的方法
  4. InitializingBean接口實現的afterPropertiesSet()方法
  5. Init-method定義的方法
  6. BeanFactoryPostProcessor接口實現的postProcessAfterInitialization()方法
  7. @PreDestroy註釋的方法
  8. DisposableBean接口實現的destroy()方法

最後,咱們能夠修改BeanFactoryPostProcessor中的代碼把prototype修改成singleton看看是否咱們的helloService這個Bean恢復爲了單例:

MyBeanFactoryPostProcessor
me.josephzhu.spring101beans.MyService@51891008#constructor:1
me.josephzhu.spring101beans.MyService@51891008#postProcessBeforeInitialization:11
me.josephzhu.spring101beans.MyService@51891008#postConstruct:12
me.josephzhu.spring101beans.MyService@51891008#afterPropertiesSet:13
me.josephzhu.spring101beans.MyService@51891008#init:14
me.josephzhu.spring101beans.MyService@51891008#postProcessAfterInitialization:15
me.josephzhu.spring101beans.MyService@49c90a9c#constructor:1
me.josephzhu.spring101beans.MyService@49c90a9c#postProcessBeforeInitialization:2
me.josephzhu.spring101beans.MyService@49c90a9c#postConstruct:3
me.josephzhu.spring101beans.MyService@49c90a9c#afterPropertiesSet:4
me.josephzhu.spring101beans.MyService@49c90a9c#postProcessAfterInitialization:5
====================
myService:me.josephzhu.spring101beans.MyService@49c90a9c
helloService:me.josephzhu.spring101beans.MyService@51891008
====================
me.josephzhu.spring101beans.MyService@51891008#hello:15
me.josephzhu.spring101beans.MyService@51891008#hello:15
me.josephzhu.spring101beans.MyService@49c90a9c#preDestroy:5
me.josephzhu.spring101beans.MyService@49c90a9c#destroy:5
me.josephzhu.spring101beans.MyService@51891008#preDestroy:15
me.josephzhu.spring101beans.MyService@51891008#destroy:15

本次輸出結果的hello()方法明顯是同一個bean,結果中也沒出現三次一、十一、十二、1三、1四、15。

總結

本文以探索的形式討論了下面的一些知識點:

  1. 容器管理對象的意義是什麼
  2. Bean的生命週期回調事件
  3. Spring提供的Bean的兩個重要擴展點
  4. @Resource和@Autowired的區別
  5. 註解方式和代碼方式配置Bean
  6. @Primary和@Qualifier註解的做用
  7. Bean的不一樣類型的Scope
相關文章
相關標籤/搜索