Spring 01: Spring配置 + IOC控制反轉 + Setter注入

簡介

Spring框架是一個容器,是整合其他框架的框架
他的核心是IOC(控制反轉)和AOP(面向切面編程),由20多個模組構成,在很多領域都提供了優秀的問題解決方案

特點

  • 輕量級:由20多個模組構成,每個jar包都很小,小於1M,核心包也就3M左右

  • 程式碼污染小:Spring框架對程式設計師開發時的條條框框的約束較少,對程式碼的污染小

  • 面向介面編程:面向介面編程,靈活性高,項目的可擴展性和可維護性都較高

  • AOP:面向切面編程,將公共的,通用的,重複的程式碼單獨開發,在需要時反織回去

  • 示例:

image

示例分析:在開發中普遍需要使用到日誌輸出功能,會將日誌輸出功能大量耦合到項目的不同位置,如上圖左側所示。
而日誌輸出功能與項目本身的核心業務邏輯無關,我們只是為了不時的查看程式的運行狀態。則可以將日誌功能單獨提出去開發,在需要的地方將日誌輸出功能(所謂:切面)反織回去即可,如上圖右側所示。

  • 整合其他框架:使其他框架也更加易用

IOC

  • IOC(Inversion of Control):是一個概念,也是一種思想,由Spring容器進行對象的創建和依賴注入,程式設計師在使用時直接取用即可
  • 正轉示例
//程式設計師創建對象
Student stu = new Student();
//程式設計師進行賦值
stu.setName("荷包蛋");		
stu.setAge(20);
  • 反轉示例
<!-- Spring容器負責對象的創建 -->
<bean id="stu" class="com.example.pojo.Student">
    <!-- Spring容器負責為屬性賦值 -->
    <property name="name" value="荷包蛋" />
    <property name="age" value="20" />
</bean>

創建Spring項目

  • 創建maven的java項目:模板類型 + 項目目錄結構 + 修改pom.xml文件添加基本配置的操作不再贅述,可以參考mybatis部落格集里對maven項目的配置
  • 在pom.xml里添加Spring依賴
    <!-- 添加spring依賴-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.22</version>
    </dependency>
  • 在src/main/resources目錄下添加Spring配置文件:applicationContext.xml,正常情況下,文件頭自動補全(在下圖狀態下,無法截圖,手機拍攝,包涵包涵)

image

image

創建實例對象

  • 容器創建實例對象和傳統方式下手動創建實例對象的對比
//實體類
package com.example.pojo;

public class Student {
    private String name;
    private int age;

    public Student() {
        System.out.println("無參方法被調用,Student實例被創建.....");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
<!-- applicationContext.xml文件 -->

<?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="stu" class="com.example.pojo.Student"/>
</beans>
//兩種創建實例對象的對比

package com.example.test;

import com.example.pojo.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestStudent {

    //測試程式設計師手動創建實例對象
    @Test
    public void testStudent(){
        Student stu = new Student();
        System.out.println(stu);
    }

    //測試Spring容器創建實例對象
    @Test
    public void testStudentSpring(){
        //創建Spring容器,並啟動
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
        //從容器中獲取對象
        Student stu = (Student) applicationContext.getBean("stu");
        System.out.println(stu);
    }
}
    //兩次測試的輸出結果均為

    無參方法被調用,Student實例被創建.....
    Student{name='null', age=0}

    Process finished with exit code 0
  • 當Spring容器創建時,對應的應用域中註冊的對象就會被創建
    @Test
    public void testStudentSpring(){
        //創建Spring容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("source01/applicationContext.xml");
    }
    //測試輸出結果

    無參方法被調用,Student實例被創建.....

    Process finished with exit code 0

基於xml的IOC(控制反轉)

setter注入

  • 簡單類型(8 + 1)注入
    • 使用value屬性
    //添加兩個屬性的set方法
    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
    <bean id="stu" class="com.example.pojo.Student">
        <!-- 使用setter注入法為實例對象的屬性賦值 -->
        <property name="name" value="荷包蛋"/>
        <property name="age" value="20"/>
    </bean>
    //測試Spring容器創建實例對象
    @Test
    public void testStudentSpring(){
        //創建Spring容器,並啟動
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("source01/applicationContext.xml");
        //從容器中獲取對象
        Student stu = (Student) applicationContext.getBean("stu");
        System.out.println(stu);
    }
    //測試輸出:
    無參方法被調用,Student實例被創建.....
    Student{name='荷包蛋', age=20}

    Process finished with exit code 0
  • 引用類型注入

    • 使用ref屬性
        //School類
        private String name;
        private String address;
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        public School() {
            System.out.println("School類的構造方法被執行,實體對象被創建.....");
        }
    
        @Override
        public String toString() {
            return "School{" +
                    "name='" + name + '\'' +
                    ", address='" + address + '\'' +
                    '}';
        }
    
    
        //Student類
        private String name;
        private int age;
        private School school;
    
        public Student() {
            System.out.println("Student類的構造方法執行,實體對象被創建....");
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public void setSchool(School school) {
            this.school = school;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", school=" + school +
                    '}';
        }
    
        <!-- applicationContext.xml文件 --> 
    
       <!-- 定義School實體類的實例對象-->
        <bean id="school" class="com.example.pojo02.School">
            <property name="name" value="nefu"/>
            <property name="address" value="哈爾濱"/>
        </bean>
    
        <!-- 定義Student實體類的實例對象 -->
        <bean id="stu" class="com.example.pojo02.Student">
            <property name="name" value="荷包蛋"/>
            <property name="age" value="20"/>
            <!-- 根據bean工廠中註冊過的對象,進行依賴注入 -->
            <property name="school" ref="school"/>
        </bean>
    
        //測試setter注入法的ref屬性
        @Test
        public void testStudent(){
            //創建Spring容器,同時生成bean工廠中註冊的對象
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("source02/applicationContext.xml");
            //獲取對象
            Student stu = (Student) applicationContext.getBean("stu");
            System.out.println(stu);
        }
    
        //測試輸出結果
        School類的構造方法被執行,實體對象被創建.....
        Student類的構造方法執行,實體對象被創建....
        Student{name='荷包蛋', age=20, school=School{name='nefu', address='哈爾濱'}}
    
        Process finished with exit code 0
    
  • 使用setter注入法必須提供無參構造方法,必須提供待注入屬性的setXXX()方法

    • 無參構造方法用於創建實例對象,此時實例對象的屬性是一張白紙,未被賦值和其他數據污染
    • 對應屬性的set方法是為了在底層調用時給目標屬性賦值用,框架再封裝,底層的執行程式碼該有的還要有,框架也不知道你在set方法中都想幹些什麼(畢竟set方法應該被用來賦值,但是不僅僅只能做賦值用)
    • 有了前兩步,你便可以得到需要的對象,而且只有指定的屬性被賦值,其他屬性一塵不染
    • 但是,如果提供了有參構造方法,或者沒有set方法,setter注入將會遇到無限多的麻煩
      • 無set方法,底層無法調用對應set方法完成屬性賦值
      • 有參構造方法的參數會擾亂我們對指定屬性的賦值計劃
      • 例如,有參構造包括name和age兩個屬性,而set又要對name屬性注入值,就會導致name屬性被兩次賦值,在創建對象時就這樣做顯然不合理
    • 所以:我們要做的就是,先創建一個乾淨的對象(無參構造,先不涉及屬性值的問題),再用對應屬性的set方法給屬性賦值,做到精準賦值,按需操作