Spring源碼系列(一)--詳細介紹bean組件


什么是spring-bean?

spring-bean 是 spring 家族中最核心的一個組件,從抽象層面來說,我們可以把它當成:

  1. 通用的對象工廠。這個有點像我們常用的**Factory,通過它,我們可以獲取到所需的對象。
  2. 全局的上下文。我把某個對象丟進這個上下文,然后可以在應用的任何位置獲取到這個對象。

本文要講什么?

針對 spring-bean 組件,我計划分成 2 到 3 篇博客來分析。本文主要講的是:

  1. spring-bean 是什么?用來解決什么問題?
  2. 幾個重要的概念,例如什么是 bean?
  3. 如何使用 spring-bean?

spring-bean用來解決什么問題?

spring-bean 主要是用來解耦實現類。這里我用一個例子來說明,會更好理解一點。

假如我的業務系統使用的是 mysql 數據庫,我通過下面的方式獲取數據庫驅動對象。

public void save(User user) {
    java.sql.Driver driver = new com.mysql.cj.jdbc.Driver();
    Connection connection = driver.connect(url, properties);
    // do something
}

看着是沒什么問題,然而,有一天我需要把數據庫更換為 oracle,於是,我不得不更改代碼。

public void save(User user) {
    java.sql.Driver driver = new oracle.jdbc.driver.OracleDriver();
    Connection connection = driver.connect(url, properties);
    // do something
}

顯然,這是不合理的。那么,有什么辦法能做到不更改代碼就能切換數據庫呢?

JDBC 規范中使用了DriverManager來解耦數據庫實現,可以做到不更改代碼就能切換數據庫。它是通過系統參數 或 SPI 來找到實現類的(SPI 的內容可以參考我的另一篇博客使用SPI解耦你的實現類)。

public void save(User user) {
    java.sql.Driver driver = DriverManager.getDriver(url);
    Connection connection = driver.connect(url, properties);
    // do something
}

大家應該都使用過 JDBC 吧,DriverManager的解耦效果,相信都見識到了。但是呢,我們的業務系統並不會采用這種方式來解耦自己的實現類。為什么呢?不難發現,我需要給UserService配套一個UserServiceManager,給DepartmentService配套一個DepartmentServiceManager······,這是非常繁瑣的。類似的,常用的**Factory也存在同樣的問題。

這時我們就會想,我不要那么多的**Manager或者**Factory行不行?有沒有一個通用的對象工廠?

spring-bean 滿足了這種需求,它就是一個通用的對象工廠,可以用來創建UserService,也可以用來創建DepartmentService。當然,前提是,你需要告訴 beanFactory 如何創建這個對象。

public void save(User user) {
    java.sql.Driver driver = beanFactory.getBean(java.sql.Driver.class);
    Connection connection = driver.connect(url, properties);
    // do something
}

所以,spring-bean 本質上就是用來解耦實現類。除此之外,spring-bean 也是一個全局的上下文,我把某個對象丟進這個上下文,然后可以在應用的任何位置獲取到這個對象。這個比較簡單,就不展開討論了。

幾個重要的概念

在介紹如何使用 spring-bean 之前,先來看看幾個重要的概念。

什么是bean

按照官方的說法, bean 是一個由 Spring IoC 容器實例化、組裝和管理的對象。

我認為,官方的表述是錯誤的。在后面的使用例子中,我們會發現,如果純粹把 spring-bean 當成一個全局的上下文,我們放進這個上下文的對象已經是一個完整的對象實例,並不會由 Spring IoC 實例化、組裝,所以,更准確的表述應該是這樣:

通過 beanFactory 獲取到的對象都屬於 bean。至於什么是 IoC 容器,在 spring-bean 組件中,我認為,beanFactory 就屬於 IoC 容器。

粗俗一點地比喻,人的消化系統就是一個 IoC 容器,拉出來的粑粑就是 bean,而拉出來的粑粑可以是你吃進去的各種食物“組裝”出來的,也可以是你直接吃進去的。

實例化、屬性裝配和初始化

在 spring-bean 組件的設計中,實例化、屬性裝配和初始化,它們完整、有序地描述了創建一個新對象的整個流程,它們是非常重要的理論基礎。具體含義如下:

  1. 實例化:new 一個新對象。
  2. 屬性裝配:給對象的成員屬性賦值。
  3. 初始化:調用對象的初始化方法。

下面通過一段代碼來簡單演示下這個流程。

public class User {
    
    private String name;
    
    private Integer age;
    
    public User() {
        super();
        System.err.println("主流程:User對象實例化中。。-->\n\t||\n\t\\/");
    }
    
    public void init() {
        System.err.println("主流程:User對象初始化中。。-->\n\t||\n\t\\/");
    }
    
    public void setName(String name) {
        System.err.println("主流程:User對象屬性name裝配中。。-->\n\t||\n\t\\/");
        this.name = name;
    }
}

如果我們將這個對象交給 spring-bean 管理,創建 bean 時會在控制台打印以下內容:

spring-bean-test02

如何使用spring-bean

項目環境

JDK:1.8.0_231

maven:3.6.3

IDE:Spring Tool Suites4 for Eclipse 4.12

Spring:5.2.6.RELEASE

依賴引入

除了引入 spring,這里還額外引入了日志和單元測試(可選)。

    <dependencies>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!-- logback -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
            <type>jar</type>
        </dependency>
    </dependencies>

作為全局上下文使用

spring-bean 是一個全局的上下文,我把某個對象丟進這個上下文,然后可以在應用的任何位置獲取到這個對象。注意,這種方式注冊的 bean,實例化、屬性裝配和初始化並不由 spring-bean 來管理。

    public void testContext() {
        // 創建beanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        
        User user = new User("zzs001", 18);
        // 存對象
        beanFactory.registerSingleton("user", user);
        
        // 取對象
        User user2 = (User)beanFactory.getBean("user");
        assertEquals(user, user2);
    }

作為對象工廠使用

如果把 spring-bean 當成對象工廠使用,我們需要告訴它如何創建對象,而 beanDefinition 就包含了如何創建對象的所有信息

    public void testObjectFactory() {
        // 創建beanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 定義一個beanDefinition
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        // 屬性裝配
        rootBeanDefinition.getPropertyValues().add("name", "zzs001");
        rootBeanDefinition.getPropertyValues().add("age", 18);
        // 初始化方法
        rootBeanDefinition.setInitMethodName("init");
        // 單例還是多例,默認單例
        rootBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        // 注冊bean
        beanFactory.registerBeanDefinition("user", rootBeanDefinition);

        // 獲取bean
        User user = (User)beanFactory.getBean("user");
        assertNotNull(user);
    }

多種獲取bean的方式

實際使用中,我們更多的會使用 beanType 而不是 beanName 來獲取 bean,beanFactory 也提供了相應的支持。我們甚至還可以同時使用 beanName 和 beanType,獲取到指定 beanName 的 bean 后會進行類型檢查,如果不通過,將會報錯。

    public void testGetBeanWays() {
        // 創建beanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 注冊bean
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        beanFactory.registerBeanDefinition("user", rootBeanDefinition);

        // 獲取bean--通過beanName
        User user1 = (User)beanFactory.getBean("user");
        assertNotNull(user1);
        
        // 獲取bean--通過beanType
        User user2 = beanFactory.getBean(User.class);
        assertNotNull(user2);
        
        // 獲取bean--通過beanName+beanType的方式
        User user3 = beanFactory.getBean("user", User.class);
        assertNotNull(user3);
    }

使用TypeConverter獲取自定義類型的對象

在上面的例子中,當使用 beanName + beanType 來獲取 bean 時,如果獲取到的 bean 不是指定的類型,這時,並不會立即報錯,beanFactory 會嘗試使用合適TypeConverter來強制轉換(需要我們注冊上去)。

    public void testTypeConverter() {

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 注冊類型轉換器
        beanFactory.setTypeConverter(new TypeConverterSupport() {

            @SuppressWarnings("unchecked")
            @Override
            public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {

                if(UserVO.class == requiredType && value instanceof User) {
                    User user = (User)value;
                    UserVO userVO = new UserVO();
                    userVO.setName(user.getName());
                    userVO.setAge(user.getAge());
                    return (T)userVO;
                }
                return null;
            }
        });

        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        beanFactory.registerBeanDefinition("user", rootBeanDefinition);

        UserVO bean = beanFactory.getBean("user", UserVO.class);
        Assert.assertNotNull(bean);
    }

bean沖突的處理

通過 beanName 獲取 bean 和 通過 beanType 獲取 bean 的區別在於,前者能唯一匹配到所需的 bean,后者就不一定了。如果我注冊了兩個相同 beanType 的 bean(這是允許的),通過 beanType 獲取 bean 時就會報錯。

    public void testPrimary() {
        // 創建beanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 注冊bean
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        beanFactory.registerBeanDefinition("user", rootBeanDefinition);
        beanFactory.registerSingleton("user2", new User("zzs002", 19));
        beanFactory.registerSingleton("user3", new User("zzs003", 18));

        // 獲取bean
        User user = beanFactory.getBean(User.class);
        assertNotNull(user);
    }

運行以上方法,將出現 NoUniqueBeanDefinitionException 的異常。

spring-bean-test01

通過 beanType 獲取 bean 時,當存在多個同類型 bean 的時候,spring-bean 的處理邏輯是這樣的:

  1. 只保留通過 registerSingleton 注冊的以及通過registerBeanDefinition注冊且autowireCandidate = true的;
  2. 檢查是否存在唯一一個isPrimary = true的 bean,存在的話將它返回;
  3. 通過OrderComparator來計算每個 bean 的 priority,取 priority 最小的返回(OrderComparator需要我們自己注冊)。注意,通過 registerSingleton 注冊的和通過 registerBeanDefinition 注冊的,比較的對象是不一樣的,前者比較的對象是 bean 實例,后者比較的對象是 bean 類型,另外,這種方法不能存在相同 priority 的 bean。

所以,為了解決這種沖突,可以采取三種方法:

  1. 僅保留一個 beanDefinition 的 autowireCandidate = true。全部 beanName 都是通過 registerBeanDefinition 注冊的才有效。
  2. 設置其中一個 beanDefinition 的 isPrimary = true。
  3. 為 beanFactory 注冊OrderComparator(這種用的不多)。

代碼如下:

    public void testPrimary() {
        // 創建beanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 為BeanFactory設置比較器,比較少用
        beanFactory.setDependencyComparator(new OrderComparator() {
            @Override
            public Integer getPriority(Object obj) {
                return obj.hashCode();
            }
        });

        // 注冊bean
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        // rootBeanDefinition.setAutowireCandidate(false);
        // rootBeanDefinition.setPrimary(true); // 設置bean優先
        beanFactory.registerBeanDefinition("user", rootBeanDefinition);
        beanFactory.registerSingleton("user2", new User("zzs002", 19));
        beanFactory.registerSingleton("user3", new User("zzs003", 18));

        // 獲取bean
        User user = beanFactory.getBean(User.class);
        assertNotNull(user);
    }

一種特殊的bean--FactoryBean

beanFactory 還支持注冊一種特殊的對象--factoryBean,當我們獲取 bean 時,拿到的不是這個 factoryBean,而是 factoryBean.getObject() 所返回的對象。那我就是想返回 factoryBean 怎么辦?可以通過以下形式的 beanName 獲取:一個或多個& + beanName。

    public void testFactoryBean() throws BeansException, Exception {

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 注冊bean--注冊一個 factoryBean
        UserFactoryBean userFactoryBean = new UserFactoryBean();
        beanFactory.registerSingleton("user", userFactoryBean);

        // 通過beanName獲取
        assertEquals(User.class, beanFactory.getBean("user").getClass());
        
        // 通過beanType獲取
        assertEquals(User.class, beanFactory.getBean(User.class).getClass());

        // 通過&+factoryBeanName的方式
        assertEquals(UserFactoryBean.class, beanFactory.getBean("&user").getClass());
    }

自動裝配

默認情況下,beanFactory 會讀取 beanDefinition 對象中的 propertyValues 來裝配成員屬性,所以,我們想要裝配哪個成員屬性,只要把鍵值對 add 進這個 propertyValues 就行。前提是我們的 bean 必須包含對應成員屬性的 setter 方法

spring-bean 還提供了更有趣的功能--自動裝配。我只需要將 beanDefinition 的 autowireMode 設置為自動裝配,beanFactory 就會幫我把包含 setter 方法的所有成員屬性都賦值(當然,要有值才會賦)。

    public void testAutowire() {
        // 創建beanFactory
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 注冊userService
        AbstractBeanDefinition userServiceBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserService.class).getBeanDefinition();
        beanFactory.registerBeanDefinition("userService", userServiceBeanDefinition);
        
        // 注冊userDao
        AbstractBeanDefinition userDaoBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(UserDao.class).getBeanDefinition();
        beanFactory.registerBeanDefinition("userDao", userDaoBeanDefinition);
        
        // 給userService設置裝配屬性userDao
        // userServiceBeanDefinition.getPropertyValues().add("userDao", userDaoBeanDefinition);
        userServiceBeanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);

        // 獲取bean
        UserService userService = (UserService)beanFactory.getBean("userService");
        assertNotNull(userService.getUserDao());
    }

bean 實例化、屬性裝配和初始化的處理器

前面講到,我們將 bean 的實例化、屬性裝配和初始化都交給了 spring-bean 處理,然而,有時我們需要在這些節點對 bean 進行自定義的處理,這時就需要用到 beanPostProcessor。

這里我簡單演示下如何添加處理器,以及處理器的執行時機,至於處理器的具體實現,我就不多擴展了。

    public void testPostProcessor() {
        
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();

        // 添加實例化處理器
        beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
            // 實例前處理
            // 如果這里我們返回了對象,則beanFactory會將它直接返回,不再進行bean的實例化、屬性裝配和初始化等操作
            public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
                System.out.println("處理器:bean實例化之前的處理。。 --> ");
                return null;
            }

            // 實例后處理
            // 這里判斷是否繼續對bean進行屬性裝配和初始化等操作
            public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
                System.out.println("處理器:bean實例化之后的處理。。 --> ");
                return true;
            }
        });

        // 添加裝配處理器
        beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
            // 屬性裝配前
            // 這里可以在屬性裝配前對參數列表進行調整
            public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
                System.out.println("處理器:屬性裝配前對參數列表進行調整。。--> ");
                return InstantiationAwareBeanPostProcessor.super.postProcessProperties(pvs, bean, beanName);
            }

        });

        // 添加初始化處理器
        beanFactory.addBeanPostProcessor(new BeanPostProcessor() {
            // 初始化前
            // 這里可以在初始化前對bean進行改造
            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                System.out.println("處理器:初始化前,對bean進行改造。。 --> ");
                return bean;
            }

            // 初始化后
            // 這里可以在初始化后對bean進行改造
            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                System.out.println("處理器:初始化后,對bean進行改造。。 --> ");
                return bean;
            }
        });
        
        // 定義一個beanDefinition
        BeanDefinition rootBeanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
        // 屬性裝配
        rootBeanDefinition.getPropertyValues().add("name", "zzs001");
        rootBeanDefinition.getPropertyValues().add("age", 18);
        // 初始化方法
        rootBeanDefinition.setInitMethodName("init");
        // 單例還是多例,默認單例
        rootBeanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
        // 注冊bean
        beanFactory.registerBeanDefinition("user", rootBeanDefinition);
        
        User user = (User)beanFactory.getBean("user");
        assertNotNull(user);
    }

運行以上方法,控制台打印出了整個處理流程。實際開發中,我們可以通過設置處理器來改造生成的 bean 。

spring-bean-test03

以上,基本介紹完 spring-bean 組件的使用。后續發現其他有趣的地方再做補充,也歡迎大家指正不足的地方。

最后,感謝閱讀。

2021-09-26更改

相關源碼請移步: spring-beans

本文為原創文章,轉載請附上原文出處鏈接:https://www.cnblogs.com/ZhangZiSheng001/p/13126053.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM