1. 程式人生 > >Spring框架學習筆記(1)——控制反轉IOC與依賴注入DI

Spring框架學習筆記(1)——控制反轉IOC與依賴注入DI

Spring框架的主要作用,就是提供了一個容器,使用該容器就可以建立並管理物件。比如說Dao類等,又或者是具有多依賴關係的類(Student類中包含有Teacher類的成員變數)

Spring有兩個核心概念,一個是控制反轉(IOC,全稱為Inverse of Control),另一個則是面向切面程式設計(AOP,全稱為 Aspect Oriented Program)

Spring 框架是 Java 應用最廣的框架,它的成功來源於理念,而不是技術本身,它的理念包括 IoC (Inversion of Control,控制反轉) 和 AOP(Aspect Oriented Programming,面向切面程式設計)

Spring 的根本使命:簡化 Java 開發

Spring框架可適用各種Java程式

架構組成:

概念介紹

控制反轉,從另外一個角度來說,和可以叫做依賴注入(DI,全稱為Dependency Injection),之後會說明,這裡就大概說個概念。

下面我們通過兩個角度來了解控制反轉的思想,可能理解上有所出入,僅供參考

程式碼角度解釋

情況1:按照我們普通程式設計,如果我們想要得到一個物件,就得根據這個類的構造方法,傳入合適的引數(資源),我們才能得到這個物件。

但是,有些情況下,這樣的操作會使得程式碼過於冗雜及繁瑣(每次需要物件都要使用一次new關鍵字),而且也不便於資源的管理。

情況2:針對此情況,我們可以考慮建立一個工廠類,來進行優化,之後我們可以呼叫工廠類的getXX方法,把引數(資源)傳入方法,獲得需要的物件。

情況3:上面的情況比我們普通程式設計要好一些,但是,對資源的管理還是有些不方便,所以,這個時候就有了Spring,幫助我們進行資源的管理,Spring也可以看做是一個大工廠,包含了對資源的管理。

控制反轉的意思就是說,我們原本是要自己通過new關鍵字建立的物件(我們控制物件的建立)變為了Spring自動建立物件(Spring控制物件的建立)

現實角度解釋:

比如說我們想吃飯,然後自己利用資源(買回來的菜,米)來做飯,吃完飯後還要剩飯剩菜進行處理(放進冰箱或者倒掉),這就是對資源管理,這就是相當於情況1。

我們自己去買菜,買米,交給媽媽或飯店,讓媽媽或者飯店為我們做飯,媽媽和飯店就是類似工廠類的角色,但是剩飯剩菜我們是不能管理的(假設媽媽是不讓我們插手處理剩飯剩菜,飯店肯定也是不讓你插手的),這就是情況2

我們不準備買菜和買米,直接去飯店或者酒店吃飯,吃完飯也不管剩下的菜和飯是怎麼處理(由飯店或者酒店處理),這裡的飯店和酒店就是相當於Spring的角色,飯店和酒店會對剩飯剩菜處理(資源自動管理),這就是相當於情況3

入門

環境配置

Spring框架jar包比較多,我們可以根據需求來進行選擇,下面列出常用的jar包及說明

檔名 說明
spring-aop.jar 使用Spring的AOP特性所需要的庫
spring-beans.jar 包含訪問配置檔案、建立和管理bean以及進行IoC/DI操作相關的所有類
spring-context.jar 為Spring核心提供了大量擴充套件
spring-core.jar Spring的框架的核心庫,Spring的各個元件都是要使用到這個包裡的類
spring-expression.jar Spring表示式語言需要的庫
spring-test.jar spring內建的一個單元測試

我使用的Maven配置,我這裡spring依賴的jar包都是使用最新的,所有的版本直接輸入RELEASE就可以了

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>RELEASE</version>
        </dependency>
    </dependencies>

需求

假設現在需要一個Student類,Student中有學號和姓名,同時,也包括一個Teacher類,我們通過Spring容器來生成物件

實體類

Student.java

public class Student{
    private String sname;
    private String sno;
    private Teacher teacher;
    //省略get/set方法
}

Teacher.java

public class Teacher{
    private String tname;
    private String tno;
    //省略get/set方法
}

spring配置檔案

由於使用的是maven,所以,這些xml配置檔案放在resources資料夾中,maven之後會自動地把這些配置檔案放在合適的位置

spring配置檔案的名字可以任意取

spring-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--這裡相當於建立了一個物件,並通過set方法給該物件設定了各屬性的數值 -->   
    <bean class="com.wan.Teacher" id="teacher1">
        <property name="tname" value="張三"/>
        <property name="tno" value="t001"/>
    </bean>
    
    <bean class="com.wan.Student" id="student">
        <!--ref屬性後面的數值是上面Teacher物件的id屬性 -->
        <property name="teacher" ref="teacher1"/>
        <property name="sname" value="李四"/>
        <property name="sno" value="s001"/>
    </bean>
</beans>

獲得物件

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//這裡的引數student就是上面的配置檔案Student物件的id,需要強轉
Student s = (Student) context.getBean("student");

分析與補充

簡單地分析一下上面的例子,spring容器會自動呼叫實體類的構造方法,生成一個student物件,然後通過set方法,把xml定義的屬性值設定到物件中去。

之後,通過spring容器來獲得一個student物件

配置檔案補充知識

bean標籤

屬性/子標籤 說明
id 唯一識別符號, 用來代表唯一的bean
class 物件的型別(包名+類名)
property bean標籤的子標籤,物件的一個屬性

property標籤

屬性/子標籤 說明
name 屬性名(與類中的屬性名相同),實際會通過反射呼叫setXx方法進行屬性的設定
value 屬性值,用於給簡單型別賦值,簡單型別(int,double,string)
ref 屬性值,用於給自定義型別賦值,比如說上面我們Student中包含有一個Teacher類的屬性,引用的是另外一個bean的id

PS:如果類中有一個屬性的型別是某個介面,也是使用ref屬性,引用實現了此介面的類的bean的id

依賴注入DI

介紹

依賴注入和控制反轉都是同一個概念,只是從不同的角度看

我們從Student類來看,Student類中有幾個屬性,我們是通過spring的配置檔案,把屬性值(資源)注入到Student物件中,這就叫依賴注入

依賴注入的三種方式

1.settter設值注入

這種方法本質上是通過反射來呼叫物件的setXx方法來進行設值,Spring首先通過實體類的無參構造方法來new了一個物件,然後再通過物件的set方法來進行設值。

所以,使用此方法的前提是保證實體類中存在有無參的構造方法和對應屬性的set方法(可以不同遵循Java Bean規範)

<property name="屬性名" value="屬性值"></property>

<property name="屬性名" ref="引用物件的id"></property>

2. 構造器注入

使用這種方法要按照構造方法的引數順序

<bean id="teacher" class="com.wan.Teacher">
    <constructor-arg value="t001"></constructor-arg>
    <!-- <constructor-arg value="t001"></constructor-arg> -->
</bean>

如果不想按照順序,可以使用index屬性

<bean id="teacher" class="com.wan.Teacher">
    <!-- 指定第一個引數的數值 -->
    <constructor-arg index="1" value="t001"></constructor-arg>
</bean>

如果有兩個構造方法,都是隻有兩個引數,但是,引數的型別不同,可以使用type屬性

<bean id="teacher" class="com.wan.Teacher">
    <constructor-arg index="1" value="t001" type="int"></constructor-arg>
    <constructor-arg index="1" value="t001" type="java.lang.String"></constructor-arg>
</bean>

使用自定義資料,也是和之前使用ref屬性

<bean id="teacher" class="com.wan.Teacher">
    <constructor-arg index="1" ref="teacher1"></constructor-arg>
</bean>

3. p名稱空間注入

使用這種方法可以更加的方便,可以不變使用property標籤,而是使用p:屬性名或者p:屬性名-ref

使用之前,得在spring的配置檔案新增

xmlns:p="http://www.springframework.org/schema/p"
<bean id="teacher" class="com.wan.Student" p:sno="001" p:teacher-ref="teacher1">
</bean>

注入各種資料型別的屬性

之前使用的是value屬性,還可以使用value標籤,使用type定義型別

<property name="屬性名">
    <value type="java.util.String"></value>
</property>

xml檔案中,<&都是特殊符號,得通過特別的方式輸入

有兩種方法,一個是使用xml預定義的實體引用,二是使用<![CDATA[ ]]>

value標籤,可以使用上面說的的兩種方法,而value屬性,只能使用xml預定義的實體引用

&amp; -> &

&lt; -> <

<null/> 設定為null

<!-- value標籤 -->
<property name="屬性名">
    <value type="java.util.String"><![CDATA[張&三]]></value>
    <value type="java.util.String">張&amp;三</value>
</property>

<!--value屬性  -->
<property name="屬性名" value="張&amp;三" />

集合型別

集合可以使用list、map、set標籤

型別 標籤
List或陣列 外層使用list標籤,內層用value或ref
Set 外層使用set標籤,內層用value或ref
Map 外層使用map,中間層使用entry,最裡層使用key和value標籤
Properties資原始檔 外層使用props,子標籤為prop
<properties name="list">
    <list>
        <value>數值</value>
        <ref bean="引用bean的id"><ref>
    </list>
</properties>

<properties name="map">
    <entry>
        <key>
            <value>數值</value>
            或
            <ref bean="引用bean的id"><ref>
        </key>
        <value></value>
        <ref bean="引用bean的id"><ref>
    </entry>
</properties>

<!-- 相當於在properties資原始檔有個 option=2 的配置 -->
<props>
    <prop key="option">2</prop>
</props>

PS:記得value和ref兩個標籤用來賦值就可以了,value給基本型別賦值,ref給自定義型別賦值

例子:

MyArray.java

package com.wan;

import java.util.List;

/**
 * @author StarsOne
 * @date Create in  2019/9/24 0024 15:54
 * @description
 */
public class MyArray {
    List<String> lines;
    List<Teacher> teachers;

    public List<String> getLines() {
        return lines;
    }

    public void setLines(List<String> lines) {
        this.lines = lines;
    }

    public List<Teacher> getTeachers() {
        return teachers;
    }

    public void setTeachers(List<Teacher> teachers) {
        this.teachers = teachers;
    }
    
}

spring的xml配置檔案:

<bean class="com.wan.Teacher" id="teacher1">
    <property name="tno" value="001"/>
    <property name="tname" value="張三"/>
</bean>
<bean class="com.wan.Teacher" id="teacher2">
    <property name="tno" value="001"/>
    <property name="tname" value="張三"/>
</bean>

<bean id="array1" class="com.wan.MyArray">
    <property name="lines">
        <list>
            <value>第一行</value>
            <value>第二行</value>
        </list>
    </property>
    <property name="teachers">
        <list>
            <ref bean="teacher1"/>
            <ref bean="teacher2"/>
        </list>
    </property>
</bean>

測試:

lassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
MyArray myarray = (MyArray) context.getBean("array1");
List<String> lines = myarray.getLines();
List<Teacher> teachers = myarray.getTeachers();
for (String line : lines) {
    System.out.println(line);
}
for (Teacher teacher : teachers) {
    System.out.println(teacher.toString());
}

自動裝配

和Mybatis一樣,spring也有約定優於配置,只要符合約定,spring就會自動幫我們完成的上面的注入屬性值操作。

我們可以通過屬性名、屬性型別或者構造器實現自動裝配

自動裝配只適用於物件型別(引用型別,ref)

byName>byType>constructor

我們之前的Student類中包含有一個Teacher類屬性

<!-- 注意這個id與Student中的Teacher屬性名一致 -->
<bean id="teacher" class="com.wan.Teacher">
...
</bean>

<bean id="student" class="com.wan.Student" autowise="byName">
</bean>

一般大專案不推薦適用自動裝配,會影響程式的可讀性

參考連結:Spring 自動裝配

bean的作用域

作用域 描述
singleton 在spring IoC容器僅存在一個Bean例項,Bean以單例方式存在,預設值
prototype 每次從容器中呼叫Bean時,都返回一個新的例項,即每次呼叫getBean()時,相當於執行newXxxBean()
request 每次HTTP請求都會建立一個新的Bean,該作用域僅適用於WebApplicationContext環境
session 同一個HTTP Session共享一個Bean,不同Session使用不同的Bean,僅適用於WebApplicationContext環境
global-session 一般用於Portlet應用環境,該運用域僅適用於WebApplicationContext環境

參考連結:Spring Bean 作用域

註解方式

使用@Controller,@Service、@Repository、@Component等註解標識類(Bean),之後配置xml的context:component-scan標籤自動掃描

註解 範圍
@Component 通用
@Controller 用於標註Controller層的類(某個具體的Servlet)
@Repository 用於標註Dao層的類
@Service 用於標註Services層的類

一般推薦使用細化後的註解,也就是後面三種,使用的方法都是一樣的,參考下面的一個簡單例子


@Component("student1")
public class Student{
    @Value("001")
    private String sno;
}

<!-- 相當於下面的配置 -->
<bean id="student1" class="com.wan.Student">
    <property name="sno" value="001"/>
</bean>

使用的時候要在spring的那個配置檔案新增下面的程式碼

<beans xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 自動掃描某個包 -->
    <context:component-scan base-package="com.wan">
    </context:component-scan>
</beans>

注意,配置檔案需要新增屬性xmlns:context="http://www.springframework.org/schema/context

xsi:schemaLocation屬性裡面要有個http://www.springframework.org/schema/context/spring-context.xsd

不然就會報錯“萬用字元的匹配很全面, 但無法找到元素 'context:component-scan' 的宣告。”

使用的話和之前一樣

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
Student student = (Student) context.getBean("student1");