Spring學習筆記 – 第一章 – IoC(控制反轉)、IoC容器、Bean的實例化與生命周期、DI(依賴注入)

Spring 學習筆記全系列傳送門:

目錄

1、學習概述

  • Spring的優點:
    • 簡化開發
      • IOC
      • AOP
        • 事務處理
    • 框架整合
      • MyBatis
      • MyBatis-plus
      • Struts
      • Struts2
      • Hibernate
      • ……
  • 主要的學習內容
    • IOC
    • 整合Mybatis(IOC的具體應用)
    • AOP
    • 聲明式事務(AOP的具體應用)
  • 學習重心
    • Spring的思想
    • Spring的基礎操作
    • 案例練習

2、Spring相關概念

2.1 Spring概述

官網://spring.io

2.1.1 Spring能做的工作

  • Web
  • 微服務
  • 分佈式
  • ……

2.1.2 重點學習的內容

  • Spring Framework:是Spring中最早最核心的技術,也是所有其他技術的基礎。
  • SpringBoot:簡化開發,而SpringBoot是來幫助Spring在簡化的基礎上能更快速進行開發。
  • SpringCloud:用來做分佈式之微服務架構的相關開發。
  • SpringData、SpringSecurity 等目前也是流行的技術

2.1.3 Spring發展史

隨着時間推移,版本不斷更新維護,目前最新的是Spring5

  • Spring1.0是純配置文件開發
  • Spring2.0為了簡化開發引入了註解開發,此時是配置文件加註解的開發方式
  • Spring3.0已經可以進行純註解開發,使開發效率大幅提升
  • Spring4.0根據JDK的版本升級對個別API進行了調整
  • Spring5.0已經全面支持JDK8,現在Spring最新的是5系列

2.2 Spring系統架構

2.2.1 系統架構及其圖示

  • 說明

    • 核心層
      • Core Container :核心容器,這個模塊是Spring最核心的模塊,其他的都需要依賴該模塊
    • AOP層
      • AOP :面向切面編程,它依賴核心層容器,目的是在不改變原有代碼的前提下對其進行功能增強
      • Aspects :AOP是思想,Aspects是對AOP思想的具體實現
    • 數據層
      • Data Access :數據訪問,Spring全家桶中有對數據訪問的具體實現技術
      • Data Integration :數據集成,Spring支持整合其他的數據層解決方案,比如Mybatis
      • Transactions :事務,Spring中事務管理是Spring AOP的一個具體實現,將在後續內容中詳細說明
    • Web層
      • 這一層的內容將在SpringMVC框架部分進行詳細說明
    • Test層
      • Spring主要整合了Junit來完成單元測試和集成測試
  • 圖示 ( Spring Framework 4.x )

    Spring Framework 4.x 系統架構圖示

2.2.2 課程學習路線

  1. 核心容器

    • 核心概念( IoC / DI )
    • 容器基本操作
  2. AOP

    • 核心概念
    • AOP基礎操作
    • AOP使用開發
  3. 事務

    • 事務實用開發
  4. 整合

    • 整合數據層技術MyBatis
  5. 家族

    • SpringMVC
    • SpringBoot
    • SpringCloud

2.3 Spring核心概念

2.3.1 目前項目中的問題

  • 存在問題:按照原先的JavaWeb開發,耦合度偏高

    如:Dao層更改了內容,將使用新的函數進行數據處理,此時Service層也要修改,並且需要重新編譯

  • 解決方案

    • 使用對象時,避免主動new創建對象,轉為由外部提供對象

2.3.2 IOC、IOC容器、Bean、DI —— 充分解耦

  • IOC(Inversion of Control)控制反轉:使用對象時,由主動new產生對象轉換為由外部提供對象,此過程中對象創建控制權由程序轉移到外部,此思想(對象的創建控制權的轉移)稱為控制反轉。

  • IoC容器:Spring提供了一個容器,稱為IOC容器,用來充當IOC思想中的”外部”

  • Bean:IOC容器負責對象的創建、初始化等一系列工作(其中包含了數據層和業務層的類對象),被創建或被管理的對象在IOC容器中都被成為稱為Bean對象

  • DI(Dependency Injection)依賴注入:IoC容器為了解決Bean對象之間的依賴關係而自動建立bean對象綁定的過程

    如:Service需要依賴Dao,IoC容器會將兩個bean對象進行綁定

3、入門案例

3.1 IOC入門案例

3.1.1 入門案例思路分析

3.1.2 入門案例代碼實現

  1. 創建Maven項目

  2. 添加Spring依賴jar包

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    
  3. 添加需要的類

    • Dao

      • 接口

        package priv.dandelion.dao;
        
        public interface BookDao {
            public void save();
        }
        
      • 實現類

        package priv.dandelion.dao.Impl;
        
        import priv.dandelion.dao.BookDao;
        
        public class BookDaoImpl implements BookDao {
            public void save() {
                System.out.println("book dao save ...");
            }
        }
        
    • Service

      • 接口

        package priv.dandelion.service;
        
        public interface BookService {
            public void save();
        }
        
      • 實現類

        package priv.dandelion.service.Impl;
        
        import priv.dandelion.dao.BookDao;
        import priv.dandelion.dao.Impl.BookDaoImpl;
        import priv.dandelion.service.BookService;
        
        public class BookServiceImpl implements BookService {
            private BookDao bookDao = new BookDaoImpl();
        
            public void save() {
                System.out.println("book service save ...");
                bookDao.save();
            }
        }
        
    • Main

      package priv.dandelion;
      
      import priv.dandelion.service.BookService;
      import priv.dandelion.service.Impl.BookServiceImpl;
      
      public class App {
          public static void main(String[] args) {
              BookService bookService = new BookServiceImpl();
              bookService.save();
              /**
               * 控制台輸出:
               * book service save ...
               * book dao save ...
               */
          }
      }
      
  4. resources目錄下添加Spring配置文件:applicationContext.xml,在配置文件中完成bean的配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="//www.springframework.org/schema/beans"
           xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- 配置Bean-->
        <!--
            id : 給Bean命名
            class : 給Bean定義類型
        -->
        <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl"/>
        <bean id="bookService" class="priv.dandelion.service.Impl.BookServiceImpl"/>
    
    </beans>
    
  5. 獲取IOC容器,從容器中獲取對象進行方法調用

    package priv.dandelion;
    
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import priv.dandelion.service.BookService;
    
    public class App2 {
        public static void main(String[] args) {
            // 獲取IOC容器
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            // 獲取Bean
            BookService bookService = (BookService) context.getBean("bookService");
            bookService.save();
            /**
             * 控制台輸出:
             * book service save ...
             * book dao save ...
             */
        }
    }
    

3.2 DI ( 依賴注入 )入門案例

IoC入門案例中,Service層仍然使用了new的方式創建對象,耦合度依然高

3.2.1 入門案例思路分析

3.2.2 入門案例代碼實現

  • 修改被依賴類的成員屬性中創建對象部分,並為其添加set方法

    package priv.dandelion.service.Impl;
    
    import priv.dandelion.dao.BookDao;
    import priv.dandelion.service.BookService;
    
    public class BookServiceImpl implements BookService {
        // 刪除業務層中使用new的方式創建的dao對象
        private BookDao bookDao;
    
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
        }
        // 提供所要創建成員對象的對應的set方法
        public void setBookDao(BookDao bookDao) {
            this.bookDao = bookDao;
        }
    }
    
  • 修改配置文件,配置其和被依賴類的關係

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="//www.springframework.org/schema/beans"
           xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!-- 配置Bean-->
        <!--
            id : 給Bean命名
            class : 給Bean定義類型
        -->
        <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl"/>
        <bean id="bookService" class="priv.dandelion.service.Impl.BookServiceImpl">
            <!-- 配置Service 與Dao的關係-->
            <!--
                property標籤 : 表示配置當前Bean的屬性
                name屬性 : 表示配置參照哪一個具體的屬性名稱,注意需要實現其set方法
                ref屬性 : 表示參照(引用)哪一個Bean
            -->
            <property name="bookDao" ref="bookDao"/>
        </bean>
    
    </beans>
    

4、IOC相關內容

4.1 Bean基礎配置

4.1.1 bean基礎配置( id 與 class )

類別 描述
名稱 bean
類型 標籤
所屬 beans標籤
功能 定義 Spring 核心容器管理的對象
格式 <beans> <bean/> <bean></bean> <beans>
屬性列表 id:bean 的 id ,使用容器可以通過 id 值獲取對應的 bean ,在一個容器中id唯一
class:bean 的類型,即配置的 bean 的全路徑名 (不能是接口,接口不能實例化)
示例 <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl"/>
<bean id="bookService" class="priv.dandelion.service.Impl.BookServiceImpl"></bean>

