Spring (一) 概念,基於XML的IOC,依賴注入

spring 是什麼

Spring 是分層的 Java SE/EE 應用 full-stack 輕量級開源框架,以 IoC(Inverse Of Control: 反轉控制)和 AOP(Aspect Oriented Programming:面向切面編程)爲內核,提供了展現層 Spring MVC 和持久層 Spring JDBC 以及業務層事務管理等衆多的企業級應用技術,還能整合開源世界衆多 著名的第三方框架和類庫,逐漸成爲使用最多的 Java EE 企業應用開源框架。

Spring 的發展歷程

1997 年 IBM 提出了 EJB 的思想
1998 年,SUN 制定開發標準規範 EJB1.0
1999 年,EJB1.1 發佈
2001 年,EJB2.0 發佈
2003 年,EJB2.1 發佈
2006 年,EJB3.0 發佈
Rod Johnson(spring 之父)
Expert One-to-One J2EE Design and Development(2002) 闡述了 J2EE 使用 EJB 開發設計的優點及解決方案
Expert One-to-One J2EE Development without EJB(2004) 闡述了 J2EE 開發不使用 EJB 的解決方式(Spring 雛形)
 
2017 年 9 月份發佈了 spring 的最新版本 spring 5.0 通用版(GA)

spring 的優勢

方便解耦,簡化開發

通過 Spring 提供的 IoC 容器,可以將對象間的依賴關係交由 Spring 進行控制,避免硬編碼所造 成的過度程序耦合。用戶也不必再爲單例模式類、屬性文件解析等這些很底層的需求編寫代碼,可 以更專注於上層的應用。

AOP 編程的支持

通過 Spring AOP 功能,方便進行面向切面的編程,許多不容易用傳統 OOP 實現的功能可以通過 AOP 輕鬆應付。

聲明式事務的支持

可以將我們從單調煩悶的事務管理代碼中解脫出來,通過聲明式方式靈活的進行事務的管理, 提高開發效率和質量。

方便程序的測試

可以用非容器依賴的編程方式進行幾乎所有的測試工作,測試不再是昂貴的操作,而是隨手可做的事情。

方便集成各種優秀框架

Spring 可以降低各種框架的使用難度,提供了對各種優秀框架(StrutsHibernateHessianQuartz 等)的直接支持。

降低 JavaEE API 的使用難度

Spring JavaEE API(如 JDBCJavaMail、遠程調用等)進行了薄薄的封裝層,使這些 API 的 使用難度大爲降低。

Java 源碼是經典學習範例

Spring 的源代碼設計精妙、結構清晰、匠心獨用,處處體現着大師對 Java 設計模式靈活運用以 及對 Java 技術的高深造詣。它的源代碼無意是 Java 技術的最佳實踐的範例。

 

spring 的體系結構

 IoC 的概念和作用

 

什麼是程序的耦合

耦合性(Coupling),也叫耦合度,是對模塊間關聯程度的度量。耦合的強弱取決於模塊間接口的複雜性、調 用模塊的方式以及通過界面傳送數據的多少。模塊間的耦合度是指模塊之間的依賴關係,包括控制關係、調用關係、數據傳遞關係。模塊間聯繫越多,其耦合性越強,同時表明其獨立性越差( 降低耦合性,可以提高其獨立 性)。耦合性存在於各個領域,而非軟件設計中獨有的,但是我們只討論軟件工程中的耦合。 在軟件工程中,耦合指的就是就是對象之間的依賴性。對象之間的耦合越高,維護成本越高。因此對象的設計 應使類和構件之間的耦合最小。軟件設計中通常用耦合度和內聚度作爲衡量模塊獨立程度的標準。劃分模塊的一個 準則就是高內聚低耦合。

它有如下分類:

1)內容耦合。當一個模塊直接修改或操作另一個模塊的數據時,或一個模塊不通過正常入口而轉入另 一個模塊時,這樣的耦合被稱爲內容耦合。內容耦合是最高程度的耦合,應該避免使用之。
2)公共耦合。兩個或兩個以上的模塊共同引用一個全局數據項,這種耦合被稱爲公共耦合。在具有大 量公共耦合的結構中,確定究竟是哪個模塊給全局變量賦了一個特定的值是十分困難的。
3) 外部耦合 。一組模塊都訪問同一全局簡單變量而不是同一全局數據結構,而且不是通過參數表傳 遞該全局變量的信息,則稱之爲外部耦合。
4) 控制耦合 。一個模塊通過接口向另一個模塊傳遞一個控制信號,接受信號的模塊根據信號值而進 行適當的動作,這種耦合被稱爲控制耦合。
5)標記耦合 。若一個模塊 A 通過接口向兩個模塊 B C 傳遞一個公共參數,那麼稱模塊 B C 之間 存在一個標記耦合。
6) 數據耦合。模塊之間通過參數來傳遞數據,那麼被稱爲數據耦合。數據耦合是最低的一種耦合形式,系統中一般都存在這種類型的耦合,因爲爲了完成一些有意義的功能,往往需要將某些模塊的輸出數據作爲另 一些模塊的輸入數據。
7) 非直接耦合 。兩個模塊之間沒有直接關係,它們之間的聯繫完全是通過主模塊的控制和調用來實現的。
總結:
耦合是影響軟件複雜程度和設計質量的一個重要因素,在設計上我們應採用以下原則:如果模塊間必須存在耦合,就儘量使用數據耦合,少用控制耦合,限制公共耦合的範圍,儘量避免使用內容耦合。

內聚與耦合

內聚標誌一個模塊內各個元素彼此結合的緊密程度,它是信息隱蔽和局部化概念的自然擴展。內聚是從 功能角度來度量模塊內的聯繫,一個好的內聚模塊應當恰好做一件事。它描述的是模塊內的功能聯繫。耦合是軟件 結構中各模塊之間相互連接的一種度量,耦合強弱取決於模塊間接口的複雜程度、進入或訪問一個模塊的點以及通 過接口的數據。 程序講究的是低耦合,高內聚。就是同一個模塊內的各個元素之間要高度緊密,但是各個模塊之 間的相互依存度卻要不那麼緊密。 內聚和耦合是密切相關的,同其他模塊存在高耦合的模塊意味着低內聚,而高內聚的模塊意味着該模塊同其他 模塊之間是低耦合。在進行軟件設計時,應力爭做到高內聚,低耦合。
我們在開發中,有些依賴關係是必須的,有些依賴關係可以通過優化代碼來解除的。
請看下面的示例代碼:
/**
* 賬戶的業務層實現類
* @author 
* @Company
* @Version 1.0
*/
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao = new AccountDaoImpl();
}
上面的代碼表示:
業務層調用持久層,並且此時業務層在依賴持久層的接口和實現類。如果此時沒有持久層實現類,編譯
將不能通過。這種編譯期依賴關係,應該在我們開發中杜絕。我們需要優化代碼解決。
再比如:
早期我們的 JDBC 操作,註冊驅動時,我們爲什麼不使用 DriverManager 的 register 方法,而是採
用 Class.forName 的方式?
public class JdbcDemo1 {
    public static void main(String[] args) throws Exception {
        //1.註冊驅動
        //DriverManager.registerDriver(new com.mysql.jdbc.Driver());
        Class.forName("com.mysql.jdbc.Driver");
        //2.獲取連接
        //3.獲取預處理 sql 語句對象
        //4.獲取結果集
        //5.遍歷結果集
    } 
}
原因就是:
我們的類依賴了數據庫的具體驅動類(MySQL),如果這時候更換了數據庫品牌(比如 Oracle),需要
修改源碼來重新數據庫驅動。這顯然不是我們想要的。

解決程序耦合的思路

當是我們講解 jdbc 時,是通過反射來註冊驅動的,代碼如下:
Class.forName("com.mysql.jdbc.Driver");//此處只是一個字符串
此時的好處是,我們的類中不再依賴具體的驅動類,此時就算刪除 mysql 的驅動 jar 包,依然可以編譯(運
行就不要想了,沒有驅動不可能運行成功的)。
同時,也產生了一個新的問題,mysql 驅動的全限定類名字符串是在 java 類中寫死的,一旦要改還是要修改
源碼。
解決這個問題也很簡單,使用配置文件配置。

工廠模式解耦

在實際開發中我們可以把三層的對象都使用配置文件配置起來,當啓動服務器應用加載的時候,讓一個類中的
方法通過讀取配置文件,把這些對象創建出來並存起來。在接下來的使用的時候,直接拿過來用就好了。
那麼,這個讀取配置文件,創建和獲取三層對象的類就是工廠。

控制反轉-Inversion Of Control

解耦的思路有 2 個問題:
1、存哪去?
分析:由於我們是很多對象,肯定要找個集合來存。這時候有 Map 和 List 供選擇。
到底選 Map 還是 List 就看我們有沒有查找需求。有查找需求,選 Map。
所以我們的答案就是在應用加載時,創建一個 Map,用於存放三層對象。
我們把這個 map 稱之爲容器。 
2、什麼是工廠?
工廠就是負責給我們從容器中獲取指定對象的類。這時候我們獲取對象的方式發生了改變。
原來:我們在獲取對象時,都是採用 new 的方式。是主動的
現在:我們獲取對象時,同時跟工廠要,有工廠爲我們查找或者創建對象。是被動的。

這種被動接收的方式獲取對象的思想就是控制反轉,它是 spring 框架的核心之一。
 

使用 spring IOC 解決程序耦合

案例的前期準備

本章我們使用的案例是,賬戶的業務層和持久層的依賴關係解決。在開始 spring 的配置之前,我們要先準備 一下環境。由於我們是使用 spring 解決依賴關係,並不是真正的要做增刪改查操作,所以此時我們沒必要寫實體類。並且我們在此處使用的是 java 工程,不是 java web 工程。

準備 spring 的開發包

官網:http://spring.io/
下載地址:
http://repo.springsource.org/libs-release-local/org/springframework/spring
解壓:(Spring 目錄結構:)
* docs :API 和開發規範.
* libs :jar 包和源碼.
* schema :約束.

特別說明:
spring5 版本是用 jdk8 編寫的,所以要求我們的 jdk 版本是 8 及以上。 同時 tomcat 的版本要求 8.5 及以上。

創建業務層接口和實現類

/**
* 賬戶的業務層接口
*
*/
public interface IAccountService {
/**
* 保存賬戶(此處只是模擬,並不是真的要保存)
*/
    void saveAccount();
}
/**
* 賬戶的業務層實現類
*
*/
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao = new AccountDaoImpl();//此處的依賴關係有待解決
    @Override
    public void saveAccount() {
         accountDao.saveAccount();
    } 
}

創建持久層接口和實現類

/**
* 賬戶的持久層接口
* 
*/
public interface IAccountDao {
/**
* 保存賬戶
*/
    void saveAccount();
}
/**
* 賬戶的持久層實現類
*
*/
public class AccountDaoImpl implements IAccountDao {
    @Override
    public void saveAccount() {
        System.out.println("保存了賬戶");
    } 
}

基於 XML 的配置

第一步:拷貝必備的 jar 包到工程的 lib 目錄中

第二步:在類的根路徑下創建一個任意名稱的 xml 文件

給配置文件導入約束:
/spring-framework-5.0.2.RELEASE/docs/spring-framework-reference/html5/core.html



<?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">
</beans>

第三步:讓 spring 管理資源,在配置文件中配置 service dao

<!-- bean 標籤:用於配置讓 spring 創建對象,並且存入 ioc 容器之中
 id 屬性:對象的唯一標識。
 class 屬性:指定要創建對象的全限定類名
-->
<!-- 配置 service --> 
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!-- 配置 dao --> 
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>

測試配置是否成功

/**
* 模擬一個表現層
* 
*/
public class Client {
/**
 * 使用 main 方法獲取容器測試執行
*/
    public static void main(String[] args) {
        //1.使用 ApplicationContext 接口,就是在獲取 spring 容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根據 bean 的 id 獲取對象
        IAccountService aService = (IAccountService) ac.getBean("accountService");
        System.out.println(aService);
        IAccountDao aDao = (IAccountDao) ac.getBean("accountDao");
        System.out.println(aDao);
    } 
}

Spring 基於 XML IOC 細節

spring 中工廠的類結構圖

BeanFactory ApplicationContext 的區別

BeanFactory 纔是 Spring 容器中的頂層接口。
ApplicationContext 是它的子接口。

BeanFactory 和 ApplicationContext 的區別:
創建對象的時間點不一樣。
ApplicationContext:只要一讀取配置文件,默認情況下就會創建對象。
BeanFactory:什麼使用什麼時候創建對象。
ApplicationContext 接口的實現類
ClassPathXmlApplicationContext:
    它是從類的根路徑下加載配置文件 推薦使用這種
FileSystemXmlApplicationContext:
    它是從磁盤路徑上加載配置文件,配置文件可以在磁盤的任意位置。
AnnotationConfigApplicationContext:
    當我們使用註解配置容器對象時,需要使用此類來創建 spring 容器。它用來讀取註解。

IOC bean 標籤和管理對象細節

bean 標籤

作用:
用於配置對象讓 spring 來創建的。
默認情況下它調用的是類中的無參構造函數。如果沒有無參構造函數則不能創建成功。
屬性:
id:給對象在容器中提供一個唯一標識。用於獲取對象。
class:指定類的全限定類名。用於反射創建對象。默認情況下調用無參構造函數。
scope:指定對象的作用範圍。
* singleton :默認值,單例的.
* prototype :多例的.
* request :WEB 項目中,Spring 創建一個 Bean 的對象,將對象存入到 request 域中.
* session :WEB 項目中,Spring 創建一個 Bean 的對象,將對象存入到 session 域中.
* global session :WEB 項目中,應用在 Portlet 環境.如果沒有 Portlet 環境那麼globalSession 相當於         
       session.
init-method:指定類中的初始化方法名稱。
destroy-method:指定類中銷燬方法名稱。

bean 的作用範圍和生命週期
單例對象:scope="singleton"
    一個應用只有一個對象的實例。它的作用範圍就是整個引用。
    生命週期:
        對象出生:當應用加載,創建容器時,對象就被創建了。
        對象活着:只要容器在,對象一直活着。
        對象死亡:當應用卸載,銷燬容器時,對象就被銷燬了。
多例對象:scope="prototype"
    每次訪問對象時,都會重新創建對象實例。
    生命週期:
        對象出生:當使用對象時,創建新的對象實例。
        對象活着:只要對象在使用中,就一直活着。
        對象死亡:當對象長時間不用時,被 java 的垃圾回收器回收了。
實例化 Bean 的三種方式
第一種方式:使用默認無參構造函數
<!--在默認情況下:
它會根據默認無參構造函數來創建類對象。如果 bean 中沒有默認無參構造函數,將會創建失敗。
--> 
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>
第二種方式:spring 管理靜態工廠-使用靜態工廠的方法創建對象
/**
* 模擬一個靜態工廠,創建業務層實現類
*/
public class StaticFactory {
    public static IAccountService createAccountService(){
        return new AccountServiceImpl();
    } 
}
<!-- 此種方式是:
使用 StaticFactory 類中的靜態方法 createAccountService 創建對象,並存入 spring 容器
id 屬性:指定 bean 的 id,用於從容器中獲取
class 屬性:指定靜態工廠的全限定類名
factory-method 屬性:指定生產對象的靜態方法
--> 
<bean 
id="accountService"
class="com.itheima.factory.StaticFactory"
factory-method="createAccountService">
</bean>
第三種方式:spring 管理實例工廠 再 用實例工廠的方法創建對象
/**
* 模擬一個實例工廠,創建業務層實現類
* 此工廠創建對象,必須現有工廠實例對象,再調用方法
*/
public class InstanceFactory {
    public IAccountService createAccountService(){
        return new AccountServiceImpl();
    } 
}
<!-- 此種方式是:
先把工廠的創建交給 spring 來管理。
然後在使用工廠的 bean 來調用裏面的方法
factory-bean 屬性:用於指定實例工廠 bean 的 id。
factory-method 屬性:用於指定實例工廠中創建對象的方法。
--> 
<bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean> 
<bean id="accountService"
 factory-bean="instancFactory"
 factory-method="createAccountService"></bean>

spring 的依賴注入

依賴注入的概念

依賴注入:Dependency Injection。它是 spring 框架核心 ioc 的具體實現。
我們的程序在編寫時,通過控制反轉,把對象的創建交給了 spring,但是代碼中不可能出現沒有依賴的情況。
ioc 解耦只是降低他們的依賴關係,但不會消除。例如:我們的業務層仍會調用持久層的方法。
那這種業務層和持久層的依賴關係,在使用 spring 之後,就讓 spring 來維護了。
簡單的說,就是坐等框架把持久層對象傳入業務層,而不用我們自己去獲取。

構造函數注入

顧名思義,就是使用類中的構造函數,給成員變量賦值。注意,賦值的操作不是我們自己做的,而是通過配置
的方式,讓 spring 框架來爲我們注入。具體代碼如下:
/**
*/
public class AccountServiceImpl implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;
    public AccountServiceImpl(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday; 
    }
    @Override
    public void saveAccount() {
        System.out.println(name+","+age+","+birthday);
    } 
}
<!-- 使用構造函數的方式,給 service 中的屬性傳值
要求:
類中需要提供一個對應參數列表的構造函數。
涉及的標籤:
constructor-arg
屬性:
index:指定參數在構造函數參數列表的索引位置
type:指定參數在構造函數中的數據類型
name:指定參數在構造函數中的名稱 用這個找給誰賦值
=======上面三個都是找給誰賦值,下面兩個指的是賦什麼值的==============
value:它能賦的值是基本數據類型和 String 類型
ref:它能賦的值是其他 bean 類型,也就是說,必須得是在配置文件中配置過的 bean--> 
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">         
    <constructor-arg name="name" value="張三"></constructor-arg> 
    <constructor-arg name="age" value="18"></constructor-arg> 
    <constructor-arg name="birthday" ref="now"></constructor-arg>
</bean> 
<bean id="now" class="java.util.Date"></bean>

優勢:獲取bean對象的時候,必須注入數據,否則無法創建對象

弊端:改變了bean對象的實例化方式,使我們在用不到 數據的時候,也必須注入數據

set 方法注入

顧名思義,就是在類中提供需要注入成員的 set 方法。具體代碼如下:
/** */
public class AccountServiceImpl implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;
    public void setName(String name) {
        this.name = name; 
    }
    public void setAge(Integer age) {
        this.age = age; 
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday; 
    }
    @Override
    public void saveAccount() {
        System.out.println(name+","+age+","+birthday);
    } 
}
<!-- 通過配置文件給 bean 中的屬性傳值:使用 set 方法的方式
涉及的標籤:
property
屬性:
name:找的是類中 set 方法後面的部分,用於調用set方法
ref:給屬性賦值是其他 bean 類型的
value:給屬性賦值是基本數據類型和 string 類型的
實際開發中,此種方式用的較多。
--> 
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> 
    <property name="name" value="test"></property> 
    <property name="age" value="21"></property> 
    <property name="birthday" ref="now"></property>
</bean> 
<bean id="now" class="java.util.Date"></bean>

創建對象沒有明確限制,但是如果某個成員必須有值,則獲取對象可能沒有調用set方法,也就沒有值。 

使用 p 名稱空間注入數據(本質還是調用 set 方法)

此種方式是通過在 xml 中導入 p 名稱空間,使用 p:propertyName 來注入數據,它的本質仍然是調用類中的
set 方法實現注入功能。
Java 類代碼:
/**
* 使用 p 名稱空間注入,本質還是調用類中的 set 方法
*/
public class AccountServiceImpl4 implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    @Override
    public void saveAccount() {
        System.out.println(name+","+age+","+birthday);
    } 
}
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       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">
 
    <bean id="accountService" 
         class="com.itheima.service.impl.AccountServiceImpl4"
         p:name="test" p:age="21" p:birthday-ref="now"/>
</beans>

注入集合屬性

顧名思義,就是給類中的集合成員傳值,它用的也是set方法注入的方式,只不過變量的數據類型都是集合。
我們這裏介紹注入數組,List,Set,Map,Properties。具體代碼如下:
/***/
public class AccountServiceImpl implements IAccountService {
    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProps;
    public void setMyStrs(String[] myStrs) {
        this.myStrs = myStrs; 
    }
    public void setMyList(List<String> myList) {
        this.myList = myList; 
    }
    public void setMySet(Set<String> mySet) {
        this.mySet = mySet; 
    }
    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap; 
    }
    public void setMyProps(Properties myProps) {
        this.myProps = myProps; 
    }
    @Override
    public void saveAccount() {
        System.out.println(Arrays.toString(myStrs));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(myProps);
    } 
}
<!-- 注入集合數據
List 結構的:
    array,list,set
Map 結構的
    map,entry,props,prop
--> 
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
     //在注入集合數據時,只要結構相同,標籤可以互換 
        <!-- 給數組注入數據 --> 
    <property name="myStrs"> 
        <set>
            <value>AAA</value> 
            <value>BBB</value> 
            <value>CCC</value>
        </set>
    </property>
    <!-- 注入 list 集合數據 --> 
    <property name="myList"> 
        <array> 
            <value>AAA</value> 
            <value>BBB</value> 
            <value>CCC</value>
        </array>
    </property>
    <!-- 注入 set 集合數據 --> 
    <property name="mySet"> 
        <list>
            <value>AAA</value> 
            <value>BBB</value> 
            <value>CCC</value>
        </list>
    </property>
    <!-- 注入 Map 數據 --> 
        <property name="myMap"> 
            <props> 
                <prop key="testA">aaa</prop> 
                <prop key="testB">bbb</prop>
            </props>
        </property>
    <!-- 注入 properties 數據 -->
    <property name="myProps"> 
        <map>
            <entry key="testA" value="aaa"></entry> 
            <entry key="testB"> 
                <value>bbb</value>
            </entry>
        </map>
    </property>
</bean>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章