1. Spring IoC 容器 | Spring 學(xué)習(xí)筆記

在上次的筆記中,為大家簡單的介紹了 Spring,點擊查看

Spring Framework 中,最重要的莫過于 控制反轉(zhuǎn)容器 了,英文名稱 Inversion of Control (IoC) container。相信每個程序員都聽過這個詞,那么這玩意兒到底是啥?

控制反轉(zhuǎn) Inversion of Control

在學(xué)習(xí) IoC 容器 之前,我們有必要先了解一下,什么是 控制反轉(zhuǎn)。

控制反轉(zhuǎn) (Inversion of Control,縮寫為 IoC),是面向?qū)ο缶幊?/strong>中的一種設(shè)計原則,可以減少代碼之間的耦合度,耦合度這東西是程序員最大的敵人,如果程序的耦合度太高,那么維護(hù)起來(Bug修復(fù),新功能開發(fā))的成本就會越來越高,這就好比一體機和組裝電腦,如果主板壞了,那么組裝電腦只用把主機箱抱去維修就行了,但是一體機就要把所有東西拿去維修。

控制反轉(zhuǎn)最常見的實現(xiàn)叫做依賴注入(Dependency Injection,簡稱 DI),大家不要把控制反轉(zhuǎn)依賴注入搞混了,前者是一種設(shè)計原則,而后者是一種具體的實現(xiàn)。比如控制反轉(zhuǎn)的實現(xiàn)還有依賴查找。

那么控制反轉(zhuǎn)到底反轉(zhuǎn)了什么呢?

我們知道在面向?qū)ο缶幊讨校钪匾漠?dāng)然是一個一個的對象,程序的運行靠的就是這些對象之間的彼此合作來完成具體的業(yè)務(wù),那么這個對象是如何創(chuàng)建和獲取的呢?在沒有控制反轉(zhuǎn)之前,這個對象就是我們自己創(chuàng)建并獲取的,比如:

SmsService service = new AliyunSmsService();
service.send(user, "測試短信");

這里我有一個短信發(fā)送服務(wù),它負(fù)責(zé)向一個用戶發(fā)送一條短信,那么我們?nèi)绻@取我們想要的短信服務(wù)呢?很直接,我們在這里直接創(chuàng)建一個短信服務(wù),是基于阿里云的短信發(fā)送服務(wù),這樣,我們就自己手動創(chuàng)建并且獲取到了我們的業(yè)務(wù)對象。
上面的代碼看似沒有問題,但是這樣的代碼就有很高的耦合性了,比如有一天我們需要將短信服務(wù)從阿里云換到騰訊云,那么我們就只能回來修改代碼了:

SmsService service = new QcloudService();
service.send(user, "測試短信");

這樣是會提高很多維護(hù)的成本,如果,我們能把代碼寫成下面的樣子就好了

SmsService service = 給我一個短信服務(wù)();
service.send(user, "測試短信");

這個業(yè)務(wù)的對象的獲取不是我自己來做,而且別人來做的,那這樣的話,我不是就可以隨意的切換我想要的短信服務(wù)了嗎。
控制反轉(zhuǎn) 就是幫我們干這個事情的,它反轉(zhuǎn)的也正是把我們主動控制對象的創(chuàng)建和獲取反轉(zhuǎn)為別人來控制對象的創(chuàng)建和獲取。

上面說的別人也就是我們所說的控制反轉(zhuǎn)容器(IoC Container)。

控制反轉(zhuǎn)的實現(xiàn)方式有兩種:

依賴注入依賴查找,依賴注入是被動地接受對象,依賴查找是主動的查詢依賴的對象。

依賴注入的實現(xiàn)方式有:

  • 基于接口:對象實現(xiàn)了特定的接口來讓容器注入所依賴的對象
  • 基于set方法:實現(xiàn)特定字段的 public set 方法,讓容器傳入所依賴類型的對象
  • 基于構(gòu)造函數(shù):實現(xiàn)特定參數(shù)的構(gòu)造函數(shù),在創(chuàng)建對象時傳入所依賴的對象
  • 基于注解:基于特定的注解,告訴容器需要注入什么樣的依賴對象

Spring IoC 容器

上面我講到了什么是控制反轉(zhuǎn),我們可以看到,控制反轉(zhuǎn)以后,最核心的一定就是上面所謂的容器了,創(chuàng)建對象,傳入依賴這些工作都是容器來做的,那么接下我們就介紹在 Spring Framework 中的 IoC 容器

Spring 框架中,IoC 容器 可以通過對象 構(gòu)造函數(shù)參數(shù),工廠方法參數(shù),屬性set方法 來注入依賴。

容器會在創(chuàng)建 Bean 的時候進(jìn)行依賴注入, Bean 就是容器管理的對象的統(tǒng)稱。

org.springframework.beansorg.springframework.contextSpring Framework IoC 容器的基礎(chǔ)。接口 BeanFactory 代表了 IoC 容器,可以管理任何類型的對象,我們看看該接口提供的方法有哪些:

BeanFactory

但是使用的更廣泛的是該接口的子類 ApplicationContext,該接口提供了以下額外的功能:

  • Spring AOP 支持,點此查看Spring AOP教程
  • 國際化(Internationalization),也叫i18n
  • 事件發(fā)布
  • 應(yīng)用層的上下文,比如 web 應(yīng)用的 WebApplicationContext

簡單來說 BeanFactory 提供了容器相關(guān)的配置框架,而 ApplicationContext 增加更多的企業(yè)級功能。

Spring 中,由 IoC 容器管理,用來組成應(yīng)用骨架的這些對象,叫做 Bean。一個 Bean 就是一個對象,這個對象通過容器來控制它的創(chuàng)建和組裝(注入依賴)。BeanBeanBean之間的依賴關(guān)系,通過配置來體現(xiàn),這些配置是通過 Java 注解或 Java 代碼來實現(xiàn)的。(在以前的應(yīng)用中,大量使用的 XML 的配置方法,這種方式已經(jīng)使用的越來越少了,在這里就不做介紹了)

容器概覽

接口 org.springframework.context.ApplicationContext 代表了 Spring IoC 容器,該容器負(fù)責(zé)初始化、配置和組裝各種 Bean。

下圖展現(xiàn)了 IoC 容器是如何工作的:

The Spring IoC Container

應(yīng)用通過各種配置組成(Java 代碼或Java 注解),然后在 ApplicationContext 被創(chuàng)建和初始化之后,就得到了一個已經(jīng)配置好可以使用的系統(tǒng)。

配置元數(shù)據(jù) (Configuration Metadata)

通過上面的圖片,我們知道 Spring IoC 容器 通過 配置元數(shù)據(jù) 來進(jìn)行配置。通過配置元數(shù)據(jù)來告訴容器應(yīng)該如何初始化、配置和組裝應(yīng)用中的各種對象。

我們通過一個例子來演示如何使用 注解 或 代碼 來進(jìn)行配置,例子使用了 Spring Boot 來簡化配置。

<?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>aaric</groupId>
    <artifactId>spring-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

</project>
  • 將上面的XML內(nèi)容保存為 pom.xml 并使用 IntelliJ 以項目的方式打開
  • 創(chuàng)建啟動類 Application
package aaric;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ApplicationContext applicationContext = SpringApplication.run(Application.class);
        SmsService smsService = applicationContext.getBean(SmsService.class);
        smsService.send("Aaric", "hello world");
    }
}
  • 可以看到在啟動類創(chuàng)建了 IoC 容器
  • 在IoC容器中根據(jù)類型取得了一個SmsService
  • 進(jìn)行短信的發(fā)送

我們再看看接口SmsService

package aaric;

public interface SmsService {
    void send(String user, String content);
}

該接口有兩個實現(xiàn),一個是阿里云的短信發(fā)送,一個是騰訊云的短信發(fā)送

package aaric.impl;

import aaric.SmsService;

public class AliyunSmsService implements SmsService {
    @Override
    public void send(String user, String content) {
        System.out.println("Aliyun Send sms to user " + user + " with content " + content);
    }
}
package aaric.impl;

import aaric.SmsService;

public class QcloudSmsService implements SmsService {
    @Override
    public void send(String user, String content) {
        System.out.println("Qcloud Send sms to user " + user + " with content " + content);
    }
}
  • 我們知道,要在容器中使用 Bean 必須先將 Bean 放在容器中,那么如何放在容器中呢?
  • 接下來我們將通過 Java 代碼的方式來進(jìn)行 Bean的配置
  • 創(chuàng)建如下的配置類
package aaric;

import aaric.impl.AliyunSmsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApplicationConfiguration {

    @Bean
    public SmsService getService() {
        return new AliyunSmsService();
    }
}

該類有幾個特點

  • @Configuration 注解表明該類是一個配置類,同時也是一個 Bean,配置類一般就是我們用來定義 Bean 的地方

  • @Bean 注解表明該方法返回的返回值會作為一個 Bean 放在容器中,這里我們將阿里云的實現(xiàn)放入了容器中

  • 運行程序以后得到如下結(jié)果


  • 可以看到我們從容器中獲取的 Bean 確實為阿里云的短信服務(wù)

  • 如果現(xiàn)在需要將阿里云的短信服務(wù)改為騰訊云的短信服務(wù),我們只需要修改創(chuàng)建 Bean 的配置就可以了

package aaric;

import aaric.impl.QcloudSmsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ApplicationConfiguration {

    @Bean
    public SmsService getService() {
        return new QcloudSmsService();
    }
}
  • 運行程序后得到如下結(jié)果


  • 可以看到,我們在沒有修改代碼的情況下,就切換了短信服務(wù)的實現(xiàn),這就是控制反轉(zhuǎn)的力量

  • 除了通過 Java 代碼,即在 @Configuration 類里面通過 @Bean 來定義 Bean, 還可以使用注解的方式來定義 Bean,下面我們用同樣的例子來試試通過注解的方式來定義 Bean

  • 刪掉ApplicationConfiguration

  • 在我們要用的實現(xiàn)類上使用 @Service 注解,比如阿里云服務(wù)

package aaric.impl;

import aaric.SmsService;
import org.springframework.stereotype.Service;

@Service
public class AliyunSmsService implements SmsService {
    @Override
    public void send(String user, String content) {
        System.out.println("Aliyun Send sms to user " + user + " with content " + content);
    }
}
  • 運行結(jié)果如下


  • 我們發(fā)現(xiàn) Bean 同樣放入到了容器中

  • 如果要切換服務(wù)的實現(xiàn),只需要將 @Service 注解從 阿里云服務(wù)中刪掉,然后寫在騰訊云服務(wù)的實現(xiàn)類上就可以了

我們可以看到通過這兩種方式都可以達(dá)到目的,那么應(yīng)該在什么情況下使用什么方式呢,推薦的有,如果是想要創(chuàng)建第三方的業(yè)務(wù)對象,放入容器中,那么只能使用代碼的方式,即通過 @Bean 注解來創(chuàng)建,如果是自己寫的業(yè)務(wù)對象,那么推薦使用 注解的方式來定義 Bean, 包括 @Service 注解在內(nèi),Spring Framework 一共提供了四種注解來定義 Bean

  • @Component:一般情況下使用該注解
  • @Service:作為服務(wù)的 Bean 一般使用它
  • @Repository: 作為數(shù)據(jù)持久化的 Bean 一般使用它
  • @Controller: 在 MVC 中的 C 使用它

在上面的例子中,我們對容器有了一個直觀的感受,Spring Boot也放我們完成了容器的啟動、初始化等相關(guān)工作,這也是推薦的方式,這樣不需要我們自己寫代碼來完成這些工作了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容