4.1.2 bean 的別名 name 屬性

  1. 配置別名

    • 使用 bean 標籤的 name 屬性為 bean 設置別名

    • 一個 bean 可以有多個別名,使用逗號或空格分隔

    • bean 的 ref 屬性可以參照(引用)對應 bean 的 name 屬性值,不過建議還是引用 id,保持一致

      <!--
          id : 給Bean命名
          class : 給Bean定義類型
      -->
      <bean id="bookDao" name="dao" class="priv.dandelion.dao.Impl.BookDaoImpl"/>
      <bean id="bookService" name="service,service2 bookEbi" class="priv.dandelion.service.Impl.BookServiceImpl">
          <!-- 配置Service 與Dao的關係-->
          <!--
              property標籤 : 表示配置當前Bean的屬性
              name屬性 : 表示配置參照哪一個具體的屬性名稱,注意需要實現其set方法
              ref屬性 : 表示參照(引用)哪一個Bean
          -->
          <property name="bookDao" ref="dao"/>
      </bean>
      
  2. 根據名稱從容器中獲取bean對象

    與使用 id 從容器中獲取 bean 對象方法相同

4.1.3 bean 的作用範圍 scope 配置

  • 多次創建 Bean 對象默認為單例,使用 scope 屬性可以配置其不為單例

    • 核心配置文件

      <!-- bookDao 配置了屬性 scope="prototype" -->
      <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl" scope="prototype"/>
      
      <bean id="bookService" name="service,service2 bookEbi" class="priv.dandelion.service.Impl.BookServiceImpl">
          <property name="bookDao" ref="bookDao"/>
      </bean>
      
    • 執行

      ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
      // 單例
      BookService bookService1 = (BookService) context.getBean("service");
      BookService bookService2 = (BookService) context.getBean("service");
      System.out.println(bookService1);   // priv.dandelion.service.Impl.BookServiceImpl@25bbe1b6
      System.out.println(bookService2);   // priv.dandelion.service.Impl.BookServiceImpl@25bbe1b6
      // 非單例
      BookDao bookDao1 = (BookDao) context.getBean("bookDao");
      BookDao bookDao2 = (BookDao) context.getBean("bookDao");
      System.out.println(bookDao1);   // priv.dandelion.dao.Impl.BookDaoImpl@5702b3b1
      System.out.println(bookDao2);   // priv.dandelion.dao.Impl.BookDaoImpl@69ea3742
      
  • 整理

    類別 描述
    名稱 scope
    類型 屬性
    所屬 bean 標籤
    功能 定義 bean 的作用範圍,默認為單例(默認單例,節約資源),可進行配置
    * singletion:單例
    * prototype:非單例
    示例 <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl" scope="prototype"/>
  • 拓展

    • 適合交給容器進行管理的 bean

      • Dao 層對象
      • Service 層對象
      • Controller 層對象
      • Utils 對象
    • 不適合交給容器管理的 bean

      • 封裝實體的域對象,各自帶有不同的數據

4.2 bean實例化

bean 的本質就是對象,創建 bean 使用無參構造方法完成(底層採用暴力反射)

bean的三種實例化方法:

  • 構造方法
  • 靜態工廠
  • 實例工廠

4.2.1 環境準備

4.2.2 構造方法實例化

  1. 準備需要被創建的類

    • Dao

      public class BookDaoImpl implements BookDao {
      
          // 驗證:bean的實例化本質上是調用了無參構造方法,手動實現並在控制台輸出內容
          // 驗證:底層使用暴力反射機制,當構造方法使用private時,也不會影響bean的實例化
          private BookDaoImpl() {
              System.out.println("book dao constructor is running ....");
          }
      
          public void save() {
              System.out.println("book dao save ...");
          }
      
      
      
  2. 將類配置到Spring容器

    <bean id="bookDao" class="priv.dandelion.dao.Impl.BookDaoImpl"/>
    
  3. 編寫運行程序

    public class AppForInstanceBook {
        public static void main(String[] args) {
    
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
    
            bookDao.save();
            
            /**
             * 控制台輸出:
             * book dao constructor is running ....
             * book dao save ...
             *
             * Process finished with exit code 0
             */
    
        }
    }
    

4.2.3 靜態工廠實例化

4.2.3.1 工廠方式創建 bean

使用工廠方式創建對象,在一定程度上進行解耦,但不如將工廠交給Spring來管理(見4.2.3.2)

  • Dao

    public class OrderDaoImpl implements OrderDao {
    
        public void save() {
            System.out.println("order dao save ...");
        }
    }
    
  • Factory

    //靜態工廠創建對象
    public class OrderDaoFactory {
        public static OrderDao getOrderDao(){
            System.out.println("factory setup....");
            return new OrderDaoImpl();
        }
    }
    
  • Main

    public class AppForInstanceOrder {
        public static void main(String[] args) {
            // 通過靜態工廠創建對象
            OrderDao orderDao = OrderDaoFactory.getOrderDao();
            orderDao.save();
            /**
             * 控制台輸出:
             * factory setup....
             * order dao save ...
             */
        }
    }
    
4.2.3.2 靜態工廠實例化

將工廠交給Spring來管理,實例化 bean

  • 核心配置文件

    <!-- property標籤 : 表示配置當前Bean的屬性 -->
    <!-- class屬性 : 所使用的工廠的全類名 -->
    <!-- factory-method屬性 : 工廠中創建bean的方法名 -->
    <bean id="orderDao" class="priv.dandelion.factory.OrderDaoFactory" factory-method="getOrderDao"/>
    
  • Main

    public class AppForInstanceOrder {
        public static void main(String[] args) {
    
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
    
            orderDao.save();
            /**
             * 控制台輸出:
             * factory setup....
             * order dao save ...
             */
        }
    }
    

4.2.4 實例工廠(非靜態)與 FactoryBean

4.2.4.1 環境準備
  • Dao

    public class UserDaoImpl implements UserDao {
    
        public void save() {
            System.out.println("user dao save ...");
        }
    }
    
  • Factory

    //實例工廠創建對象
    public class UserDaoFactory {
        public UserDao getUserDao(){
            return new UserDaoImpl();
        }
    }
    
4.2.4.2 實例工廠實例化
  • 核心配置文件

    <!-- 工廠中獲取Dao的方法是非靜態的,必須拿到工廠對象 -->
    <bean id="userFactory" class="priv.dandelion.factory.UserDaoFactory"/>
    
    <!-- 指定工廠,並使用工廠對應方法獲取所需的bean對象 -->
    <!-- factory-bean:指定工廠bean -->
    <bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"/>
    
  • Main

    public class AppForInstanceUser {
        public static void main(String[] args) {
    
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            UserDao userDao = (UserDao) ctx.getBean("userDao");
            
            userDao.save();
            /**
             * 控制台輸出:
             * user dao save ...
             */ 
        }
    }
    
4.2.4.3 FactoryBean 的使用(改良:實例工廠實例化)

4.2.4.2 實例工廠實例化中存在問題,需要進行改進:

  1. 工廠的bean對象只是為了配合使用,毫無意義
  2. 對於每個需要的bean,其中factory-method屬性值不固定,每次都需要配置

使用FactoryBean對其進行改進

  • FactoryBean 工廠類

    // FactoryBean創建對象,需要實現FactoryBean<T>接口並指定泛型
    public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    
        // 代替原始實例工廠中創建對象的方法
        public UserDao getObject() throws Exception {
            return new UserDaoImpl();
        }
    
        // 指定Object的類型,返回對應的類的位元組碼文件
        public Class<?> getObjectType() {
            return UserDao.class;
        }
        
        // 創建出的 bean 是否為單例,返回 true 為單例,不覆寫該方法時默認為為單例
        public boolean isSingleton() {
            return true;
        }
    }
    
  • 核心配置文件

    <!-- <bean id="userFactory" class="priv.dandelion.factory.UserDaoFactory"/> -->
    <!-- <bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"/> -->
    <bean id="userDao" class="priv.dandelion.factory.UserDaoFactoryBean"/>
    
  • Main

    與 4.2.4.2 一致,無需修改

4.3 bean的生命周期

bean 的生命周期有以下幾個階段:

  • 初始化容器
    1. 創建對象(分配內存)
    2. 執行構造方法
    3. 執行屬性注入(set 操作)
    4. 執行 bean 的初始化方法
  • 使用 bean
    1. 執行業務操作
  • 關閉 / 銷毀容器
    1. 執行 bean 銷毀方法

4.3.1 環境準備

  • Dao

    public class BookDaoImpl implements BookDao {
        public void save() {
            System.out.println("book dao save ...");
        }
    }
    
  • 核心配置文件

    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl"/>
    
  • Main

    public class AppForLifeCycle {
        public static void main( String[] args ) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
            bookDao.save();
            /**
             * book dao save ...
             */
        }
    }
    

4.3.2 生命周期設置

  1. 類中添加初始化和銷毀方法

    public class BookDaoImpl implements BookDao {
        public void save() {
            System.out.println("book dao save ...");
        }
        // 表示bean初始化對應的操作
        public void init(){
            System.out.println("init...");
        }
        // 表示bean銷毀前對應的操作
        public void destroy(){
            System.out.println("destroy...");
        }
    }
    
  2. 在核心配置文件中進行配置

    <!--init-method:設置bean初始化生命周期回調函數-->
    <!--destroy-method:設置bean銷毀生命周期回調函數,僅適用於單例對象-->
    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/><bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
    
  3. 運行程序,控制台輸出僅包含init

    如運行情況中所示,控制台沒有輸出destroy...,說明銷毀方法未被執行,該問題將在 4.3.3 和 4.3.4 得到解決

    /**
     * init...
     * book dao save ...
     */
    

4.3.3 close關閉容器

  • 銷毀方法未執行原因與解決方案分析

    • 原因:程序運行結束時,JVM虛擬機直接被關閉,銷毀方法來不及執行
    • 解決方案:在關閉JVM虛擬機之前手動關閉容器,可以執行銷毀方法
  • 實現

    存在問題:ApplicationContext中沒有實現close()

    解決方案:其實現類ClassPathXmlApplicationContext對其進行了實現

    public class AppForLifeCycle {
        public static void main( String[] args ) {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
            bookDao.save();
            //關閉容器
            ctx.close();
            /**
             * init...
             * book dao save ...
             * destroy...
             */
        }
    }
    

4.3.4 註冊鉤子關閉容器

  • 使用該方式的原因

    • close關閉容器相對而言比較暴力,如果在 close 之後還有未執行的語句,可能會發生問題
    • 註冊鉤子關閉容器語句可以寫在任何位置,且僅在JVM虛擬機關閉前執行銷毀
  • 註冊鉤子關閉容器的使用方法

    • Main中

      //註冊關閉鉤子函數,在虛擬機退出之前回調此函數,關閉容器
      ctx.registerShutdownHook();
      

4.3.5 生命周期管理的接口方式

Spring為生命周期控制提供了接口,在上述代碼的基礎上添加以下代碼進行演示

  • 核心配置文件(不需要寫 init-method 和 destroy-method 屬性)

    <bean id="bookService" class="priv.dandelion.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
    
  • Service

    public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
        private BookDao bookDao;
    
        public void setBookDao(BookDao bookDao) {
            System.out.println("set .....");
            this.bookDao = bookDao;
        }
    
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
        }
    
        public void destroy() throws Exception {
            System.out.println("service destroy");
        }
    
        // 如方法名所述,該方法運行在set方法之後
        public void afterPropertiesSet() throws Exception {
            System.out.println("service init");
        }
    }
    
  • Main

    public class AppForLifeCycle {
        public static void main( String[] args ) {
            ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
            bookDao.save();
            //註冊關閉鉤子函數,在虛擬機退出之前回調此函數,關閉容器
            ctx.registerShutdownHook();
            /**
             * init...
             * set .....
             * service init
             * book dao save ...
             * service destroy
             * destroy...
             */
        }
    }
    

5、DI(依賴注入)相關內容

  • 向一個類中傳遞數據的方式
    • 普通方法(setter)
    • 構造方法(構造器)
  • 容器中的 bean 可能是什麼類型的數據
    • 引用類型
    • 簡單類型(基本數據類型和 String)

5.1 setter注入 (推薦,使用簡單)

5.1.1 注入引用數據類型

  1. 類中給出成員變量並實現其setter

    public class BookServiceImpl implements BookService {
        private BookDao bookDao;
        private UserDao userDao;
        //setter注入需要提供要注入對象的set方法
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
        //setter注入需要提供要注入對象的set方法
        public void setBookDao(BookDao bookDao) {
            this.bookDao = bookDao;
        }
    
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
            userDao.save();
        }
    }
    
  2. 核心配置文件

    <!-- 被注入的需要配置,定義成bean -->
    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl"/>
    <bean id="userDao" class="priv.dandelion.dao.impl.UserDaoImpl"/>
    
    <!-- 注入引用類型 -->
    <bean id="bookService" class="priv.dandelion.service.impl.BookServiceImpl">
        <!--property標籤:設置注入屬性-->
        <!--name屬性:設置注入的屬性名,實際是set方法對應的名稱-->
        <!--ref屬性:設置注入引用類型bean的id或name-->
        <property name="bookDao" ref="bookDao"/>
        <property name="userDao" ref="userDao"/>
    </bean>
    

5.1.2 注入簡單數據類型

  1. 在類中定義成員變量並實現其 setter

    public class BookDaoImpl implements BookDao {
    
        private String databaseName;
        private int connectionNum;
        //setter注入需要提供要注入對象的set方法
        public void setConnectionNum(int connectionNum) {
            this.connectionNum = connectionNum;
        }
        //setter注入需要提供要注入對象的set方法
        public void setDatabaseName(String databaseName) {
            this.databaseName = databaseName;
        }
    
        public void save() {
            System.out.println("book dao save ..."+databaseName+","+connectionNum);
        }
    }
    
  2. 核心配置文件

    <!-- 注入簡單類型 -->
    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl">
        <!--property標籤:設置注入屬性-->
        <!--name屬性:設置注入的屬性名,實際是set方法對應的名稱-->
        <!--value屬性:設置注入簡單類型數據值,Spring內部會自動進行類型轉換-->
        <property name="connectionNum" value="100"/>
        <property name="databaseName" value="mysql"/>
    </bean>
    

5.2 構造器注入

5.2.1 構造器注入引用數據類型

  1. 類中給出成員變量並使用有參構造對需要注入的成員進行初始化操作

    public class BookServiceImpl implements BookService {
        private BookDao bookDao;
        private UserDao userDao;
    
        public BookServiceImpl(BookDao bookDao, UserDao userDao) {
            this.bookDao = bookDao;
            this.userDao = userDao;
        }
    
        public void save() {
            System.out.println("book service save ...");
            bookDao.save();
            userDao.save();
        }
    }
    
  2. 核心配置文件

    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl"/>
    <bean id="userDao" class="priv.dandelion.dao.impl.UserDaoImpl"/>
    
    <bean id="bookService" class="priv.dandelion.service.impl.BookServiceImpl">
        <!-- 注意此處的 ref 屬性值為:構造方法的形參名 -->
        <constructor-arg name="userDao" ref="userDao"/>
        <constructor-arg name="bookDao" ref="bookDao"/>
    </bean>
    

5.2.2 構造器注入簡單數據類型

  1. 類中給出成員變量並使用有參構造對需要注入的成員進行初始化操作

    public class BookDaoImpl implements BookDao {
        private String databaseName;
        private int connectionNum;
    
        public BookDaoImpl(String databaseName, int connectionNum) {
            this.databaseName = databaseName;
            this.connectionNum = connectionNum;
        }
    
        public void save() {
            System.out.println("book dao save ..."+databaseName+","+connectionNum);
        }
    }
    
  2. 核心配置文件

    <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl">
        <!-- 根據構造方法參數名稱注入 -->
        <constructor-arg name="connectionNum" value="10"/>
        <constructor-arg name="databaseName" value="mysql"/>
    </bean>
    

5.2.3 【補充】不同場景構造器注入簡單數據類型的其他寫法 —— 用於解耦

<!-- 1. 標準書寫 -->
<bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl">
    <constructor-arg name="connectionNum" value="10"/>
    <constructor-arg name="databaseName" value="mysql"/>
</bean>

<!-- 2. 解決形參名稱的問題,與形參名不耦合 -->
<bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl">
    <constructor-arg type="int" value="10"/>
    <constructor-arg type="java.lang.String" value="mysql"/>
</bean>

<!-- 3. 解決參數類型重複問題,使用位置解決參數匹配 -->
<bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl">
    <constructor-arg index="0" value="mysql"/>
    <constructor-arg index="1" value="100"/>
</bean>

5.3 自動配置

  • IoC 容器根據 bean 所依賴的資源在容器中自動查找並注入到 bean 中的過程稱為自動裝配

  • 自動裝配的方式:

    • 按類型(常用)

    • 按名稱(耦合度高)

    • 按構造器(不推薦)

    • 不啟用自動裝配

  • 依賴自動裝配特徵

    • 自動裝配用於引用類型依賴注入,不能對簡單類型進行操作
    • 使用按類型裝配時(byType)必須保障容器中相同類型(class)的 bean 唯一,推薦使用
    • 使用按名稱裝配時(byName)必須保障容器中具有指定名稱(id)的 bean,因變量名與配置耦合,不推薦使用
    • 自動裝配優先級低於setter注入與構造器注入,同時出現時自動裝配配置失效
  • 按類型(推薦)

    • 注意事項

      1. 被注入的 bean 所在的類必須實現 setter
      2. 被注入的 bean(按照全類名,即 class 屬性)有且僅有一個
      3. 按類型注入時,被注入的 bean 可以沒有 id
    • 配置

      <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl"/>
      <bean id="bookService" class="priv.dandelion.service.impl.BookServiceImpl" autowire="byType"/>
      
  • 按名稱

    • 注意事項

      1. 被注入的 bean 所在的類必須實現 setter
      2. 被注入的 bean 的 id 屬性必須和類中的成員名稱,準確說是 setter 名稱保持一致,否則會給一個空對象,導致空指針異常
      3. 變量名與配置耦合
    • 配置代碼

      <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl"/>
      <bean id="bookService" class="priv.dandelion.service.impl.BookServiceImpl" autowire="byName"/>
      

5.4 集合注入

  • List 和 Array 可以相互通用

  • 如果集合的元素是引用類型則不使用 <value></value>

    <ref bean="bean的id"/>
    
  • Dao

    public class BookDaoImpl implements BookDao {
    
        private int[] array;
    
        private List<String> list;
    
        private Set<String> set;
    
        private Map<String,String> map;
    
        private Properties properties;
    
    
        public void setArray(int[] array) {
            this.array = array;
        }
    
        public void setList(List<String> list) {
            this.list = list;
        }
    
        public void setSet(Set<String> set) {
            this.set = set;
        }
    
        public void setMap(Map<String, String> map) {
            this.map = map;
        }
    
        public void setProperties(Properties properties) {
            this.properties = properties;
        }
    
        
        public void save() {
            System.out.println("book priv.dandelion.dao save ...");
    
            System.out.println("遍曆數組:" + Arrays.toString(array));
    
            System.out.println("遍歷List" + list);
    
            System.out.println("遍歷Set" + set);
    
            System.out.println("遍歷Map" + map);
    
            System.out.println("遍歷Properties" + properties);
        }
    }
    
  • 核心配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="//www.springframework.org/schema/beans"
           xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="//www.springframework.org/schema/beans //www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="bookDao" class="priv.dandelion.dao.impl.BookDaoImpl">
            <!--數組注入-->
            <property name="array">
                <array>
                    <value>100</value>
                    <value>200</value>
                    <value>300</value>
                </array>
            </property>
            <!--list集合注入-->
            <property name="list">
                <list>
                    <value>dande</value>
                    <value>dandel</value>
                    <value>dandelio</value>
                    <value>dandelion</value>
                </list>
            </property>
            <!--set集合注入-->
            <property name="set">
                <set>
                    <value>dande</value>
                    <value>dandel</value>
                    <value>dandelio</value>
                    <value>dandelio</value>
                </set>
            </property>
            <!--map集合注入-->
            <property name="map">
                <map>
                    <entry key="country" value="china"/>
                    <entry key="province" value="henan"/>
                    <entry key="city" value="kaifeng"/>
                </map>
            </property>
            <!--Properties注入-->
            <property name="properties">
                <props>
                    <prop key="country">china</prop>
                    <prop key="province">henan</prop>
                    <prop key="city">kaifeng</prop>
                </props>
            </property>
        </bean>
    </beans>
    
  • Main

    public class AppForDICollection {
        public static void main( String[] args ) {
            ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
            BookDao bookDao = (BookDao) ctx.getBean("bookDao");
    
            bookDao.save();
        }
        /**
         * book priv.dandelion.dao save ...
         * 遍曆數組:[100, 200, 300]
         * 遍歷List[dande, dandel, dandelio, dandelion]
         * 遍歷Set[dande, dandel, dandelio]
         * 遍歷Map{country=china, province=henan, city=kaifeng}
         * 遍歷Properties{province=henan, city=kaifeng, country=china}
         */
    }
    
Tags: