一篇文章學會Spring4.0



一篇文章學會Spring4.0                                          轉自 http://blog.csdn.net/johnstrive/article/details/50577477


spring概述

Spring 是一個開源框架. 
Spring 爲簡化企業級應用開發而生. 使用 Spring 可以使簡單的 JavaBean 實現以前只有 EJB 才能實現的功能. 
Spring 是一個 IOC(DI) 和 AOP 容器框架. 
具體描述 Spring: 
輕量級:Spring 是非侵入性的 - 基於 Spring 開發的應用中的對象可以不依賴於 Spring 的 API 
依賴注入(DI — dependency injection、IOC) 
面向切面編程(AOP — aspect oriented programming) 
容器: Spring 是一個容器, 因爲它包含並且管理應用對象的生命週期 
框架: Spring 實現了使用簡單的組件配置組合成一個複雜的應用. 在 Spring 中可以使用 XML 和 Java 註解組合這些對象 
一站式:在 IOC 和 AOP 的基礎上可以整合各種企業應用的開源框架和優秀的第三方類庫 (實際上 Spring 自身也提供了展現層的 SpringMVC 和 持久層的 Spring JDBC)

spring模塊

這裏寫圖片描述

搭建 Spring 開發環境

Spring 的配置文件: 
一個典型的 Spring 項目需要創建一個或多個 Bean 配置文件, 這些配置文件用於在 Spring IOC 容器裏配置 Bean. Bean 的配置文件可以放在 classpath 下, 也可以放在其它目錄下. 
MAVEN配置增加(因爲要設計到數據庫操作,所以一塊兒放進來)

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.7.4</version>
    </dependency>

    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.35</version>
    </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

IOC 和 DI

IOC(Inversion of Control):其思想是反轉資源獲取的方向. 傳統的資源查找方式要求組件向容器發起請求查找資源. 作爲迴應, 容器適時的返回資源. 而應用了 IOC 之後, 則是容器主動地將資源推送給它所管理的組件, 組件所要做的僅是選擇一種合適的方式來接受資源. 這種行爲也被稱爲查找的被動形式 
DI(Dependency Injection) — IOC 的另一種表述方式:即組件以一些預先定義好的方式(例如: setter 方法)接受來自如容器的資源注入. 相對於 IOC 而言,這種表述更直接

這裏寫圖片描述

IOC 前生 — 分離接口與實現

需求: 生成 HTML 或 PDF 格式的不同類型的報表 
這裏寫圖片描述

IOC 前生 — 採用工廠設計模式

這裏寫圖片描述

IOC — 採用反轉控制

這裏寫圖片描述

Spring 容器

在 Spring IOC 容器讀取 Bean 配置創建 Bean 實例之前, 必須對它進行實例化. 只有在容器實例化後, 纔可以從 IOC 容器裏獲取 Bean 實例並使用. 
Spring 提供了兩種類型的 IOC 容器實現. 
BeanFactory: IOC 容器的基本實現. 
ApplicationContext: 提供了更多的高級特性. 是 BeanFactory 的子接口. 
BeanFactory 是 Spring 框架的基礎設施,面向 Spring 本身;ApplicationContext 面向使用 Spring 框架的開發者,幾乎所有的應用場合都直接使用 ApplicationContext 而非底層的 BeanFactory 
無論使用何種方式, 配置文件時相同的.

ApplicationContext

ApplicationContext 的主要實現類: 
ClassPathXmlApplicationContext:從 類路徑下加載配置文件 
FileSystemXmlApplicationContext: 從文件系統中加載配置文件 
ConfigurableApplicationContext 擴展於 ApplicationContext,新增加兩個主要方法:refresh() 和 close(), 讓 ApplicationContext 具有啓動、刷新和關閉上下文的能力 
ApplicationContext 在初始化上下文時就實例化所有單例的 Bean。 
WebApplicationContext 是專門爲 WEB 應用而準備的,它允許從相對於 WEB 根目錄的路徑中完成初始化工作

從 IOC 容器中獲取 Bean

調用 ApplicationContext 的 getBean() 方法 
這裏寫圖片描述

依賴注入的方式

Spring 支持 3 種依賴注入的方式 
1. 屬性注入 
2. 構造器注入 
3. 工廠方法注入(很少使用,不推薦)

屬性注入

屬性注入即通過 setter 方法注入Bean 的屬性值或依賴的對象 
屬性注入使用<property>元素, 使用 name 屬性指定 Bean 的屬性名稱,value 屬性或<value>子節點指定屬性值 
屬性注入是實際應用中最常用的注入方式 
這裏寫圖片描述

構造方法注入

通過構造方法注入Bean 的屬性值或依賴的對象,它保證了 Bean 實例在實例化後就可以使用。 
構造器注入在 <constructor-arg> 元素裏聲明屬性, <constructor-arg>中沒有 name 屬性 
按索引匹配入參: 
這裏寫圖片描述 
按類型匹配入參: 
這裏寫圖片描述

注入細節

字面值

字面值:可用字符串表示的值,可以通過<value>元素標籤或 value 屬性進行注入。 
基本數據類型及其封裝類、String 等類型都可以採取字面值注入的方式 
若字面值中包含特殊字符,可以使用

引用其它 Bean

組成應用程序的 Bean 經常需要相互協作以完成應用程序的功能. 要使 Bean 能夠相互訪問, 就必須在 Bean 配置文件中指定對 Bean 的引用 
在 Bean 的配置文件中, 可以通過<ref>元素或 ref 屬性爲 Bean 的屬性或構造器參數指定對 Bean 的引用. 
也可以在屬性或構造器裏包含 Bean 的聲明, 這樣的 Bean 稱爲內部 Bean 
這裏寫圖片描述

內部 Bean

當 Bean 實例僅僅給一個特定的屬性使用時, 可以將其聲明爲內部 Bean. 內部 Bean 聲明直接包含在<property>或 <constructor-arg>元素裏, 不需要設置任何 id 或 name 屬性 
內部 Bean 不能使用在任何其他地方

注入參數詳解:null 值和級聯屬性

可以使用專用的 <null/>元素標籤爲 Bean 的字符串或其它對象類型的屬性注入 null 值 
和 Struts、Hiberante 等框架一樣,Spring 支持級聯屬性的配置。

集合屬性

在 Spring中可以通過一組內置的 xml 標籤(例如:<list>, <set> 或 <map>) 來配置集合屬性.

java.util.List

配置 java.util.List 類型的屬性, 需要指定<list>標籤, 在標籤裏包含一些元素. 這些標籤可以通過<value> 指定簡單的常量值, 通過 <ref>指定對其他 Bean 的引用. 通過<bean> 指定內置 Bean 定義. 通過<null/>指定空元素. 甚至可以內嵌其他集合. 
數組的定義和 List 一樣, 都使用<list>

java.util.Set

配置 java.util.Set 需要使用<set> 標籤, 定義元素的方法與 List 一樣.

Java.util.Map

Java.util.Map 通過 <map> 標籤定義, <map>標籤裏可以使用多個 <entry> 作爲子標籤. 每個條目包含一個鍵和一個值. 
必須在<key> 標籤裏定義鍵 
因爲鍵和值的類型沒有限制, 所以可以自由地爲它們指定<value>, <ref>, <bean> 或 <null>元素. 
可以將 Map 的鍵和值作爲 <entry>的屬性定義: 簡單常量使用 key 和 value 來定義; Bean 引用通過 key-ref 和 value-ref 屬性定義 
使用<props>定義 java.util.Properties, 該標籤使用多個<prop>作爲子標籤. 每個<prop>標籤必須定義 key 屬性.

使用 utility scheme 定義集合

使用基本的集合標籤定義集合時, 不能將集合作爲獨立的 Bean 定義, 導致其他 Bean 無法引用該集合, 所以無法在不同 Bean 之間共享集合. 
可以使用 util schema 裏的集合標籤定義獨立的集合 Bean. 需要注意的是, 必須在 根元素裏添加 util schema 定義

使用 p 命名空間

爲了簡化 XML 文件的配置,越來越多的 XML 文件採用屬性而非子元素配置信息。 
Spring 從 2.5 版本開始引入了一個新的 p 命名空間,可以通過 <bean>元素屬性的方式配置 Bean 的屬性。 
使用 p 命名空間後,基於 XML 的配置方式將進一步簡化

XML 配置裏的 Bean 自動裝配

Spring IOC 容器可以自動裝配 Bean. 需要做的僅僅是在 的 autowire 屬性裏指定自動裝配的模式 
byType(根據類型自動裝配): 若 IOC 容器中有多個與目標 Bean 類型一致的 Bean. 在這種情況下, Spring 將無法判定哪個 Bean 最合適該屬性, 所以不能執行自動裝配. 
byName(根據名稱自動裝配): 必須將目標 Bean 的名稱和屬性名設置的完全相同. 
constructor(通過構造器自動裝配): 當 Bean 中存在多個構造器時, 此種自動裝配方式將會很複雜. 不推薦使用

自動裝配的缺點

在 Bean 配置文件裏設置 autowire 屬性進行自動裝配將會裝配 Bean 的所有屬性. 然而, 若只希望裝配個別屬性時, autowire 屬性就不夠靈活了. 
autowire 屬性要麼根據類型自動裝配, 要麼根據名稱自動裝配, 不能兩者兼而有之. 
一般情況下,在實際的項目中很少使用自動裝配功能,因爲和自動裝配功能所帶來的好處比起來,明確清晰的配置文檔更有說服力一些

繼承 Bean 配置

Spring 允許繼承 bean 的配置, 被繼承的 bean 稱爲父 bean. 繼承這個父 Bean 的 Bean 稱爲子 Bean 
子 Bean 從父 Bean 中繼承配置, 包括 Bean 的屬性配置 
子 Bean 也可以覆蓋從父 Bean 繼承過來的配置 
父 Bean 可以作爲配置模板, 也可以作爲 Bean 實例. 若只想把父 Bean 作爲模板, 可以設置 的abstract 屬性爲 true, 這樣 Spring 將不會實例化這個 Bean 
並不是 元素裏的所有屬性都會被繼承. 比如: autowire, abstract 等. 
也可以忽略父 Bean 的 class 屬性, 讓子 Bean 指定自己的類, 而共享相同的屬性配置. 但此時 abstract 必須設爲 true

依賴 Bean 配置

Spring 允許用戶通過 depends-on 屬性設定 Bean 前置依賴的Bean,前置依賴的 Bean 會在本 Bean 實例化之前創建好 
如果前置依賴於多個 Bean,則可以通過逗號,空格或的方式配置 Bean 的名稱

Bean 的作用域

在 Spring 中, 可以在 元素的 scope 屬性裏設置 Bean 的作用域. 
默認情況下, Spring 只爲每個在 IOC 容器裏聲明的 Bean 創建唯一一個實例, 整個 IOC 容器範圍內都能共享該實例:所有後續的 getBean() 調用和 Bean 引用都將返回這個唯一的 Bean 實例.該作用域被稱爲 singleton, 它是所有 Bean 的默認作用域. 
這裏寫圖片描述

使用外部屬性文件

在配置文件裏配置 Bean 時, 有時需要在 Bean 的配置裏混入系統部署的細節信息(例如: 文件路徑, 數據源配置信息等). 而這些部署細節實際上需要和 Bean 配置相分離 
Spring 提供了一個 PropertyPlaceholderConfigurer 的 BeanFactory 後置處理器, 這個處理器允許用戶將 Bean 配置的部分內容外移到屬性文件中. 可以在 Bean 配置文件裏使用形式爲 ${var}的變量, PropertyPlaceholderConfigurer 從屬性文件里加載屬性, 並使用這些屬性來替換變量. 
Spring 還允許在屬性文件中使用${propName},以實現屬性之間的相互引用。

註冊 PropertyPlaceholderConfigurer

Spring 2.0:

這裏寫圖片描述

Spring 2.5 之後:

可通過<context:property-placeholder>元素簡化: 
<beans>中添加 context Schema 定義 
在配置文件中加入如下配置: 
這裏寫圖片描述

Spring表達式語言:SpEL

Spring 表達式語言(簡稱SpEL):是一個支持運行時查詢和操作對象圖的強大的表達式語言。 
語法類似於 EL:SpEL 使用 #{…}作爲定界符,所有在大框號中的字符都將被認爲是 SpEL 
SpEL 爲 bean 的屬性進行動態賦值提供了便利 
通過 SpEL 可以實現: 
1. 通過 bean 的 id 對 bean 進行引用 
2. 調用方法以及引用對象中的屬性 
3. 計算表達式的值 
4. 正則表達式的匹配

SpEL:字面量

字面量的表示: 
整數:<property name="count" value="#{5}"/> 
小數:<property name="frequency" value="#{89.7}"/> 
科學計數法:<property name="capacity" value="#{1e4}"/> 
String可以使用單引號或者雙引號作爲字符串的定界符號:<property name=“name” value="#{'Chuck'}"/> 或 <property name='name' value='#{"Chuck"}'/> 
Boolean:<property name="enabled" value="#{false}"/>

SpEL:引用 Bean、屬性和方法

  1. 引用其他對象: 
    這裏寫圖片描述
  2. 引用其他對象的屬性 
    這裏寫圖片描述
  3. 調用其他方法,還可以鏈式操作 
    這裏寫圖片描述
    這裏寫圖片描述
  4. 調用靜態方法或靜態屬性:通過 T() 調用一個類的靜態方法,它將返回一個 Class Object,然後再調用相應的方法或屬性: 
    這裏寫圖片描述

SpEL支持的運算符號

  1. 算數運算符:+, -, *, /, %, ^: 
    這裏寫圖片描述
  2. 加號還可以用作字符串連接: 
    這裏寫圖片描述
  3. 比較運算符: <, >, ==, <=, >=, lt, gt, eq, le, ge 
    這裏寫圖片描述
  4. 邏輯運算符號: and, or, not, | 
    這裏寫圖片描述
  5. if-else 運算符:?: (ternary), ?: (Elvis) 
    這裏寫圖片描述
  6. if-else 的變體 
    這裏寫圖片描述
  7. 正則表達式:matches 
    這裏寫圖片描述

IOC 容器中 Bean 的生命週期方法

Spring IOC 容器可以管理 Bean 的生命週期, Spring 允許在 Bean 生命週期的特定點執行定製的任務. 
Spring IOC 容器對 Bean 的生命週期進行管理的過程: 
1. 通過構造器或工廠方法創建 Bean 實例 
2. 爲 Bean 的屬性設置值和對其他 Bean 的引用 
3. 調用 Bean 的初始化方法 
4. Bean 可以使用了 
5. 當容器關閉時, 調用 Bean 的銷燬方法 
在 Bean 的聲明裏設置 init-method 和 destroy-method 屬性, 爲 Bean 指定初始化和銷燬方法.

創建 Bean 後置處理器

Bean 後置處理器允許在調用初始化方法前後對 Bean 進行額外的處理. 
Bean 後置處理器對 IOC 容器裏的所有 Bean 實例逐一處理, 而非單一實例. 其典型應用是: 檢查 Bean 屬性的正確性或根據特定的標準更改 Bean 的屬性. 
對Bean 後置處理器而言, 需要實現 
這裏寫圖片描述 
接口. 在初始化方法被調用前後, Spring 將把每個 Bean 實例分別傳遞給上述接口的以下兩個方法: 
這裏寫圖片描述

添加 Bean 後置處理器後 Bean 的生命週期

後置處理器實現類

@Component
public class MyBeanProcesser implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanProcesser BeforeInitialization" + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanProcesser AfterInitialization" + beanName);
        return bean;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Spring IOC 容器對 Bean 的生命週期進行管理的過程: 
1. 通過構造器或工廠方法創建 Bean 實例 
2. 爲 Bean 的屬性設置值和對其他 Bean 的引用 
3. 將 Bean 實例傳遞給 Bean 後置處理器的 postProcessBeforeInitialization 方法 
4. 調用 Bean 的初始化方法 
5. 將 Bean 實例傳遞給 Bean 後置處理器的 postProcessAfterInitialization方法 
6. Bean 可以使用了 
7. 當容器關閉時, 調用 Bean 的銷燬方法

通過工廠創建Bean

通過調用靜態工廠方法創建 Bean

調用靜態工廠方法創建 Bean是將對象創建的過程封裝到靜態方法中. 當客戶端需要對象時, 只需要簡單地調用靜態方法, 而不同關心創建對象的細節. 
要聲明通過靜態方法創建的 Bean, 需要在 Bean 的 class 屬性裏指定擁有該工廠的方法的類, 同時在 factory-method 屬性裏指定工廠方法的名稱. 最後, 使用<constrctor-arg> 元素爲該方法傳遞方法參數. 
STEP1 準備需要構造的Bean,以及靜態工廠類

public class Phone {
    private int id;
    private int stock;

    public Phone(int id, int stock) {
        this.id = id;
        this.stock = stock;
    }
    getter setter...
    @Override
    public String toString() {
        return "Phone{" +
                "id=" + id +
                ", stock=" + stock +
                '}';
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
/**
 * 靜態工程方法,不需要實例化,直接調用某一個類的靜態方法就可以返回Bean的實例
 */
public class BeanStaticFactory {

    private static Map<String,Object> m;

    static {
        if (m == null)
            m = new HashMap<>();
        m.put("iphone",new Phone(1,300));
        m.put("nokia",new Phone(3,10));
    }

    public static Object getBeanByName(String beanName){
        return m.get(beanName);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

STEP2 配置文件

    <!--
        通過靜態工廠方法來配置Bean,注意不是配置靜態工廠方法的實例,而是配置bean的實例
        class屬性 指向靜態工廠方法的全類名
        factory-method  指向靜態工廠方法的方法名
        如果靜態方法有參數,則通過constructor方法傳入 factory-method的參數.
    -->
    <bean id="phone"
          class="com.iboray.spring4test.factoryBean.BeanStaticFactory"
          factory-method="getBeanByName"
          >
        <constructor-arg value="iphone"></constructor-arg>
    </bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

測試

ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");

        Phone phone = (Phone)act.getBean("phone");

        System.out.println("The Phone is : "+ phone);
  • 1
  • 2
  • 3
  • 4
  • 5

通過調用實例工廠方法創建 Bean

STEP1 準備Bean(同上)以及實例工廠

/**
 * 實例工廠方法
 *
 */
public class InstanceFactory {

    private static Map<String,Object> m;

    public InstanceFactory() {
        if (m == null)
            m = new HashMap<>();
        m.put("iphone",new Phone(1,300));
        m.put("nokia",new Phone(3,10));
    }

    public Object getBeanByName(String beanName){
        return m.get(beanName);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

STEP2 配置文件

<!--
        通過實例工廠方法來配置Bean,首先需要實例化工廠類
        class屬性 指向實例工廠方法的全類名
    -->
    <bean id="instanceFactory"
          class="com.iboray.spring4test.factoryBean.InstanceFactory" >

    </bean>
    <!--
        配置Bean
        要聲明通過實例工廠方法創建的 Bean
        在 bean 的 factory-bean 屬性裏指定擁有該工廠方法的 Bean
        在 factory-method 屬性裏指定該工廠方法的名稱
        使用 construtor-arg 元素爲工廠方法傳遞方法參數
    -->
    <bean id="nokia" factory-bean="instanceFactory"
          factory-method="getBeanByName">
        <constructor-arg value="nokia"></constructor-arg>
    </bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

測試

    public static void main(String[] args){

        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");

        Phone phone = (Phone)act.getBean("nokia");

        System.out.println("The Phone is : "+ phone);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

實現 FactoryBean 接口在 Spring IOC 容器中配置 Bean

STEP1 準備自定義工廠類

/**
 * 自定義的BeanFactory需要實現spring提供的FactoryBean
 */
public class PhoneFactory implements FactoryBean<Phone> {

    private int phoneId;

    public void setPhoneId(int phoneId) {
        this.phoneId = phoneId;
    }

    //返回Bean對象
    @Override
    public Phone getObject() throws Exception {
        return new Phone(phoneId,3);
    }

    //返回Bean類型
    @Override
    public Class<?> getObjectType() {
        return Phone.class;
    }

    //是否單例
    @Override
    public boolean isSingleton() {
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

STEP2 配置文件

    <!--
        通過FactoryBean來配置Bean的實例
        class指向 FactoryBean的全類名
        property 配置FactoryBean的屬性
        但實際返回的是FactoryBean的geiObject返回的實例
    -->
    <bean id="phone" class="com.iboray.spring4test.factoryBean.PhoneFactory">
        <property name="phoneId" value="10"></property>
    </bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

測試

    public static void main(String[] args){

        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");

        Phone phone = (Phone)act.getBean("phone");

        System.out.println("The Phone is : "+ phone);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在 classpath 中掃描組件

組件掃描(component scanning): Spring 能夠從 classpath 下自動掃描, 偵測和實例化具有特定註解的組件. 
特定組件包括: 
1. @Component: 基本註解, 標識了一個受 Spring 管理的組件 
2. @Respository: 標識持久層組件 
3. @Service: 標識服務層(業務層)組件 
4. @Controller: 標識表現層組件 
對於掃描到的組件, Spring 有默認的命名策略: 使用非限定類名, 第一個字母小寫. 也可以在註解中通過 value 屬性值標識組件的名稱 
當在組件類上使用了特定的註解之後, 還需要在 Spring 的配置文件中聲明<context:component-scan> : 
base-package 屬性指定一個需要掃描的基類包,Spring 容器將會掃描這個基類包裏及其子包中的所有類. 
當需要掃描多個包時, 可以使用逗號分隔. 
如果僅希望掃描特定的類而非基包下的所有類,可使用 resource-pattern 屬性過濾特定的類,示例:

    <content:component-scan
            base-package="com.iboray.spring4test"
            resource-pattern="aop/*.class"
    />
  • 1
  • 2
  • 3
  • 4

<context:include-filter>子節點表示要包含的目標類 
<context:exclude-filter>子節點表示要排除在外的目標類 
<context:component-scan>下可以擁有若干個 <context:include-filter> 和 <context:exclude-filter>子節點 
<context:include-filter> 和 <context:exclude-filter>子節點支持多種類型的過濾表達式: 
這裏寫圖片描述

組件裝配

<context:component-scan> 元素還會自動註冊 AutowiredAnnotationBeanPostProcessor 實例, 該實例可以自動裝配具有 @Autowired 和 @Resource 、@Inject註解的屬性.

使用 @Autowired 自動裝配 Bean

@Autowired 註解自動裝配具有兼容類型的單個 Bean屬性 
構造器, 普通字段(即使是非 public), 一切具有參數的方法都可以應用@Authwired 註解 
默認情況下, 所有使用 @Authwired 註解的屬性都需要被設置. 當 Spring 找不到匹配的 Bean 裝配屬性時, 會拋出異常, 若某一屬性允許不被設置, 可以設置 @Authwired 註解的 required 屬性爲 false 
默認情況下, 當 IOC 容器裏存在多個類型兼容的 Bean 時, 通過類型的自動裝配將無法工作. 此時可以在 @Qualifier 註解裏提供 Bean 的名稱. Spring 允許對方法的入參標註 @Qualifiter 已指定注入 Bean 的名稱 
@Authwired 註解也可以應用在數組類型的屬性上, 此時 Spring 將會把所有匹配的 Bean 進行自動裝配. 
@Authwired 註解也可以應用在集合屬性上, 此時 Spring 讀取該集合的類型信息, 然後自動裝配所有與之兼容的 Bean. 
@Authwired 註解用在 java.util.Map 上時, 若該 Map 的鍵值爲 String, 那麼 Spring 將自動裝配與之 Map 值類型兼容的 Bean, 此時 Bean 的名稱作爲鍵值

使用 @Resource 或 @Inject 自動裝配 Bean

Spring 還支持 @Resource 和 @Inject 註解,這兩個註解和 @Autowired 註解的功用類似 
@Resource 註解要求提供一個 Bean 名稱的屬性,若該屬性爲空,則自動採用標註處的變量或方法名作爲 Bean 的名稱 
@Inject 和 @Autowired 註解一樣也是按類型匹配注入的 Bean, 但沒有 reqired 屬性 
建議使用 @Autowired 註解

整合多個配置文件

Spring 允許通過 將多個配置文件引入到一個文件中,進行配置文件的集成。這樣在啓動 Spring 容器時,僅需要指定這個合併好的配置文件就可以。 
import 元素的 resource 屬性支持 Spring 的標準的路徑資源 
這裏寫圖片描述

泛型依賴注入

Spring 4.x 中可以爲子類注入子類對應的泛型類型的成員變量的引用 
這裏寫圖片描述 
STEP0 準備一個類

public class User {
}
  • 1
  • 2

STEP1 準備Base的service和repository

/**
 * 聲明一個父類Repository,並標註泛型T
 */

public abstract class BaseRepository<T> {

    public void insert(){
        System.out.println("BaseRepository insert");
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
/**
 * 聲明一個父類Service,標註泛型T
 */
public abstract class BaseService<T> {


    /**
     * 這裏標註Autowired,意思是可以被子類繼承
     * 這樣子類傳入子類實體類型(User)後與Repository的同類型(User)的子類建立關聯.
     * 從而實現泛型依賴注入,實際調用的是BaseRepository的實現類UserRepository
     */
    @Autowired
    public BaseRepository<T> baseRepository;

    public void add(){
        System.out.println("baseRepository --> "+baseRepository);
        baseRepository.insert();

    };

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

STEP2 repository子類

/**
 * 標註Repository註解,納入IOC容器
 */
@Repository
public class UserRepository extends BaseRepository<User> {

    @Override
    public void insert() {
        System.out.println("UserRepository insert");
        super.insert();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Service子類

/**
 * 標註Service註解,納入IOC容器
 */
@Service
public class MyUserServiceImpl extends  BaseService<User>  {


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

STEP3 測試

public class Test {

    public static void main(String[] args){
        ApplicationContext atx = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyUserServiceImpl userService = (MyUserServiceImpl) atx.getBean("myUserServiceImpl");
        userService.add();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

結果

baseRepository --> com.iboray.spring4test.genericDI.UserRepository@b59d31
UserRepository insert
BaseRepository insert
  • 1
  • 2
  • 3

Spring AOP

問題背景

代碼混亂:越來越多的非業務需求(日誌和驗證等)加入後, 原有的業務方法急劇膨脹. 每個方法在處理核心邏輯的同時還必須兼顧其他多個關注點. 
代碼分散: 以日誌需求爲例, 只是爲了滿足這個單一需求, 就不得不在多個模塊(方法)裏多次重複相同的日誌代碼. 如果日誌需求發生變化, 必須修改所有模塊.

使用動態代理解決上述問題

代理設計模式的原理: 使用一個代理將對象包裝起來, 然後用該代理對象取代原始對象. 任何對原始對象的調用都要通過代理. 代理對象決定是否以及何時將方法調用轉到原始對象上. 
這裏寫圖片描述 
CalculatorLoggingHandler 
這裏寫圖片描述 
CalculatorValidationHandler 
這裏寫圖片描述 
測試代碼 
這裏寫圖片描述

AOP 簡介

AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方法論, 是對傳統 OOP(Object-Oriented Programming, 面向對象編程) 的補充. 
AOP 的主要編程對象是切面(aspect), 而切面模塊化橫切關注點. 
在應用 AOP 編程時, 仍然需要定義公共功能, 但可以明確的定義這個功能在哪裏, 以什麼方式應用, 並且不必修改受影響的類. 這樣一來橫切關注點就被模塊化到特殊的對象(切面)裏. 
AOP 的好處: 
每個事物邏輯位於一個位置, 代碼不分散, 便於維護和升級 
業務模塊更簡潔, 只包含核心業務代碼. 
這裏寫圖片描述

AOP 術語

切面(Aspect): 橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象 
通知(Advice): 切面必須要完成的工作 
目標(Target): 被通知的對象 
代理(Proxy): 向目標對象應用通知之後創建的對象 
連接點(Joinpoint):程序執行的某個特定位置:如類某個方法調用前、調用後、方法拋出異常後等。連接點由兩個信息確定:方法表示的程序執行點;相對點表示的方位。例如 ArithmethicCalculator#add() 方法執行前的連接點,執行點爲 ArithmethicCalculator#add(); 方位爲該方法執行前的位置 
切點(pointcut):每個類都擁有多個連接點:例如 ArithmethicCalculator 的所有方法實際上都是連接點,即連接點是程序類中客觀存在的事務。AOP 通過切點定位到特定的連接點。類比:連接點相當於數據庫中的記錄,切點相當於查詢條件。切點和連接點不是一對一的關係,一個切點匹配多個連接點,切點通過 org.springframework.aop.Pointcut 接口進行描述,它使用類和方法作爲連接點的查詢條件。

AspectJ:

Java 社區裏最完整最流行的 AOP 框架. 
在 Spring2.0 以上版本中, 可以使用基於 AspectJ 註解或基於 XML 配置的 AOP

在 Spring 中啓用 AspectJ 註解支持

要在 Spring 應用中使用 AspectJ 註解, 必須在 classpath 下包含 AspectJ 類庫: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar 
將 aop Schema 添加到<beans>根元素中. 
要在 Spring IOC 容器中啓用 AspectJ 註解支持, 只要在 Bean 配置文件中定義一個空的 XML 元素 <aop:aspectj-autoproxy> 
當 Spring IOC 容器偵測到 Bean 配置文件中的 <aop:aspectj-autoproxy>元素時, 會自動爲與 AspectJ 切面匹配的 Bean 創建代理.

用 AspectJ 註解聲明切面

要在 Spring 中聲明 AspectJ 切面, 只需要在 IOC 容器中將切面聲明爲 Bean 實例. 當在 Spring IOC 容器中初始化 AspectJ 切面之後, Spring IOC 容器就會爲那些與 AspectJ 切面相匹配的 Bean 創建代理. 
在 AspectJ 註解中, 切面只是一個帶有 @Aspect 註解的 Java 類. 
通知是標註有某種註解的簡單的 Java 方法. 
AspectJ 支持 5 種類型的通知註解: 
1. @Before: 前置通知, 在方法執行之前執行 
2. @After: 後置通知, 在方法執行之後執行 
3. @AfterRunning: 返回通知, 在方法返回結果之後執行 
4. @AfterThrowing: 異常通知, 在方法拋出異常之後 
5. @Around: 環繞通知, 圍繞着方法執行

前置通知

前置通知:在方法執行之前執行的通知 
前置通知使用 @Before 註解, 並將切入點表達式的值作爲註解值. 
這裏寫圖片描述

利用方法簽名編寫 AspectJ 切入點表達式

最典型的切入點表達式時根據方法的簽名來匹配各種方法: 
execution * com.atguigu.spring.ArithmeticCalculator.(..): 匹配 ArithmeticCalculator 中聲明的所有方法,第一個 代表任意修飾符及任意返回值. 第二個 * 代表任意方法. .. 匹配任意數量的參數. 若目標類與接口與該切面在同一個包中, 可以省略包名. 
execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 接口的所有公有方法. 
execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中返回 double 類型數值的方法 
execution public double ArithmeticCalculator.*(double, ..): 匹配第一個參數爲 double 類型的方法, .. 匹配任意數量任意類型的參數 
execution public double ArithmeticCalculator.*(double, double): 匹配參數類型爲 double, double 類型的方法.

合併切入點表達式

在 AspectJ 中, 切入點表達式可以通過操作符 &&, ||, ! 結合起來. 
這裏寫圖片描述

讓通知訪問當前連接點的細節

可以在通知方法中聲明一個類型爲 JoinPoint 的參數. 然後就能訪問鏈接細節. 如方法名稱和參數值. 
這裏寫圖片描述

後置通知

後置通知是在連接點完成之後執行的, 即連接點返回結果或者拋出異常的時候, 下面的後置通知記錄了方法的終止. 
一個切面可以包括一個或者多個通知. 
這裏寫圖片描述

返回通知

無論連接點是正常返回還是拋出異常, 後置通知都會執行. 如果只想在連接點返回的時候記錄日誌, 應使用返回通知代替後置通知. 
這裏寫圖片描述

在返回通知中訪問連接點的返回值

在返回通知中, 只要將 returning 屬性添加到 @AfterReturning 註解中, 就可以訪問連接點的返回值. 該屬性的值即爲用來傳入返回值的參數名稱. 
必須在通知方法的簽名中添加一個同名參數. 在運行時, Spring AOP 會通過這個參數傳遞返回值. 
原始的切點表達式需要出現在 pointcut 屬性中 
這裏寫圖片描述

異常通知

只在連接點拋出異常時才執行異常通知 
將 throwing 屬性添加到 @AfterThrowing 註解中, 也可以訪問連接點拋出的異常. Throwable 是所有錯誤和異常類的超類. 所以在異常通知方法可以捕獲到任何錯誤和異常. 
如果只對某種特殊的異常類型感興趣, 可以將參數聲明爲其他異常的參數類型. 然後通知就只在拋出這個類型及其子類的異常時才被執行. 
這裏寫圖片描述

環繞通知

環繞通知是所有通知類型中功能最爲強大的, 能夠全面地控制連接點. 甚至可以控制是否執行連接點. 
對於環繞通知來說, 連接點的參數類型必須是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允許控制何時執行, 是否執行連接點. 
在環繞通知中需要明確調用 ProceedingJoinPoint 的 proceed() 方法來執行被代理的方法. 如果忘記這樣做就會導致通知被執行了, 但目標方法沒有被執行. 
注意: 環繞通知的方法需要返回目標方法執行之後的結果, 即調用 joinPoint.proceed(); 的返回值, 否則會出現空指針異常 
這裏寫圖片描述

引入通知

引入通知是一種特殊的通知類型. 它通過爲接口提供實現類, 允許對象動態地實現接口, 就像對象已經在運行時擴展了實現類一樣. 
這裏寫圖片描述 
引入通知可以使用兩個實現類 MaxCalculatorImpl 和 MinCalculatorImpl, 讓 ArithmeticCalculatorImpl 動態地實現 MaxCalculator 和 MinCalculator 接口. 而這與從 MaxCalculatorImpl 和 MinCalculatorImpl 中實現多繼承的效果相同. 但卻不需要修改 ArithmeticCalculatorImpl 的源代碼 
引入通知也必須在切面中聲明 
在切面中, 通過爲任意字段添加@DeclareParents 註解來引入聲明. 
註解類型的 value 屬性表示哪些類是當前引入通知的目標. value 屬性值也可以是一個 AspectJ 類型的表達式, 以將一個即可引入到多個類中. defaultImpl 屬性中指定這個接口使用的實現類 
這裏寫圖片描述

指定切面的優先級

在同一個連接點上應用不止一個切面時, 除非明確指定, 否則它們的優先級是不確定的. 
切面的優先級可以通過實現 Ordered 接口或利用 @Order 註解指定. 
實現 Ordered 接口, getOrder() 方法的返回值越小, 優先級越高. 
若使用 @Order 註解, 序號出現在註解中 
這裏寫圖片描述

重用切入點定義

在編寫 AspectJ 切面時, 可以直接在通知註解中書寫切入點表達式. 但同一個切點表達式可能會在多個通知中重複出現. 
在 AspectJ 切面中, 可以通過 @Pointcut 註解將一個切入點聲明成簡單的方法. 切入點的方法體通常是空的, 因爲將切入點定義與應用程序邏輯混在一起是不合理的. 
切入點方法的訪問控制符同時也控制着這個切入點的可見性. 如果切入點要在多個切面中共用, 最好將它們集中在一個公共的類中. 在這種情況下, 它們必須被聲明爲 public. 在引入這個切入點時, 必須將類名也包括在內. 如果類沒有與這個切面放在同一個包中, 還必須包含包名. 
其他通知可以通過方法名稱引入該切入點. 
這裏寫圖片描述

練習

通過UserService的add方法練習日誌切面和驗證切面

STEP 1 UserService及實現類UserServiceImpl

public interface UserService {
    public int add(int a,int b);

    public double div(int a,int b);

    public void send(String a);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
@Service
public class UserServiceImpl implements UserService{


    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public double div(int a, int b) {
        return a/b;
    }

    @Override
    public void send(String a) {
        System.out.println("Send Message content :"+ a);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

STEP 2 日誌切面

/**
 * 標註Aspect,聲明這是一個切面
 * Component就不多說了.是爲了load到Spring IOC容器
 *
 * 指定切面的優先級
 * 在同一個連接點上應用不止一個切面時, 除非明確指定, 否則它們的優先級是不確定的.
 * 切面的優先級可以通過實現 Ordered 接口或利用 @Order 註解指定.
 * 實現 Ordered 接口, getOrder() 方法的返回值越小, 優先級越高.
 * 若使用 @Order 註解, 序號出現在註解中
 */
@Aspect
@Component
@Order(2)
public class LoggerAop {

    /**
     * 重用切入點定義
     * 在編寫 AspectJ 切面時, 可以直接在通知註解中書寫切入點表達式. 但同一個切點表達式可能會在多個通知中重複出現.
     * 在 AspectJ 切面中, 可以通過 @Pointcut 註解將一個切入點聲明成簡單的方法.
     * 切入點的方法體通常是空的, 因爲將切入點定義與應用程序邏輯混在一起是不合理的.
     * 切入點方法的訪問控制符同時也控制着這個切入點的可見性. 如果切入點要在多個切面中共用, 最好將它們集中在一個公共的類中.
     * 在這種情況下, 它們必須被聲明爲 public. 在引入這個切入點時, 必須將類名也包括在內. 如果類沒有與這個切面放在同一個包中, 還必須包含包名.
     * 其他通知可以通過方法名稱引入該切入點.
     *
     * 合併切入點表達式
     * 在 AspectJ 中, 切入點表達式可以通過操作符 &&, ||, ! 結合起來.
     */
    @Pointcut("execution(* com.iboray.spring4test.aop.*.add(..)) " +
            "|| execution(* com.iboray.spring4test.aop.*.div(..))" +
            "|| execution(* com.iboray.spring4test.aop.*.send(..))")
    private void logginOperation(){};

    /**
     * 前置通知
     * 在方法執行之前執行的通知,前置通知使用 @Before 註解, 並將切入點表達式的值作爲註解值.
     *
     *
     * 利用方法簽名編寫 AspectJ 切入點表達式
     * 最典型的切入點表達式時根據方法的簽名來匹配各種方法:
     * execution * com.atguigu.spring.ArithmeticCalculator.*(..):
     *      匹配 ArithmeticCalculator 中聲明的所有方法,第一個 * 代表任意修飾符及任意返回值.
     *      第二個 * 代表任意方法. .. 匹配任意數量的參數. 若目標類與接口與該切面在同一個包中, 可以省略包名.
     * execution public * ArithmeticCalculator.*(..):
     *      匹配 ArithmeticCalculator 接口的所有公有方法.
     * execution public double ArithmeticCalculator.*(..):
     *      匹配 ArithmeticCalculator 中返回 double 類型數值的方法
     * execution public double ArithmeticCalculator.*(double, ..):
     *      匹配第一個參數爲 double 類型的方法, .. 匹配任意數量任意類型的參數
     * execution public double ArithmeticCalculator.*(double, double):
     *      匹配參數類型爲 double, double 類型的方法.
     *
     *
     * 讓通知訪問當前連接點的細節
     * 可以在通知方法中聲明一個類型爲 JoinPoint 的參數. 然後就能訪問鏈接細節. 如方法名稱和參數值.
     */
    @Before("execution(* com.iboray.spring4test.aop.*.*(..))")
    public void beforLog(JoinPoint joinPoint){
        Object[] o = joinPoint.getArgs();
        System.out.println("[LOG] LoggerAop.beforLog--> Method : "+ joinPoint.getSignature().getName()
                +" args : "+ Arrays.asList(o));
    }

    /**
     * 後置通知
     * 後置通知是在連接點完成之後執行的, 即連接點返回結果或者拋出異常的時候, 下面的後置通知記錄了方法的終止.
     * 一個切面可以包括一個或者多個通知.
     */
    @After("logginOperation()")
    public void afterLog(JoinPoint joinPoint){
        System.out.println("[LOG] LoggerAop.afterLog  Method :"+joinPoint.getSignature().getName() +" END!");
    }

    /**
     * 返回通知
     * 無論連接點是正常返回還是拋出異常, 後置通知都會執行.
     * 如果只想在連接點返回的時候記錄日誌, 應使用返回通知代替後置通知.
     */
    @AfterReturning("logginOperation()")
    public void afterReturning(JoinPoint joinPoint){
        System.out.println("[LOG] LoggerAop.afterReturning  Method :"+joinPoint.getSignature().getName() +" END!");
    }

    /**
     * 在返回通知中訪問連接點的返回值
     * 在返回通知中, 只要將 returning 屬性添加到 @AfterReturning 註解中, 就可以訪問連接點的返回值.
     * 該屬性的值即爲用來傳入返回值的參數名稱.
     * 必須在通知方法的簽名中添加一個同名參數. 在運行時, Spring AOP 會通過這個參數傳遞返回值.
     * 原始的切點表達式需要出現在 pointcut 屬性中
     */
    @AfterReturning(pointcut = "logginOperation()",returning = "result")
    public void afterReturningByResult(JoinPoint joinPoint,Object result){

        System.out.println("[LOG] LoggerAop.afterReturningByResult  Method :"+joinPoint.getSignature().getName() +"" +
                "    Result : "+result+ "   END!");

    }

    /**
     * 異常通知
     * 只在連接點拋出異常時才執行異常通知將 throwing 屬性添加到 @AfterThrowing 註解中,
     * 也可以訪問連接點拋出的異常. Throwable 是所有錯誤和異常類的超類. 所以在異常通知方法可以捕獲到任何錯誤和異常.
     * 如果只對某種特殊的異常類型感興趣, 可以將參數聲明爲其他異常的參數類型.
     * 然後通知就只在拋出這個類型及其子類的異常時才被執行.
     */
    @AfterThrowing(pointcut = "logginOperation()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e){
        System.out.println("[LOG] LoggerAop.afterThrowing  Method :"+joinPoint.getSignature().getName() +"" +
                "    Exception : "+e+ "   END!");
    }

    /**
     * 環繞通知
     * 環繞通知是所有通知類型中功能最爲強大的, 能夠全面地控制連接點. 甚至可以控制是否執行連接點.
     * 對於環繞通知來說, 連接點的參數類型必須是 ProceedingJoinPoint . 它是 JoinPoint 的子接口,
     * 允許控制何時執行, 是否執行連接點.
     * 在環繞通知中需要明確調用 ProceedingJoinPoint 的 proceed() 方法來執行被代理的方法.
     * 如果忘記這樣做就會導致通知被執行了, 但目標方法沒有被執行.
     * 注意: 環繞通知的方法需要返回目標方法執行之後的結果, 即調用 joinPoint.proceed(); 的返回值, 否則會出現空指針異常
     */
    @Around("logginOperation()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[LOG] LoggerAop.around  Method :"+proceedingJoinPoint.getSignature().getName()+" Start...");
        try {

            proceedingJoinPoint.proceed();

            System.out.println("[LOG] LoggerAop.around  Method :"+proceedingJoinPoint.getSignature().getName()+" End...");
        } catch (Throwable throwable) {
            System.out.println("[LOG] LoggerAop.around  Method :"+proceedingJoinPoint.getSignature().getName()+" Throws Exception..." + throwable);
            throw throwable;
        }

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134

STEP3 驗證切面

@Aspect
@Component
@Order(1)
public class ValidatorAop {

    @Before("execution(* com.iboray.spring4test.aop.*.*(..))")
    public void beforValidation(JoinPoint joinPoint){

        System.out.println("[VALIDATE] ValidatorAopXml.beforValidation Method : "+ joinPoint.getSignature().getName());

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

STEP4 測試

public class Test {

    public static void main(String[] args){
/**
         * applicationContext.xml配置
         * <content:component-scan base-package="com.iboray.spring4test.aop" />
         * <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
         */
        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userService = (UserService)act.getBean("userServiceImpl");

        double c = userService.add(2,1);

        System.out.println("The result is : "+ c);


    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

結果

[VALIDATE] ValidatorAopXml.beforValidation Method : add
[LOG] LoggerAop.around  Method :add Start...
[LOG] LoggerAop.beforLog--> Method : add args : [2, 1]
[LOG] LoggerAop.around  Method :add End...
[LOG] LoggerAop.afterLog  Method :add END!
[LOG] LoggerAop.afterReturning  Method :add END!
[LOG] LoggerAop.afterReturningByResult  Method :add    Result : null   END!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

用基於 XML 的配置聲明切面

除了使用 AspectJ 註解聲明切面, Spring 也支持在 Bean 配置文件中聲明切面. 這種聲明是通過 aop schema 中的 XML 元素完成的. 
正常情況下, 基於註解的聲明要優先於基於 XML 的聲明. 通過 AspectJ 註解, 切面可以與 AspectJ 兼容, 而基於 XML 的配置則是 Spring 專有的. 由於 AspectJ 得到越來越多的 AOP 框架支持, 所以以註解風格編寫的切面將會有更多重用的機會.

基於 XML —- 聲明切面

當使用 XML 聲明切面時, 需要在 根元素中導入 aop Schema 
在 Bean 配置文件中, 所有的 Spring AOP 配置都必須定義在 元素內部. 對於每個切面而言, 都要創建一個 元素來爲具體的切面實現引用後端 Bean 實例. 
切面 Bean 必須有一個標示符, 供 元素引用 
這裏寫圖片描述

基於 XML —- 聲明切入點

切入點使用 元素聲明 
切入點必須定義在 元素下, 或者直接定義在 元素下. 
定義在 元素下: 只對當前切面有效 
定義在 元素下: 對所有切面都有效 
基於 XML 的 AOP 配置不允許在切入點表達式中用名稱引用其他切入點. 
這裏寫圖片描述

基於 XML —- 聲明通知

在 aop Schema 中, 每種通知類型都對應一個特定的 XML 元素. 
通知元素需要使用 來引用切入點, 或用 直接嵌入切入點表達式. method 屬性指定切面類中通知方法的名稱. 
這裏寫圖片描述

聲明引入

可以利用 元素在切面內部聲明引入 
這裏寫圖片描述

練習

STEP1 驗證切面

public class ValidatorAopXml {

    public void beforValidation(JoinPoint joinPoint){

        System.out.println("[VALIDATE] ValidatorAopXml.beforValidation Method : "+ joinPoint.getSignature().getName());

    }

    public void throwMethod(JoinPoint joinPoint,Exception e ){

        System.out.println("[VALIDATE] ValidatorAopXml.throwMethod Method : "+ joinPoint.getSignature().getName()+"" +
                " Exception"+e);

    }

    public void resultValidation(JoinPoint joinPoint,Object result){

        System.out.println("[VALIDATE] ValidatorAopXml.resultValidation Method : "+ joinPoint.getSignature().getName()+"" +
                " Result is : "+result);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

STEP2 配置文件

<!--
        基於 XML 的配置聲明切面
        除了使用 AspectJ 註解聲明切面, Spring 也支持在 Bean 配置文件中聲明切面.
        這種聲明是通過 aop schema 中的 XML 元素完成的.
        正常情況下, 基於註解的聲明要優先於基於 XML 的聲明. 通過 AspectJ 註解, 切面可以與 AspectJ 兼容,
        而基於 XML 的配置則是 Spring 專有的. 由於 AspectJ 得到越來越多的 AOP 框架支持,
        所以以註解風格編寫的切面將會有更多重用的機會.
    -->
    <!--配置通知-->
    <bean id="validatorAopXml" class="com.iboray.spring4test.aop.xml.ValidatorAopXml"></bean>

    <!--配置接口-->
    <bean id="userService" class="com.iboray.spring4test.aop.UserServiceImpl"></bean>

    <!--
        聲明切面
        當使用 XML 聲明切面時, 需要在 <beans> 根元素中導入 aop Schema
        在 Bean 配置文件中, 所有的 Spring AOP 配置都必須定義在 <aop:config> 元素內部.
        對於每個切面而言, 都要創建一個 <aop:aspect> 元素來爲具體的切面實現引用後端 Bean 實例.
        切面 Bean 必須有一個標示符, 供 <aop:aspect> 元素引用
    -->
    <!--配置AOP-->
    <aop:config>
        <!--
            聲明切入點
            切入點使用 <aop:pointcut> 元素聲明
            切入點必須定義在 <aop:aspect> 元素下, 或者直接定義在 <aop:config> 元素下.
            定義在 <aop:aspect> 元素下: 只對當前切面有效
            定義在 <aop:config> 元素下: 對所有切面都有效
            基於 XML 的 AOP 配置不允許在切入點表達式中用名稱引用其他切入點.
        -->
        <aop:pointcut id="default" expression="execution(* com.iboray.spring4test.aop.*.*(..))" />
        <aop:aspect id="vax" ref="validatorAopXml">
            <!--
                聲明通知
                在 aop Schema 中, 每種通知類型都對應一個特定的 XML 元素.
                通知元素需要使用 <pointcut-ref> 來引用切入點, 或用 <pointcut> 直接嵌入切入點表達式.
                method 屬性指定切面類中通知方法的名稱.
            -->
            <aop:before method="beforValidation" pointcut-ref="default"></aop:before>
            <aop:after-throwing method="throwMethod" pointcut-ref="default" throwing="e"></aop:after-throwing>
            <aop:after-returning method="resultValidation" pointcut-ref="default" returning="result"></aop:after-returning>
        </aop:aspect>


    </aop:config>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

測試

    public static void main(String[] args){

        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext-aop-xml.xml");

        UserService userService = (UserService)act.getBean("userService");

        double c = userService.div(2,1);

        System.out.println("The main result is : "+ c);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

結果

[VALIDATE] ValidatorAopXml.beforValidation Method : div
[VALIDATE] ValidatorAopXml.resultValidation Method : div Result is : 2.0
The main result is : 2.0
  • 1
  • 2
  • 3

JDBC 支持

JdbcTemplate 簡介

爲了使 JDBC 更加易於使用, Spring 在 JDBC API 上定義了一個抽象層, 以此建立一個 JDBC 存取框架. 
作爲 Spring JDBC 框架的核心, JDBC 模板的設計目的是爲不同類型的 JDBC 操作提供模板方法. 每個模板方法都能控制整個過程, 並允許覆蓋過程中的特定任務. 通過這種方式, 可以在儘可能保留靈活性的情況下, 將數據庫存取的工作量降到最低.

使用 JdbcTemplate 更新數據庫

這裏寫圖片描述

使用 JdbcTemplate 查詢數據庫

這裏寫圖片描述 
這裏寫圖片描述

簡化 JDBC 模板查詢

每次使用都創建一個 JdbcTemplate 的新實例, 這種做法效率很低下. 
JdbcTemplate 類被設計成爲線程安全的, 所以可以再 IOC 容器中聲明它的單個實例, 並將這個實例注入到所有的 DAO 實例中. 
JdbcTemplate 也利用了 Java 1.5 的特定(自動裝箱, 泛型, 可變長度等)來簡化開發 
Spring JDBC 框架還提供了一個 JdbcDaoSupport 類來簡化 DAO 實現. 該類聲明瞭 jdbcTemplate 屬性, 它可以從 IOC 容器中注入, 或者自動從數據源中創建.

注入 JDBC 模板示例代碼

這裏寫圖片描述

擴展 JdbcDaoSupport 示例代碼

這裏寫圖片描述

在 JDBC 模板中使用具名參數

在經典的 JDBC 用法中, SQL 參數是用佔位符 ? 表示,並且受到位置的限制. 定位參數的問題在於, 一旦參數的順序發生變化, 就必須改變參數綁定. 
在 Spring JDBC 框架中, 綁定 SQL 參數的另一種選擇是使用具名參數(named parameter). 
具名參數: SQL 按名稱(以冒號開頭)而不是按位置進行指定. 具名參數更易於維護, 也提升了可讀性. 具名參數由框架類在運行時用佔位符取代 
具名參數只在 NamedParameterJdbcTemplate 中得到支持 
在 SQL 語句中使用具名參數時, 可以在一個 Map 中提供參數值, 參數名爲鍵 
也可以使用 SqlParameterSource 參數 
批量更新時可以提供 Map 或 SqlParameterSource 的數組 
這裏寫圖片描述

事務管理

事務管理是企業級應用程序開發中必不可少的技術, 用來確保數據的完整性和一致性. 
事務就是一系列的動作, 它們被當做一個單獨的工作單元. 這些動作要麼全部完成, 要麼全部不起作用 
事務的四個關鍵屬性(ACID) 
1. 原子性(atomicity): 事務是一個原子操作, 由一系列動作組成. 事務的原子性確保動作要麼全部完成要麼完全不起作用. 
2. 一致性(consistency): 一旦所有事務動作完成, 事務就被提交. 數據和資源就處於一種滿足業務規則的一致性狀態中. 
3. 隔離性(isolation): 可能有許多事務會同時處理相同的數據, 因此每個事物都應該與其他事務隔離開來, 防止數據損壞. 
4. 持久性(durability): 一旦事務完成, 無論發生什麼系統錯誤, 它的結果都不應該受到影響. 通常情況下, 事務的結果被寫到持久化存儲器中.

問題

必須爲不同的方法重寫類似的樣板代碼 
這段代碼是特定於 JDBC 的, 一旦選擇類其它數據庫存取技術, 代碼需要作出相應的修改

Spring 中的事務管理

作爲企業級應用程序框架, Spring 在不同的事務管理 API 之上定義了一個抽象層. 而應用程序開發人員不必瞭解底層的事務管理 API, 就可以使用 Spring 的事務管理機制. 
Spring 既支持編程式事務管理, 也支持聲明式的事務管理. 
編程式事務管理: 將事務管理代碼嵌入到業務方法中來控制事務的提交和回滾. 在編程式管理事務時, 必須在每個事務操作中包含額外的事務管理代碼. 
聲明式事務管理: 大多數情況下比編程式事務管理更好用. 它將事務管理代碼從業務方法中分離出來, 以聲明的方式來實現事務管理. 事務管理作爲一種橫切關注點, 可以通過 AOP 方法模塊化. Spring 通過 Spring AOP 框架支持聲明式事務管理.

Spring 中的事務管理器

Spring 從不同的事務管理 API 中抽象了一整套的事務機制. 開發人員不必瞭解底層的事務 API, 就可以利用這些事務機制. 有了這些事務機制, 事務管理代碼就能獨立於特定的事務技術了. 
Spring 的核心事務管理抽象是 
這裏寫圖片描述 
它爲事務管理封裝了一組獨立於技術的方法. 無論使用 Spring 的哪種事務管理策略(編程式或聲明式), 事務管理器都是必須的.

Spring 中的事務管理器的不同實現

  1. 在應用程序中只需要處理一個數據源, 而且通過 JDBC 存取 
    這裏寫圖片描述
  2. 在 JavaEE 應用服務器上用 JTA(Java Transaction API) 進行事務管理 
    這裏寫圖片描述
  3. 用 Hibernate 框架存取數據庫 
    這裏寫圖片描述

事務管理器以普通的 Bean 形式聲明在 Spring IOC 容器中

用事務通知聲明式地管理事務

事務管理是一種橫切關注點 
爲了在 Spring 2.x 中啓用聲明式事務管理, 可以通過 tx Schema 中定義的 元素聲明事務通知, 爲此必須事先將這個 Schema 定義添加到 根元素中去. 
聲明瞭事務通知後, 就需要將它與切入點關聯起來. 由於事務通知是在 元素外部聲明的, 所以它無法直接與切入點產生關聯. 所以必須在 元素中聲明一個增強器通知與切入點關聯起來. 
由於 Spring AOP 是基於代理的方法, 所以只能增強公共方法. 因此, 只有公有方法才能通過 Spring AOP 進行事務管理. 
這裏寫圖片描述

用 @Transactional 註解聲明式地管理事務

除了在帶有切入點, 通知和增強器的 Bean 配置文件中聲明事務外, Spring 還允許簡單地用 @Transactional 註解來標註事務方法. 
爲了將方法定義爲支持事務處理的, 可以爲方法添加 @Transactional 註解. 根據 Spring AOP 基於代理機制, 只能標註公有方法. 
可以在方法或者類級別上添加 @Transactional 註解. 當把這個註解應用到類上時, 這個類中的所有公共方法都會被定義成支持事務處理的. 
在 Bean 配置文件中只需要啓用 元素, 併爲之指定事務管理器就可以了. 
如果事務處理器的名稱是 transactionManager, 就可以在 元素中省略 transaction-manager 屬性. 這個元素會自動檢測該名稱的事務處理器. 
用 @Transactional 註解聲明式地管理事務配置文件示例代碼

    <context:property-placeholder location="classpath:db.properties" order="1" />
    <context:component-scan base-package="com.iboray.spring4test.tx.annotation" />
    <!--配置dataSource-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
     </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置transactionManager-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--啓用註解事務-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

事務傳播屬性

當事務方法被另一個事務方法調用時, 必須指定事務應該如何傳播. 例如: 方法可能繼續在現有事務中運行, 也可能開啓一個新事務, 並在自己的事務中運行. 
事務的傳播行爲可以由傳播屬性指定. Spring 定義了 7 種類傳播行爲.

Spring 支持的事務傳播行爲

這裏寫圖片描述

REQUIRED 傳播行爲

當 bookService 的 purchase() 方法被另一個事務方法 checkout() 調用時, 它默認會在現有的事務內運行. 這個默認的傳播行爲就是 REQUIRED. 因此在 checkout() 方法的開始和終止邊界內只有一個事務. 這個事務只在 checkout() 方法結束的時候被提交, 結果用戶一本書都買不了 
事務傳播屬性可以在 @Transactional 註解的 propagation 屬性中定義 
這裏寫圖片描述

REQUIRES_NEW 傳播行爲

另一種常見的傳播行爲是 REQUIRES_NEW. 它表示該方法必須啓動一個新事務, 並在自己的事務內運行. 如果有事務在運行, 就應該先掛起它. 
這裏寫圖片描述

在 Spring 2.x 事務通知中配置傳播屬性

在 Spring 2.x 事務通知中, 可以像下面這樣在 元素中設定傳播事務屬性 
這裏寫圖片描述

併發事務所導致的問題

當同一個應用程序或者不同應用程序中的多個事務在同一個數據集上併發執行時, 可能會出現許多意外的問題 
併發事務所導致的問題可以分爲下面三種類型: 
髒讀: 對於兩個事物 T1, T2, T1 讀取了已經被 T2 更新但 還沒有被提交的字段. 之後, 若 T2 回滾, T1讀取的內容就是臨時且無效的. 
不可重複讀:對於兩個事物 T1, T2, T1 讀取了一個字段, 然後 T2 更新了該字段. 之後, T1再次讀取同一個字段, 值就不同了. 
幻讀:對於兩個事物 T1, T2, T1 從一個表中讀取了一個字段, 然後 T2 在該表中插入了一些新的行. 之後, 如果 T1 再次讀取同一個表, 就會多出幾行.

事務的隔離級別

從理論上來說, 事務應該彼此完全隔離, 以避免併發事務所導致的問題. 然而, 那樣會對性能產生極大的影響, 因爲事務必須按順序運行. 
在實際開發中, 爲了提升性能, 事務會以較低的隔離級別運行. 
事務的隔離級別可以通過隔離事務屬性指定

Spring 支持的事務隔離級別

這裏寫圖片描述 
事務的隔離級別要得到底層數據庫引擎的支持, 而不是應用程序或者框架的支持. 
Oracle 支持的 2 種事務隔離級別:READ_COMMITED , SERIALIZABLE 
Mysql 支持 4 中事務隔離級別.

設置隔離事務屬性

用 @Transactional 註解聲明式地管理事務時可以在 @Transactional 的 isolation 屬性中設置隔離級別. 
這裏寫圖片描述 
在 Spring 2.x 事務通知中, 可以在 元素中指定隔離級別 
這裏寫圖片描述

設置回滾事務屬性

默認情況下只有未檢查異常(RuntimeException和Error類型的異常)會導致事務回滾. 而受檢查異常不會. 
事務的回滾規則可以通過 @Transactional 註解的 rollbackFor 和 noRollbackFor 屬性來定義. 這兩個屬性被聲明爲 Class[] 類型的, 因此可以爲這兩個屬性指定多個異常類. 
1. rollbackFor: 遇到時必須進行回滾 
2. noRollbackFor: 一組異常類,遇到時必須不回滾 
這裏寫圖片描述 
在 Spring 2.x 事務通知中, 可以在 元素中指定回滾規則. 如果有不止一種異常, 用逗號分隔. 
這裏寫圖片描述

超時和只讀屬性

由於事務可以在行和表上獲得鎖, 因此長事務會佔用資源, 並對整體性能產生影響. 
如果一個事物只讀取數據但不做修改, 數據庫引擎可以對這個事務進行優化. 
超時事務屬性: 事務在強制回滾之前可以保持多久. 這樣可以防止長期運行的事務佔用資源. 
只讀事務屬性: 表示這個事務只讀取數據但不更新數據, 這樣可以幫助數據庫引擎優化事務.

設置超時和只讀事務屬性

超時和只讀屬性可以在 @Transactional 註解中定義.超時屬性以秒爲單位來計算. 
這裏寫圖片描述 
在 Spring 2.x 事務通知中, 超時和只讀屬性可以在 元素中進行指定. 
這裏寫圖片描述

練習-註解方式

STEP1 準備數據庫及實體類

//手機
public class Phone {
    private int id; //手機編號
    private int stock; //庫存
    getter setter...
}
//餘額
public class Balance {
    private int id; 
    private BigDecimal money; //餘額
    getter setter...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

STEP2 手機和餘額的repository

@Repository
public class BalanceRepository  {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void sell(Integer id,BigDecimal m){
        System.out.println("正在計算扣款....");
        String sql="UPDATE balance SET money = ( money - ? ) WHERE id = ?";
        Object[] objects = new Object[]{m,id};
        int result = jdbcTemplate.update(sql,objects);
        if (result == 1){
            System.out.println("賬戶已扣款");
        }
    }

}

@Repository
public class PhoneRepository {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void buy(Integer id){
        int stock = calculationStock(id);
        Thread t  = Thread.currentThread();
        try {
            t.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String sql="UPDATE phone SET stock = ( stock - 1 ) WHERE id = ?";
        Object[] objects = new Object[]{id};
        int result = jdbcTemplate.update(sql,objects);
        if (result == 1){
            System.out.println("庫存已減少");
        }
    }
    public int calculationStock(Integer bookId){
        System.out.println("正在覈算庫存....");
        String sql = "SELECT stock FROM phone where id = ?";
        Integer stock = 0;
        try {
            stock = jdbcTemplate.queryForObject(sql,new Object[]{bookId},Integer.class);
        } catch (DataAccessException e) {
            throw new StockException("庫存異常");
        }
        System.out.println("ID爲 : [ "+bookId+" ] 的庫存爲:"+stock);
        if (stock == 0)
            throw new StockException("庫存異常");
        return stock;
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

STEP3 service

public interface BalanceService {

    /**
     *
     * @param balanceId 賬戶ID
     * @param bookId 圖書ID
     * @param money 圖書價格
     */
    public void selling(int balanceId, int bookId, BigDecimal money);
}

@Service(value = "balanceService")
public class BalanceServiceImpl implements BalanceService {

    @Autowired
    private BalanceRepository balanceRepository;

    @Autowired
    private PhoneRepository phoneRepository;

    @Transactional(propagation = Propagation.REQUIRED
            ,isolation = Isolation.READ_COMMITTED
            ,timeout = 2)
    @Override
    public void selling(int balanceId,int bookId, BigDecimal money) {

        //先扣錢
        balanceRepository.sell(balanceId,money);

        //再交貨
        phoneRepository.buy(bookId);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

STEP4 自定義異常

public class StockException extends RuntimeException {

    public StockException(String message) {
        super(message);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

STEP5 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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-4.1.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">

    <context:property-placeholder location="classpath:db.properties" order="1" />
    <context:component-scan base-package="com.iboray.spring4test.tx.annotation" />
    <!--配置dataSource-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
     </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置transactionManager-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--啓用註解事務-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

STEP6 測試 
數據庫情況 
phone表 
這裏寫圖片描述 
balance表 
這裏寫圖片描述

測試1 正常情況

public static void main(String[] args){
        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext-tx-annotation.xml");
        BalanceService balanceService = (BalanceService) act.getBean("balanceService");
        balanceService.selling(1,2,new BigDecimal(30));
    }
  • 1
  • 2
  • 3
  • 4
  • 5

執行結果

正在計算扣款....
賬戶已扣款
正在覈算庫存....
ID爲 : [ 2 ] 的庫存爲:4
庫存已減少
  • 1
  • 2
  • 3
  • 4
  • 5

數據庫 
這裏寫圖片描述

情況2 異常情況

//將這裏的圖書ID換爲沒有庫存的ID
        balanceService.selling(1,1,new BigDecimal(30));
  • 1
  • 2

執行結果

正在計算扣款....
賬戶已扣款
正在覈算庫存....
ID爲 : [ 1 ] 的庫存爲:0
Exception in thread "main" com.iboray.spring4test.tx.xml.StockException: 庫存異常
  • 1
  • 2
  • 3
  • 4
  • 5

數據庫 
這裏寫圖片描述

練習-XML方式

說明:數據庫,實體類,repository,service接口類和測試類都一樣。 
STEP1 
將BalanceServiceImpl中事務註解去掉,修改爲

@Service(value = "balanceService")
public class BalanceServiceImpl implements BalanceService {

    @Autowired
    private BalanceRepository balanceRepository;

    @Autowired
    private PhoneRepository phoneRepository;

    @Override
    public void selling(int balanceId,int bookId, BigDecimal money) {

        //先扣錢
        balanceRepository.sell(balanceId,money);

        //再交貨
        phoneRepository.buy(bookId);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

STEP2 修改配置文件

    <context:property-placeholder location="classpath:db.properties" order="1" />
    <context:component-scan base-package="com.iboray.spring4test.tx.xml" />
    <!--配置dataSource-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
     </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置transactionManager-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置advice-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!--配置aspect-->
    <aop:config>
        <aop:pointcut id="default" expression="execution(* com.iboray.spring4test.tx.xml.service.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="default"></aop:advisor>
    </aop:config>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

測試正常和異常情況均和註解測試結果一致。

[置頂] 一篇文章學會Spring4.0

標籤: spring
 4984人閱讀 評論(9) 收藏 舉報
 分類:
SPRING(16) 

目錄(?)[+]

spring概述

Spring 是一個開源框架. 
Spring 爲簡化企業級應用開發而生. 使用 Spring 可以使簡單的 JavaBean 實現以前只有 EJB 才能實現的功能. 
Spring 是一個 IOC(DI) 和 AOP 容器框架. 
具體描述 Spring: 
輕量級:Spring 是非侵入性的 - 基於 Spring 開發的應用中的對象可以不依賴於 Spring 的 API 
依賴注入(DI — dependency injection、IOC) 
面向切面編程(AOP — aspect oriented programming) 
容器: Spring 是一個容器, 因爲它包含並且管理應用對象的生命週期 
框架: Spring 實現了使用簡單的組件配置組合成一個複雜的應用. 在 Spring 中可以使用 XML 和 Java 註解組合這些對象 
一站式:在 IOC 和 AOP 的基礎上可以整合各種企業應用的開源框架和優秀的第三方類庫 (實際上 Spring 自身也提供了展現層的 SpringMVC 和 持久層的 Spring JDBC)

spring模塊

這裏寫圖片描述

搭建 Spring 開發環境

Spring 的配置文件: 
一個典型的 Spring 項目需要創建一個或多個 Bean 配置文件, 這些配置文件用於在 Spring IOC 容器裏配置 Bean. Bean 的配置文件可以放在 classpath 下, 也可以放在其它目錄下. 
MAVEN配置增加(因爲要設計到數據庫操作,所以一塊兒放進來)

<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring-version}</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
      <version>1.7.4</version>
    </dependency>

    <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.35</version>
    </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

IOC 和 DI

IOC(Inversion of Control):其思想是反轉資源獲取的方向. 傳統的資源查找方式要求組件向容器發起請求查找資源. 作爲迴應, 容器適時的返回資源. 而應用了 IOC 之後, 則是容器主動地將資源推送給它所管理的組件, 組件所要做的僅是選擇一種合適的方式來接受資源. 這種行爲也被稱爲查找的被動形式 
DI(Dependency Injection) — IOC 的另一種表述方式:即組件以一些預先定義好的方式(例如: setter 方法)接受來自如容器的資源注入. 相對於 IOC 而言,這種表述更直接

這裏寫圖片描述

IOC 前生 — 分離接口與實現

需求: 生成 HTML 或 PDF 格式的不同類型的報表 
這裏寫圖片描述

IOC 前生 — 採用工廠設計模式

這裏寫圖片描述

IOC — 採用反轉控制

這裏寫圖片描述

Spring 容器

在 Spring IOC 容器讀取 Bean 配置創建 Bean 實例之前, 必須對它進行實例化. 只有在容器實例化後, 纔可以從 IOC 容器裏獲取 Bean 實例並使用. 
Spring 提供了兩種類型的 IOC 容器實現. 
BeanFactory: IOC 容器的基本實現. 
ApplicationContext: 提供了更多的高級特性. 是 BeanFactory 的子接口. 
BeanFactory 是 Spring 框架的基礎設施,面向 Spring 本身;ApplicationContext 面向使用 Spring 框架的開發者,幾乎所有的應用場合都直接使用 ApplicationContext 而非底層的 BeanFactory 
無論使用何種方式, 配置文件時相同的.

ApplicationContext

ApplicationContext 的主要實現類: 
ClassPathXmlApplicationContext:從 類路徑下加載配置文件 
FileSystemXmlApplicationContext: 從文件系統中加載配置文件 
ConfigurableApplicationContext 擴展於 ApplicationContext,新增加兩個主要方法:refresh() 和 close(), 讓 ApplicationContext 具有啓動、刷新和關閉上下文的能力 
ApplicationContext 在初始化上下文時就實例化所有單例的 Bean。 
WebApplicationContext 是專門爲 WEB 應用而準備的,它允許從相對於 WEB 根目錄的路徑中完成初始化工作

從 IOC 容器中獲取 Bean

調用 ApplicationContext 的 getBean() 方法 
這裏寫圖片描述

依賴注入的方式

Spring 支持 3 種依賴注入的方式 
1. 屬性注入 
2. 構造器注入 
3. 工廠方法注入(很少使用,不推薦)

屬性注入

屬性注入即通過 setter 方法注入Bean 的屬性值或依賴的對象 
屬性注入使用<property>元素, 使用 name 屬性指定 Bean 的屬性名稱,value 屬性或<value>子節點指定屬性值 
屬性注入是實際應用中最常用的注入方式 
這裏寫圖片描述

構造方法注入

通過構造方法注入Bean 的屬性值或依賴的對象,它保證了 Bean 實例在實例化後就可以使用。 
構造器注入在 <constructor-arg> 元素裏聲明屬性, <constructor-arg>中沒有 name 屬性 
按索引匹配入參: 
這裏寫圖片描述 
按類型匹配入參: 
這裏寫圖片描述

注入細節

字面值

字面值:可用字符串表示的值,可以通過<value>元素標籤或 value 屬性進行注入。 
基本數據類型及其封裝類、String 等類型都可以採取字面值注入的方式 
若字面值中包含特殊字符,可以使用

引用其它 Bean

組成應用程序的 Bean 經常需要相互協作以完成應用程序的功能. 要使 Bean 能夠相互訪問, 就必須在 Bean 配置文件中指定對 Bean 的引用 
在 Bean 的配置文件中, 可以通過<ref>元素或 ref 屬性爲 Bean 的屬性或構造器參數指定對 Bean 的引用. 
也可以在屬性或構造器裏包含 Bean 的聲明, 這樣的 Bean 稱爲內部 Bean 
這裏寫圖片描述

內部 Bean

當 Bean 實例僅僅給一個特定的屬性使用時, 可以將其聲明爲內部 Bean. 內部 Bean 聲明直接包含在<property>或 <constructor-arg>元素裏, 不需要設置任何 id 或 name 屬性 
內部 Bean 不能使用在任何其他地方

注入參數詳解:null 值和級聯屬性

可以使用專用的 <null/>元素標籤爲 Bean 的字符串或其它對象類型的屬性注入 null 值 
和 Struts、Hiberante 等框架一樣,Spring 支持級聯屬性的配置。

集合屬性

在 Spring中可以通過一組內置的 xml 標籤(例如:<list>, <set> 或 <map>) 來配置集合屬性.

java.util.List

配置 java.util.List 類型的屬性, 需要指定<list>標籤, 在標籤裏包含一些元素. 這些標籤可以通過<value> 指定簡單的常量值, 通過 <ref>指定對其他 Bean 的引用. 通過<bean> 指定內置 Bean 定義. 通過<null/>指定空元素. 甚至可以內嵌其他集合. 
數組的定義和 List 一樣, 都使用<list>

java.util.Set

配置 java.util.Set 需要使用<set> 標籤, 定義元素的方法與 List 一樣.

Java.util.Map

Java.util.Map 通過 <map> 標籤定義, <map>標籤裏可以使用多個 <entry> 作爲子標籤. 每個條目包含一個鍵和一個值. 
必須在<key> 標籤裏定義鍵 
因爲鍵和值的類型沒有限制, 所以可以自由地爲它們指定<value>, <ref>, <bean> 或 <null>元素. 
可以將 Map 的鍵和值作爲 <entry>的屬性定義: 簡單常量使用 key 和 value 來定義; Bean 引用通過 key-ref 和 value-ref 屬性定義 
使用<props>定義 java.util.Properties, 該標籤使用多個<prop>作爲子標籤. 每個<prop>標籤必須定義 key 屬性.

使用 utility scheme 定義集合

使用基本的集合標籤定義集合時, 不能將集合作爲獨立的 Bean 定義, 導致其他 Bean 無法引用該集合, 所以無法在不同 Bean 之間共享集合. 
可以使用 util schema 裏的集合標籤定義獨立的集合 Bean. 需要注意的是, 必須在 根元素裏添加 util schema 定義

使用 p 命名空間

爲了簡化 XML 文件的配置,越來越多的 XML 文件採用屬性而非子元素配置信息。 
Spring 從 2.5 版本開始引入了一個新的 p 命名空間,可以通過 <bean>元素屬性的方式配置 Bean 的屬性。 
使用 p 命名空間後,基於 XML 的配置方式將進一步簡化

XML 配置裏的 Bean 自動裝配

Spring IOC 容器可以自動裝配 Bean. 需要做的僅僅是在 的 autowire 屬性裏指定自動裝配的模式 
byType(根據類型自動裝配): 若 IOC 容器中有多個與目標 Bean 類型一致的 Bean. 在這種情況下, Spring 將無法判定哪個 Bean 最合適該屬性, 所以不能執行自動裝配. 
byName(根據名稱自動裝配): 必須將目標 Bean 的名稱和屬性名設置的完全相同. 
constructor(通過構造器自動裝配): 當 Bean 中存在多個構造器時, 此種自動裝配方式將會很複雜. 不推薦使用

自動裝配的缺點

在 Bean 配置文件裏設置 autowire 屬性進行自動裝配將會裝配 Bean 的所有屬性. 然而, 若只希望裝配個別屬性時, autowire 屬性就不夠靈活了. 
autowire 屬性要麼根據類型自動裝配, 要麼根據名稱自動裝配, 不能兩者兼而有之. 
一般情況下,在實際的項目中很少使用自動裝配功能,因爲和自動裝配功能所帶來的好處比起來,明確清晰的配置文檔更有說服力一些

繼承 Bean 配置

Spring 允許繼承 bean 的配置, 被繼承的 bean 稱爲父 bean. 繼承這個父 Bean 的 Bean 稱爲子 Bean 
子 Bean 從父 Bean 中繼承配置, 包括 Bean 的屬性配置 
子 Bean 也可以覆蓋從父 Bean 繼承過來的配置 
父 Bean 可以作爲配置模板, 也可以作爲 Bean 實例. 若只想把父 Bean 作爲模板, 可以設置 的abstract 屬性爲 true, 這樣 Spring 將不會實例化這個 Bean 
並不是 元素裏的所有屬性都會被繼承. 比如: autowire, abstract 等. 
也可以忽略父 Bean 的 class 屬性, 讓子 Bean 指定自己的類, 而共享相同的屬性配置. 但此時 abstract 必須設爲 true

依賴 Bean 配置

Spring 允許用戶通過 depends-on 屬性設定 Bean 前置依賴的Bean,前置依賴的 Bean 會在本 Bean 實例化之前創建好 
如果前置依賴於多個 Bean,則可以通過逗號,空格或的方式配置 Bean 的名稱

Bean 的作用域

在 Spring 中, 可以在 元素的 scope 屬性裏設置 Bean 的作用域. 
默認情況下, Spring 只爲每個在 IOC 容器裏聲明的 Bean 創建唯一一個實例, 整個 IOC 容器範圍內都能共享該實例:所有後續的 getBean() 調用和 Bean 引用都將返回這個唯一的 Bean 實例.該作用域被稱爲 singleton, 它是所有 Bean 的默認作用域. 
這裏寫圖片描述

使用外部屬性文件

在配置文件裏配置 Bean 時, 有時需要在 Bean 的配置裏混入系統部署的細節信息(例如: 文件路徑, 數據源配置信息等). 而這些部署細節實際上需要和 Bean 配置相分離 
Spring 提供了一個 PropertyPlaceholderConfigurer 的 BeanFactory 後置處理器, 這個處理器允許用戶將 Bean 配置的部分內容外移到屬性文件中. 可以在 Bean 配置文件裏使用形式爲 ${var}的變量, PropertyPlaceholderConfigurer 從屬性文件里加載屬性, 並使用這些屬性來替換變量. 
Spring 還允許在屬性文件中使用${propName},以實現屬性之間的相互引用。

註冊 PropertyPlaceholderConfigurer

Spring 2.0:

這裏寫圖片描述

Spring 2.5 之後:

可通過<context:property-placeholder>元素簡化: 
<beans>中添加 context Schema 定義 
在配置文件中加入如下配置: 
這裏寫圖片描述

Spring表達式語言:SpEL

Spring 表達式語言(簡稱SpEL):是一個支持運行時查詢和操作對象圖的強大的表達式語言。 
語法類似於 EL:SpEL 使用 #{…}作爲定界符,所有在大框號中的字符都將被認爲是 SpEL 
SpEL 爲 bean 的屬性進行動態賦值提供了便利 
通過 SpEL 可以實現: 
1. 通過 bean 的 id 對 bean 進行引用 
2. 調用方法以及引用對象中的屬性 
3. 計算表達式的值 
4. 正則表達式的匹配

SpEL:字面量

字面量的表示: 
整數:<property name="count" value="#{5}"/> 
小數:<property name="frequency" value="#{89.7}"/> 
科學計數法:<property name="capacity" value="#{1e4}"/> 
String可以使用單引號或者雙引號作爲字符串的定界符號:<property name=“name” value="#{'Chuck'}"/> 或 <property name='name' value='#{"Chuck"}'/> 
Boolean:<property name="enabled" value="#{false}"/>

SpEL:引用 Bean、屬性和方法

  1. 引用其他對象: 
    這裏寫圖片描述
  2. 引用其他對象的屬性 
    這裏寫圖片描述
  3. 調用其他方法,還可以鏈式操作 
    這裏寫圖片描述
    這裏寫圖片描述
  4. 調用靜態方法或靜態屬性:通過 T() 調用一個類的靜態方法,它將返回一個 Class Object,然後再調用相應的方法或屬性: 
    這裏寫圖片描述

SpEL支持的運算符號

  1. 算數運算符:+, -, *, /, %, ^: 
    這裏寫圖片描述
  2. 加號還可以用作字符串連接: 
    這裏寫圖片描述
  3. 比較運算符: <, >, ==, <=, >=, lt, gt, eq, le, ge 
    這裏寫圖片描述
  4. 邏輯運算符號: and, or, not, | 
    這裏寫圖片描述
  5. if-else 運算符:?: (ternary), ?: (Elvis) 
    這裏寫圖片描述
  6. if-else 的變體 
    這裏寫圖片描述
  7. 正則表達式:matches 
    這裏寫圖片描述

IOC 容器中 Bean 的生命週期方法

Spring IOC 容器可以管理 Bean 的生命週期, Spring 允許在 Bean 生命週期的特定點執行定製的任務. 
Spring IOC 容器對 Bean 的生命週期進行管理的過程: 
1. 通過構造器或工廠方法創建 Bean 實例 
2. 爲 Bean 的屬性設置值和對其他 Bean 的引用 
3. 調用 Bean 的初始化方法 
4. Bean 可以使用了 
5. 當容器關閉時, 調用 Bean 的銷燬方法 
在 Bean 的聲明裏設置 init-method 和 destroy-method 屬性, 爲 Bean 指定初始化和銷燬方法.

創建 Bean 後置處理器

Bean 後置處理器允許在調用初始化方法前後對 Bean 進行額外的處理. 
Bean 後置處理器對 IOC 容器裏的所有 Bean 實例逐一處理, 而非單一實例. 其典型應用是: 檢查 Bean 屬性的正確性或根據特定的標準更改 Bean 的屬性. 
對Bean 後置處理器而言, 需要實現 
這裏寫圖片描述 
接口. 在初始化方法被調用前後, Spring 將把每個 Bean 實例分別傳遞給上述接口的以下兩個方法: 
這裏寫圖片描述

添加 Bean 後置處理器後 Bean 的生命週期

後置處理器實現類

@Component
public class MyBeanProcesser implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanProcesser BeforeInitialization" + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("MyBeanProcesser AfterInitialization" + beanName);
        return bean;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

Spring IOC 容器對 Bean 的生命週期進行管理的過程: 
1. 通過構造器或工廠方法創建 Bean 實例 
2. 爲 Bean 的屬性設置值和對其他 Bean 的引用 
3. 將 Bean 實例傳遞給 Bean 後置處理器的 postProcessBeforeInitialization 方法 
4. 調用 Bean 的初始化方法 
5. 將 Bean 實例傳遞給 Bean 後置處理器的 postProcessAfterInitialization方法 
6. Bean 可以使用了 
7. 當容器關閉時, 調用 Bean 的銷燬方法

通過工廠創建Bean

通過調用靜態工廠方法創建 Bean

調用靜態工廠方法創建 Bean是將對象創建的過程封裝到靜態方法中. 當客戶端需要對象時, 只需要簡單地調用靜態方法, 而不同關心創建對象的細節. 
要聲明通過靜態方法創建的 Bean, 需要在 Bean 的 class 屬性裏指定擁有該工廠的方法的類, 同時在 factory-method 屬性裏指定工廠方法的名稱. 最後, 使用<constrctor-arg> 元素爲該方法傳遞方法參數. 
STEP1 準備需要構造的Bean,以及靜態工廠類

public class Phone {
    private int id;
    private int stock;

    public Phone(int id, int stock) {
        this.id = id;
        this.stock = stock;
    }
    getter setter...
    @Override
    public String toString() {
        return "Phone{" +
                "id=" + id +
                ", stock=" + stock +
                '}';
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
/**
 * 靜態工程方法,不需要實例化,直接調用某一個類的靜態方法就可以返回Bean的實例
 */
public class BeanStaticFactory {

    private static Map<String,Object> m;

    static {
        if (m == null)
            m = new HashMap<>();
        m.put("iphone",new Phone(1,300));
        m.put("nokia",new Phone(3,10));
    }

    public static Object getBeanByName(String beanName){
        return m.get(beanName);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

STEP2 配置文件

    <!--
        通過靜態工廠方法來配置Bean,注意不是配置靜態工廠方法的實例,而是配置bean的實例
        class屬性 指向靜態工廠方法的全類名
        factory-method  指向靜態工廠方法的方法名
        如果靜態方法有參數,則通過constructor方法傳入 factory-method的參數.
    -->
    <bean id="phone"
          class="com.iboray.spring4test.factoryBean.BeanStaticFactory"
          factory-method="getBeanByName"
          >
        <constructor-arg value="iphone"></constructor-arg>
    </bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

測試

ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");

        Phone phone = (Phone)act.getBean("phone");

        System.out.println("The Phone is : "+ phone);
  • 1
  • 2
  • 3
  • 4
  • 5

通過調用實例工廠方法創建 Bean

STEP1 準備Bean(同上)以及實例工廠

/**
 * 實例工廠方法
 *
 */
public class InstanceFactory {

    private static Map<String,Object> m;

    public InstanceFactory() {
        if (m == null)
            m = new HashMap<>();
        m.put("iphone",new Phone(1,300));
        m.put("nokia",new Phone(3,10));
    }

    public Object getBeanByName(String beanName){
        return m.get(beanName);
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

STEP2 配置文件

<!--
        通過實例工廠方法來配置Bean,首先需要實例化工廠類
        class屬性 指向實例工廠方法的全類名
    -->
    <bean id="instanceFactory"
          class="com.iboray.spring4test.factoryBean.InstanceFactory" >

    </bean>
    <!--
        配置Bean
        要聲明通過實例工廠方法創建的 Bean
        在 bean 的 factory-bean 屬性裏指定擁有該工廠方法的 Bean
        在 factory-method 屬性裏指定該工廠方法的名稱
        使用 construtor-arg 元素爲工廠方法傳遞方法參數
    -->
    <bean id="nokia" factory-bean="instanceFactory"
          factory-method="getBeanByName">
        <constructor-arg value="nokia"></constructor-arg>
    </bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

測試

    public static void main(String[] args){

        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");

        Phone phone = (Phone)act.getBean("nokia");

        System.out.println("The Phone is : "+ phone);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

實現 FactoryBean 接口在 Spring IOC 容器中配置 Bean

STEP1 準備自定義工廠類

/**
 * 自定義的BeanFactory需要實現spring提供的FactoryBean
 */
public class PhoneFactory implements FactoryBean<Phone> {

    private int phoneId;

    public void setPhoneId(int phoneId) {
        this.phoneId = phoneId;
    }

    //返回Bean對象
    @Override
    public Phone getObject() throws Exception {
        return new Phone(phoneId,3);
    }

    //返回Bean類型
    @Override
    public Class<?> getObjectType() {
        return Phone.class;
    }

    //是否單例
    @Override
    public boolean isSingleton() {
        return true;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

STEP2 配置文件

    <!--
        通過FactoryBean來配置Bean的實例
        class指向 FactoryBean的全類名
        property 配置FactoryBean的屬性
        但實際返回的是FactoryBean的geiObject返回的實例
    -->
    <bean id="phone" class="com.iboray.spring4test.factoryBean.PhoneFactory">
        <property name="phoneId" value="10"></property>
    </bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

測試

    public static void main(String[] args){

        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");

        Phone phone = (Phone)act.getBean("phone");

        System.out.println("The Phone is : "+ phone);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在 classpath 中掃描組件

組件掃描(component scanning): Spring 能夠從 classpath 下自動掃描, 偵測和實例化具有特定註解的組件. 
特定組件包括: 
1. @Component: 基本註解, 標識了一個受 Spring 管理的組件 
2. @Respository: 標識持久層組件 
3. @Service: 標識服務層(業務層)組件 
4. @Controller: 標識表現層組件 
對於掃描到的組件, Spring 有默認的命名策略: 使用非限定類名, 第一個字母小寫. 也可以在註解中通過 value 屬性值標識組件的名稱 
當在組件類上使用了特定的註解之後, 還需要在 Spring 的配置文件中聲明<context:component-scan> : 
base-package 屬性指定一個需要掃描的基類包,Spring 容器將會掃描這個基類包裏及其子包中的所有類. 
當需要掃描多個包時, 可以使用逗號分隔. 
如果僅希望掃描特定的類而非基包下的所有類,可使用 resource-pattern 屬性過濾特定的類,示例:

    <content:component-scan
            base-package="com.iboray.spring4test"
            resource-pattern="aop/*.class"
    />
  • 1
  • 2
  • 3
  • 4

<context:include-filter>子節點表示要包含的目標類 
<context:exclude-filter>子節點表示要排除在外的目標類 
<context:component-scan>下可以擁有若干個 <context:include-filter> 和 <context:exclude-filter>子節點 
<context:include-filter> 和 <context:exclude-filter>子節點支持多種類型的過濾表達式: 
這裏寫圖片描述

組件裝配

<context:component-scan> 元素還會自動註冊 AutowiredAnnotationBeanPostProcessor 實例, 該實例可以自動裝配具有 @Autowired 和 @Resource 、@Inject註解的屬性.

使用 @Autowired 自動裝配 Bean

@Autowired 註解自動裝配具有兼容類型的單個 Bean屬性 
構造器, 普通字段(即使是非 public), 一切具有參數的方法都可以應用@Authwired 註解 
默認情況下, 所有使用 @Authwired 註解的屬性都需要被設置. 當 Spring 找不到匹配的 Bean 裝配屬性時, 會拋出異常, 若某一屬性允許不被設置, 可以設置 @Authwired 註解的 required 屬性爲 false 
默認情況下, 當 IOC 容器裏存在多個類型兼容的 Bean 時, 通過類型的自動裝配將無法工作. 此時可以在 @Qualifier 註解裏提供 Bean 的名稱. Spring 允許對方法的入參標註 @Qualifiter 已指定注入 Bean 的名稱 
@Authwired 註解也可以應用在數組類型的屬性上, 此時 Spring 將會把所有匹配的 Bean 進行自動裝配. 
@Authwired 註解也可以應用在集合屬性上, 此時 Spring 讀取該集合的類型信息, 然後自動裝配所有與之兼容的 Bean. 
@Authwired 註解用在 java.util.Map 上時, 若該 Map 的鍵值爲 String, 那麼 Spring 將自動裝配與之 Map 值類型兼容的 Bean, 此時 Bean 的名稱作爲鍵值

使用 @Resource 或 @Inject 自動裝配 Bean

Spring 還支持 @Resource 和 @Inject 註解,這兩個註解和 @Autowired 註解的功用類似 
@Resource 註解要求提供一個 Bean 名稱的屬性,若該屬性爲空,則自動採用標註處的變量或方法名作爲 Bean 的名稱 
@Inject 和 @Autowired 註解一樣也是按類型匹配注入的 Bean, 但沒有 reqired 屬性 
建議使用 @Autowired 註解

整合多個配置文件

Spring 允許通過 將多個配置文件引入到一個文件中,進行配置文件的集成。這樣在啓動 Spring 容器時,僅需要指定這個合併好的配置文件就可以。 
import 元素的 resource 屬性支持 Spring 的標準的路徑資源 
這裏寫圖片描述

泛型依賴注入

Spring 4.x 中可以爲子類注入子類對應的泛型類型的成員變量的引用 
這裏寫圖片描述 
STEP0 準備一個類

public class User {
}
  • 1
  • 2

STEP1 準備Base的service和repository

/**
 * 聲明一個父類Repository,並標註泛型T
 */

public abstract class BaseRepository<T> {

    public void insert(){
        System.out.println("BaseRepository insert");
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
/**
 * 聲明一個父類Service,標註泛型T
 */
public abstract class BaseService<T> {


    /**
     * 這裏標註Autowired,意思是可以被子類繼承
     * 這樣子類傳入子類實體類型(User)後與Repository的同類型(User)的子類建立關聯.
     * 從而實現泛型依賴注入,實際調用的是BaseRepository的實現類UserRepository
     */
    @Autowired
    public BaseRepository<T> baseRepository;

    public void add(){
        System.out.println("baseRepository --> "+baseRepository);
        baseRepository.insert();

    };

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

STEP2 repository子類

/**
 * 標註Repository註解,納入IOC容器
 */
@Repository
public class UserRepository extends BaseRepository<User> {

    @Override
    public void insert() {
        System.out.println("UserRepository insert");
        super.insert();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

Service子類

/**
 * 標註Service註解,納入IOC容器
 */
@Service
public class MyUserServiceImpl extends  BaseService<User>  {


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

STEP3 測試

public class Test {

    public static void main(String[] args){
        ApplicationContext atx = new ClassPathXmlApplicationContext("applicationContext.xml");
        MyUserServiceImpl userService = (MyUserServiceImpl) atx.getBean("myUserServiceImpl");
        userService.add();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

結果

baseRepository --> com.iboray.spring4test.genericDI.UserRepository@b59d31
UserRepository insert
BaseRepository insert
  • 1
  • 2
  • 3

Spring AOP

問題背景

代碼混亂:越來越多的非業務需求(日誌和驗證等)加入後, 原有的業務方法急劇膨脹. 每個方法在處理核心邏輯的同時還必須兼顧其他多個關注點. 
代碼分散: 以日誌需求爲例, 只是爲了滿足這個單一需求, 就不得不在多個模塊(方法)裏多次重複相同的日誌代碼. 如果日誌需求發生變化, 必須修改所有模塊.

使用動態代理解決上述問題

代理設計模式的原理: 使用一個代理將對象包裝起來, 然後用該代理對象取代原始對象. 任何對原始對象的調用都要通過代理. 代理對象決定是否以及何時將方法調用轉到原始對象上. 
這裏寫圖片描述 
CalculatorLoggingHandler 
這裏寫圖片描述 
CalculatorValidationHandler 
這裏寫圖片描述 
測試代碼 
這裏寫圖片描述

AOP 簡介

AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方法論, 是對傳統 OOP(Object-Oriented Programming, 面向對象編程) 的補充. 
AOP 的主要編程對象是切面(aspect), 而切面模塊化橫切關注點. 
在應用 AOP 編程時, 仍然需要定義公共功能, 但可以明確的定義這個功能在哪裏, 以什麼方式應用, 並且不必修改受影響的類. 這樣一來橫切關注點就被模塊化到特殊的對象(切面)裏. 
AOP 的好處: 
每個事物邏輯位於一個位置, 代碼不分散, 便於維護和升級 
業務模塊更簡潔, 只包含核心業務代碼. 
這裏寫圖片描述

AOP 術語

切面(Aspect): 橫切關注點(跨越應用程序多個模塊的功能)被模塊化的特殊對象 
通知(Advice): 切面必須要完成的工作 
目標(Target): 被通知的對象 
代理(Proxy): 向目標對象應用通知之後創建的對象 
連接點(Joinpoint):程序執行的某個特定位置:如類某個方法調用前、調用後、方法拋出異常後等。連接點由兩個信息確定:方法表示的程序執行點;相對點表示的方位。例如 ArithmethicCalculator#add() 方法執行前的連接點,執行點爲 ArithmethicCalculator#add(); 方位爲該方法執行前的位置 
切點(pointcut):每個類都擁有多個連接點:例如 ArithmethicCalculator 的所有方法實際上都是連接點,即連接點是程序類中客觀存在的事務。AOP 通過切點定位到特定的連接點。類比:連接點相當於數據庫中的記錄,切點相當於查詢條件。切點和連接點不是一對一的關係,一個切點匹配多個連接點,切點通過 org.springframework.aop.Pointcut 接口進行描述,它使用類和方法作爲連接點的查詢條件。

AspectJ:

Java 社區裏最完整最流行的 AOP 框架. 
在 Spring2.0 以上版本中, 可以使用基於 AspectJ 註解或基於 XML 配置的 AOP

在 Spring 中啓用 AspectJ 註解支持

要在 Spring 應用中使用 AspectJ 註解, 必須在 classpath 下包含 AspectJ 類庫: aopalliance.jar、aspectj.weaver.jar 和 spring-aspects.jar 
將 aop Schema 添加到<beans>根元素中. 
要在 Spring IOC 容器中啓用 AspectJ 註解支持, 只要在 Bean 配置文件中定義一個空的 XML 元素 <aop:aspectj-autoproxy> 
當 Spring IOC 容器偵測到 Bean 配置文件中的 <aop:aspectj-autoproxy>元素時, 會自動爲與 AspectJ 切面匹配的 Bean 創建代理.

用 AspectJ 註解聲明切面

要在 Spring 中聲明 AspectJ 切面, 只需要在 IOC 容器中將切面聲明爲 Bean 實例. 當在 Spring IOC 容器中初始化 AspectJ 切面之後, Spring IOC 容器就會爲那些與 AspectJ 切面相匹配的 Bean 創建代理. 
在 AspectJ 註解中, 切面只是一個帶有 @Aspect 註解的 Java 類. 
通知是標註有某種註解的簡單的 Java 方法. 
AspectJ 支持 5 種類型的通知註解: 
1. @Before: 前置通知, 在方法執行之前執行 
2. @After: 後置通知, 在方法執行之後執行 
3. @AfterRunning: 返回通知, 在方法返回結果之後執行 
4. @AfterThrowing: 異常通知, 在方法拋出異常之後 
5. @Around: 環繞通知, 圍繞着方法執行

前置通知

前置通知:在方法執行之前執行的通知 
前置通知使用 @Before 註解, 並將切入點表達式的值作爲註解值. 
這裏寫圖片描述

利用方法簽名編寫 AspectJ 切入點表達式

最典型的切入點表達式時根據方法的簽名來匹配各種方法: 
execution * com.atguigu.spring.ArithmeticCalculator.(..): 匹配 ArithmeticCalculator 中聲明的所有方法,第一個 代表任意修飾符及任意返回值. 第二個 * 代表任意方法. .. 匹配任意數量的參數. 若目標類與接口與該切面在同一個包中, 可以省略包名. 
execution public * ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 接口的所有公有方法. 
execution public double ArithmeticCalculator.*(..): 匹配 ArithmeticCalculator 中返回 double 類型數值的方法 
execution public double ArithmeticCalculator.*(double, ..): 匹配第一個參數爲 double 類型的方法, .. 匹配任意數量任意類型的參數 
execution public double ArithmeticCalculator.*(double, double): 匹配參數類型爲 double, double 類型的方法.

合併切入點表達式

在 AspectJ 中, 切入點表達式可以通過操作符 &&, ||, ! 結合起來. 
這裏寫圖片描述

讓通知訪問當前連接點的細節

可以在通知方法中聲明一個類型爲 JoinPoint 的參數. 然後就能訪問鏈接細節. 如方法名稱和參數值. 
這裏寫圖片描述

後置通知

後置通知是在連接點完成之後執行的, 即連接點返回結果或者拋出異常的時候, 下面的後置通知記錄了方法的終止. 
一個切面可以包括一個或者多個通知. 
這裏寫圖片描述

返回通知

無論連接點是正常返回還是拋出異常, 後置通知都會執行. 如果只想在連接點返回的時候記錄日誌, 應使用返回通知代替後置通知. 
這裏寫圖片描述

在返回通知中訪問連接點的返回值

在返回通知中, 只要將 returning 屬性添加到 @AfterReturning 註解中, 就可以訪問連接點的返回值. 該屬性的值即爲用來傳入返回值的參數名稱. 
必須在通知方法的簽名中添加一個同名參數. 在運行時, Spring AOP 會通過這個參數傳遞返回值. 
原始的切點表達式需要出現在 pointcut 屬性中 
這裏寫圖片描述

異常通知

只在連接點拋出異常時才執行異常通知 
將 throwing 屬性添加到 @AfterThrowing 註解中, 也可以訪問連接點拋出的異常. Throwable 是所有錯誤和異常類的超類. 所以在異常通知方法可以捕獲到任何錯誤和異常. 
如果只對某種特殊的異常類型感興趣, 可以將參數聲明爲其他異常的參數類型. 然後通知就只在拋出這個類型及其子類的異常時才被執行. 
這裏寫圖片描述

環繞通知

環繞通知是所有通知類型中功能最爲強大的, 能夠全面地控制連接點. 甚至可以控制是否執行連接點. 
對於環繞通知來說, 連接點的參數類型必須是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 允許控制何時執行, 是否執行連接點. 
在環繞通知中需要明確調用 ProceedingJoinPoint 的 proceed() 方法來執行被代理的方法. 如果忘記這樣做就會導致通知被執行了, 但目標方法沒有被執行. 
注意: 環繞通知的方法需要返回目標方法執行之後的結果, 即調用 joinPoint.proceed(); 的返回值, 否則會出現空指針異常 
這裏寫圖片描述

引入通知

引入通知是一種特殊的通知類型. 它通過爲接口提供實現類, 允許對象動態地實現接口, 就像對象已經在運行時擴展了實現類一樣. 
這裏寫圖片描述 
引入通知可以使用兩個實現類 MaxCalculatorImpl 和 MinCalculatorImpl, 讓 ArithmeticCalculatorImpl 動態地實現 MaxCalculator 和 MinCalculator 接口. 而這與從 MaxCalculatorImpl 和 MinCalculatorImpl 中實現多繼承的效果相同. 但卻不需要修改 ArithmeticCalculatorImpl 的源代碼 
引入通知也必須在切面中聲明 
在切面中, 通過爲任意字段添加@DeclareParents 註解來引入聲明. 
註解類型的 value 屬性表示哪些類是當前引入通知的目標. value 屬性值也可以是一個 AspectJ 類型的表達式, 以將一個即可引入到多個類中. defaultImpl 屬性中指定這個接口使用的實現類 
這裏寫圖片描述

指定切面的優先級

在同一個連接點上應用不止一個切面時, 除非明確指定, 否則它們的優先級是不確定的. 
切面的優先級可以通過實現 Ordered 接口或利用 @Order 註解指定. 
實現 Ordered 接口, getOrder() 方法的返回值越小, 優先級越高. 
若使用 @Order 註解, 序號出現在註解中 
這裏寫圖片描述

重用切入點定義

在編寫 AspectJ 切面時, 可以直接在通知註解中書寫切入點表達式. 但同一個切點表達式可能會在多個通知中重複出現. 
在 AspectJ 切面中, 可以通過 @Pointcut 註解將一個切入點聲明成簡單的方法. 切入點的方法體通常是空的, 因爲將切入點定義與應用程序邏輯混在一起是不合理的. 
切入點方法的訪問控制符同時也控制着這個切入點的可見性. 如果切入點要在多個切面中共用, 最好將它們集中在一個公共的類中. 在這種情況下, 它們必須被聲明爲 public. 在引入這個切入點時, 必須將類名也包括在內. 如果類沒有與這個切面放在同一個包中, 還必須包含包名. 
其他通知可以通過方法名稱引入該切入點. 
這裏寫圖片描述

練習

通過UserService的add方法練習日誌切面和驗證切面

STEP 1 UserService及實現類UserServiceImpl

public interface UserService {
    public int add(int a,int b);

    public double div(int a,int b);

    public void send(String a);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
@Service
public class UserServiceImpl implements UserService{


    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public double div(int a, int b) {
        return a/b;
    }

    @Override
    public void send(String a) {
        System.out.println("Send Message content :"+ a);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

STEP 2 日誌切面

/**
 * 標註Aspect,聲明這是一個切面
 * Component就不多說了.是爲了load到Spring IOC容器
 *
 * 指定切面的優先級
 * 在同一個連接點上應用不止一個切面時, 除非明確指定, 否則它們的優先級是不確定的.
 * 切面的優先級可以通過實現 Ordered 接口或利用 @Order 註解指定.
 * 實現 Ordered 接口, getOrder() 方法的返回值越小, 優先級越高.
 * 若使用 @Order 註解, 序號出現在註解中
 */
@Aspect
@Component
@Order(2)
public class LoggerAop {

    /**
     * 重用切入點定義
     * 在編寫 AspectJ 切面時, 可以直接在通知註解中書寫切入點表達式. 但同一個切點表達式可能會在多個通知中重複出現.
     * 在 AspectJ 切面中, 可以通過 @Pointcut 註解將一個切入點聲明成簡單的方法.
     * 切入點的方法體通常是空的, 因爲將切入點定義與應用程序邏輯混在一起是不合理的.
     * 切入點方法的訪問控制符同時也控制着這個切入點的可見性. 如果切入點要在多個切面中共用, 最好將它們集中在一個公共的類中.
     * 在這種情況下, 它們必須被聲明爲 public. 在引入這個切入點時, 必須將類名也包括在內. 如果類沒有與這個切面放在同一個包中, 還必須包含包名.
     * 其他通知可以通過方法名稱引入該切入點.
     *
     * 合併切入點表達式
     * 在 AspectJ 中, 切入點表達式可以通過操作符 &&, ||, ! 結合起來.
     */
    @Pointcut("execution(* com.iboray.spring4test.aop.*.add(..)) " +
            "|| execution(* com.iboray.spring4test.aop.*.div(..))" +
            "|| execution(* com.iboray.spring4test.aop.*.send(..))")
    private void logginOperation(){};

    /**
     * 前置通知
     * 在方法執行之前執行的通知,前置通知使用 @Before 註解, 並將切入點表達式的值作爲註解值.
     *
     *
     * 利用方法簽名編寫 AspectJ 切入點表達式
     * 最典型的切入點表達式時根據方法的簽名來匹配各種方法:
     * execution * com.atguigu.spring.ArithmeticCalculator.*(..):
     *      匹配 ArithmeticCalculator 中聲明的所有方法,第一個 * 代表任意修飾符及任意返回值.
     *      第二個 * 代表任意方法. .. 匹配任意數量的參數. 若目標類與接口與該切面在同一個包中, 可以省略包名.
     * execution public * ArithmeticCalculator.*(..):
     *      匹配 ArithmeticCalculator 接口的所有公有方法.
     * execution public double ArithmeticCalculator.*(..):
     *      匹配 ArithmeticCalculator 中返回 double 類型數值的方法
     * execution public double ArithmeticCalculator.*(double, ..):
     *      匹配第一個參數爲 double 類型的方法, .. 匹配任意數量任意類型的參數
     * execution public double ArithmeticCalculator.*(double, double):
     *      匹配參數類型爲 double, double 類型的方法.
     *
     *
     * 讓通知訪問當前連接點的細節
     * 可以在通知方法中聲明一個類型爲 JoinPoint 的參數. 然後就能訪問鏈接細節. 如方法名稱和參數值.
     */
    @Before("execution(* com.iboray.spring4test.aop.*.*(..))")
    public void beforLog(JoinPoint joinPoint){
        Object[] o = joinPoint.getArgs();
        System.out.println("[LOG] LoggerAop.beforLog--> Method : "+ joinPoint.getSignature().getName()
                +" args : "+ Arrays.asList(o));
    }

    /**
     * 後置通知
     * 後置通知是在連接點完成之後執行的, 即連接點返回結果或者拋出異常的時候, 下面的後置通知記錄了方法的終止.
     * 一個切面可以包括一個或者多個通知.
     */
    @After("logginOperation()")
    public void afterLog(JoinPoint joinPoint){
        System.out.println("[LOG] LoggerAop.afterLog  Method :"+joinPoint.getSignature().getName() +" END!");
    }

    /**
     * 返回通知
     * 無論連接點是正常返回還是拋出異常, 後置通知都會執行.
     * 如果只想在連接點返回的時候記錄日誌, 應使用返回通知代替後置通知.
     */
    @AfterReturning("logginOperation()")
    public void afterReturning(JoinPoint joinPoint){
        System.out.println("[LOG] LoggerAop.afterReturning  Method :"+joinPoint.getSignature().getName() +" END!");
    }

    /**
     * 在返回通知中訪問連接點的返回值
     * 在返回通知中, 只要將 returning 屬性添加到 @AfterReturning 註解中, 就可以訪問連接點的返回值.
     * 該屬性的值即爲用來傳入返回值的參數名稱.
     * 必須在通知方法的簽名中添加一個同名參數. 在運行時, Spring AOP 會通過這個參數傳遞返回值.
     * 原始的切點表達式需要出現在 pointcut 屬性中
     */
    @AfterReturning(pointcut = "logginOperation()",returning = "result")
    public void afterReturningByResult(JoinPoint joinPoint,Object result){

        System.out.println("[LOG] LoggerAop.afterReturningByResult  Method :"+joinPoint.getSignature().getName() +"" +
                "    Result : "+result+ "   END!");

    }

    /**
     * 異常通知
     * 只在連接點拋出異常時才執行異常通知將 throwing 屬性添加到 @AfterThrowing 註解中,
     * 也可以訪問連接點拋出的異常. Throwable 是所有錯誤和異常類的超類. 所以在異常通知方法可以捕獲到任何錯誤和異常.
     * 如果只對某種特殊的異常類型感興趣, 可以將參數聲明爲其他異常的參數類型.
     * 然後通知就只在拋出這個類型及其子類的異常時才被執行.
     */
    @AfterThrowing(pointcut = "logginOperation()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint,Exception e){
        System.out.println("[LOG] LoggerAop.afterThrowing  Method :"+joinPoint.getSignature().getName() +"" +
                "    Exception : "+e+ "   END!");
    }

    /**
     * 環繞通知
     * 環繞通知是所有通知類型中功能最爲強大的, 能夠全面地控制連接點. 甚至可以控制是否執行連接點.
     * 對於環繞通知來說, 連接點的參數類型必須是 ProceedingJoinPoint . 它是 JoinPoint 的子接口,
     * 允許控制何時執行, 是否執行連接點.
     * 在環繞通知中需要明確調用 ProceedingJoinPoint 的 proceed() 方法來執行被代理的方法.
     * 如果忘記這樣做就會導致通知被執行了, 但目標方法沒有被執行.
     * 注意: 環繞通知的方法需要返回目標方法執行之後的結果, 即調用 joinPoint.proceed(); 的返回值, 否則會出現空指針異常
     */
    @Around("logginOperation()")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("[LOG] LoggerAop.around  Method :"+proceedingJoinPoint.getSignature().getName()+" Start...");
        try {

            proceedingJoinPoint.proceed();

            System.out.println("[LOG] LoggerAop.around  Method :"+proceedingJoinPoint.getSignature().getName()+" End...");
        } catch (Throwable throwable) {
            System.out.println("[LOG] LoggerAop.around  Method :"+proceedingJoinPoint.getSignature().getName()+" Throws Exception..." + throwable);
            throw throwable;
        }

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134

STEP3 驗證切面

@Aspect
@Component
@Order(1)
public class ValidatorAop {

    @Before("execution(* com.iboray.spring4test.aop.*.*(..))")
    public void beforValidation(JoinPoint joinPoint){

        System.out.println("[VALIDATE] ValidatorAopXml.beforValidation Method : "+ joinPoint.getSignature().getName());

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

STEP4 測試

public class Test {

    public static void main(String[] args){
/**
         * applicationContext.xml配置
         * <content:component-scan base-package="com.iboray.spring4test.aop" />
         * <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
         */
        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserService userService = (UserService)act.getBean("userServiceImpl");

        double c = userService.add(2,1);

        System.out.println("The result is : "+ c);


    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

結果

[VALIDATE] ValidatorAopXml.beforValidation Method : add
[LOG] LoggerAop.around  Method :add Start...
[LOG] LoggerAop.beforLog--> Method : add args : [2, 1]
[LOG] LoggerAop.around  Method :add End...
[LOG] LoggerAop.afterLog  Method :add END!
[LOG] LoggerAop.afterReturning  Method :add END!
[LOG] LoggerAop.afterReturningByResult  Method :add    Result : null   END!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

用基於 XML 的配置聲明切面

除了使用 AspectJ 註解聲明切面, Spring 也支持在 Bean 配置文件中聲明切面. 這種聲明是通過 aop schema 中的 XML 元素完成的. 
正常情況下, 基於註解的聲明要優先於基於 XML 的聲明. 通過 AspectJ 註解, 切面可以與 AspectJ 兼容, 而基於 XML 的配置則是 Spring 專有的. 由於 AspectJ 得到越來越多的 AOP 框架支持, 所以以註解風格編寫的切面將會有更多重用的機會.

基於 XML —- 聲明切面

當使用 XML 聲明切面時, 需要在 根元素中導入 aop Schema 
在 Bean 配置文件中, 所有的 Spring AOP 配置都必須定義在 元素內部. 對於每個切面而言, 都要創建一個 元素來爲具體的切面實現引用後端 Bean 實例. 
切面 Bean 必須有一個標示符, 供 元素引用 
這裏寫圖片描述

基於 XML —- 聲明切入點

切入點使用 元素聲明 
切入點必須定義在 元素下, 或者直接定義在 元素下. 
定義在 元素下: 只對當前切面有效 
定義在 元素下: 對所有切面都有效 
基於 XML 的 AOP 配置不允許在切入點表達式中用名稱引用其他切入點. 
這裏寫圖片描述

基於 XML —- 聲明通知

在 aop Schema 中, 每種通知類型都對應一個特定的 XML 元素. 
通知元素需要使用 來引用切入點, 或用 直接嵌入切入點表達式. method 屬性指定切面類中通知方法的名稱. 
這裏寫圖片描述

聲明引入

可以利用 元素在切面內部聲明引入 
這裏寫圖片描述

練習

STEP1 驗證切面

public class ValidatorAopXml {

    public void beforValidation(JoinPoint joinPoint){

        System.out.println("[VALIDATE] ValidatorAopXml.beforValidation Method : "+ joinPoint.getSignature().getName());

    }

    public void throwMethod(JoinPoint joinPoint,Exception e ){

        System.out.println("[VALIDATE] ValidatorAopXml.throwMethod Method : "+ joinPoint.getSignature().getName()+"" +
                " Exception"+e);

    }

    public void resultValidation(JoinPoint joinPoint,Object result){

        System.out.println("[VALIDATE] ValidatorAopXml.resultValidation Method : "+ joinPoint.getSignature().getName()+"" +
                " Result is : "+result);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

STEP2 配置文件

<!--
        基於 XML 的配置聲明切面
        除了使用 AspectJ 註解聲明切面, Spring 也支持在 Bean 配置文件中聲明切面.
        這種聲明是通過 aop schema 中的 XML 元素完成的.
        正常情況下, 基於註解的聲明要優先於基於 XML 的聲明. 通過 AspectJ 註解, 切面可以與 AspectJ 兼容,
        而基於 XML 的配置則是 Spring 專有的. 由於 AspectJ 得到越來越多的 AOP 框架支持,
        所以以註解風格編寫的切面將會有更多重用的機會.
    -->
    <!--配置通知-->
    <bean id="validatorAopXml" class="com.iboray.spring4test.aop.xml.ValidatorAopXml"></bean>

    <!--配置接口-->
    <bean id="userService" class="com.iboray.spring4test.aop.UserServiceImpl"></bean>

    <!--
        聲明切面
        當使用 XML 聲明切面時, 需要在 <beans> 根元素中導入 aop Schema
        在 Bean 配置文件中, 所有的 Spring AOP 配置都必須定義在 <aop:config> 元素內部.
        對於每個切面而言, 都要創建一個 <aop:aspect> 元素來爲具體的切面實現引用後端 Bean 實例.
        切面 Bean 必須有一個標示符, 供 <aop:aspect> 元素引用
    -->
    <!--配置AOP-->
    <aop:config>
        <!--
            聲明切入點
            切入點使用 <aop:pointcut> 元素聲明
            切入點必須定義在 <aop:aspect> 元素下, 或者直接定義在 <aop:config> 元素下.
            定義在 <aop:aspect> 元素下: 只對當前切面有效
            定義在 <aop:config> 元素下: 對所有切面都有效
            基於 XML 的 AOP 配置不允許在切入點表達式中用名稱引用其他切入點.
        -->
        <aop:pointcut id="default" expression="execution(* com.iboray.spring4test.aop.*.*(..))" />
        <aop:aspect id="vax" ref="validatorAopXml">
            <!--
                聲明通知
                在 aop Schema 中, 每種通知類型都對應一個特定的 XML 元素.
                通知元素需要使用 <pointcut-ref> 來引用切入點, 或用 <pointcut> 直接嵌入切入點表達式.
                method 屬性指定切面類中通知方法的名稱.
            -->
            <aop:before method="beforValidation" pointcut-ref="default"></aop:before>
            <aop:after-throwing method="throwMethod" pointcut-ref="default" throwing="e"></aop:after-throwing>
            <aop:after-returning method="resultValidation" pointcut-ref="default" returning="result"></aop:after-returning>
        </aop:aspect>


    </aop:config>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

測試

    public static void main(String[] args){

        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext-aop-xml.xml");

        UserService userService = (UserService)act.getBean("userService");

        double c = userService.div(2,1);

        System.out.println("The main result is : "+ c);

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

結果

[VALIDATE] ValidatorAopXml.beforValidation Method : div
[VALIDATE] ValidatorAopXml.resultValidation Method : div Result is : 2.0
The main result is : 2.0
  • 1
  • 2
  • 3

JDBC 支持

JdbcTemplate 簡介

爲了使 JDBC 更加易於使用, Spring 在 JDBC API 上定義了一個抽象層, 以此建立一個 JDBC 存取框架. 
作爲 Spring JDBC 框架的核心, JDBC 模板的設計目的是爲不同類型的 JDBC 操作提供模板方法. 每個模板方法都能控制整個過程, 並允許覆蓋過程中的特定任務. 通過這種方式, 可以在儘可能保留靈活性的情況下, 將數據庫存取的工作量降到最低.

使用 JdbcTemplate 更新數據庫

這裏寫圖片描述

使用 JdbcTemplate 查詢數據庫

這裏寫圖片描述 
這裏寫圖片描述

簡化 JDBC 模板查詢

每次使用都創建一個 JdbcTemplate 的新實例, 這種做法效率很低下. 
JdbcTemplate 類被設計成爲線程安全的, 所以可以再 IOC 容器中聲明它的單個實例, 並將這個實例注入到所有的 DAO 實例中. 
JdbcTemplate 也利用了 Java 1.5 的特定(自動裝箱, 泛型, 可變長度等)來簡化開發 
Spring JDBC 框架還提供了一個 JdbcDaoSupport 類來簡化 DAO 實現. 該類聲明瞭 jdbcTemplate 屬性, 它可以從 IOC 容器中注入, 或者自動從數據源中創建.

注入 JDBC 模板示例代碼

這裏寫圖片描述

擴展 JdbcDaoSupport 示例代碼

這裏寫圖片描述

在 JDBC 模板中使用具名參數

在經典的 JDBC 用法中, SQL 參數是用佔位符 ? 表示,並且受到位置的限制. 定位參數的問題在於, 一旦參數的順序發生變化, 就必須改變參數綁定. 
在 Spring JDBC 框架中, 綁定 SQL 參數的另一種選擇是使用具名參數(named parameter). 
具名參數: SQL 按名稱(以冒號開頭)而不是按位置進行指定. 具名參數更易於維護, 也提升了可讀性. 具名參數由框架類在運行時用佔位符取代 
具名參數只在 NamedParameterJdbcTemplate 中得到支持 
在 SQL 語句中使用具名參數時, 可以在一個 Map 中提供參數值, 參數名爲鍵 
也可以使用 SqlParameterSource 參數 
批量更新時可以提供 Map 或 SqlParameterSource 的數組 
這裏寫圖片描述

事務管理

事務管理是企業級應用程序開發中必不可少的技術, 用來確保數據的完整性和一致性. 
事務就是一系列的動作, 它們被當做一個單獨的工作單元. 這些動作要麼全部完成, 要麼全部不起作用 
事務的四個關鍵屬性(ACID) 
1. 原子性(atomicity): 事務是一個原子操作, 由一系列動作組成. 事務的原子性確保動作要麼全部完成要麼完全不起作用. 
2. 一致性(consistency): 一旦所有事務動作完成, 事務就被提交. 數據和資源就處於一種滿足業務規則的一致性狀態中. 
3. 隔離性(isolation): 可能有許多事務會同時處理相同的數據, 因此每個事物都應該與其他事務隔離開來, 防止數據損壞. 
4. 持久性(durability): 一旦事務完成, 無論發生什麼系統錯誤, 它的結果都不應該受到影響. 通常情況下, 事務的結果被寫到持久化存儲器中.

問題

必須爲不同的方法重寫類似的樣板代碼 
這段代碼是特定於 JDBC 的, 一旦選擇類其它數據庫存取技術, 代碼需要作出相應的修改

Spring 中的事務管理

作爲企業級應用程序框架, Spring 在不同的事務管理 API 之上定義了一個抽象層. 而應用程序開發人員不必瞭解底層的事務管理 API, 就可以使用 Spring 的事務管理機制. 
Spring 既支持編程式事務管理, 也支持聲明式的事務管理. 
編程式事務管理: 將事務管理代碼嵌入到業務方法中來控制事務的提交和回滾. 在編程式管理事務時, 必須在每個事務操作中包含額外的事務管理代碼. 
聲明式事務管理: 大多數情況下比編程式事務管理更好用. 它將事務管理代碼從業務方法中分離出來, 以聲明的方式來實現事務管理. 事務管理作爲一種橫切關注點, 可以通過 AOP 方法模塊化. Spring 通過 Spring AOP 框架支持聲明式事務管理.

Spring 中的事務管理器

Spring 從不同的事務管理 API 中抽象了一整套的事務機制. 開發人員不必瞭解底層的事務 API, 就可以利用這些事務機制. 有了這些事務機制, 事務管理代碼就能獨立於特定的事務技術了. 
Spring 的核心事務管理抽象是 
這裏寫圖片描述 
它爲事務管理封裝了一組獨立於技術的方法. 無論使用 Spring 的哪種事務管理策略(編程式或聲明式), 事務管理器都是必須的.

Spring 中的事務管理器的不同實現

  1. 在應用程序中只需要處理一個數據源, 而且通過 JDBC 存取 
    這裏寫圖片描述
  2. 在 JavaEE 應用服務器上用 JTA(Java Transaction API) 進行事務管理 
    這裏寫圖片描述
  3. 用 Hibernate 框架存取數據庫 
    這裏寫圖片描述

事務管理器以普通的 Bean 形式聲明在 Spring IOC 容器中

用事務通知聲明式地管理事務

事務管理是一種橫切關注點 
爲了在 Spring 2.x 中啓用聲明式事務管理, 可以通過 tx Schema 中定義的 元素聲明事務通知, 爲此必須事先將這個 Schema 定義添加到 根元素中去. 
聲明瞭事務通知後, 就需要將它與切入點關聯起來. 由於事務通知是在 元素外部聲明的, 所以它無法直接與切入點產生關聯. 所以必須在 元素中聲明一個增強器通知與切入點關聯起來. 
由於 Spring AOP 是基於代理的方法, 所以只能增強公共方法. 因此, 只有公有方法才能通過 Spring AOP 進行事務管理. 
這裏寫圖片描述

用 @Transactional 註解聲明式地管理事務

除了在帶有切入點, 通知和增強器的 Bean 配置文件中聲明事務外, Spring 還允許簡單地用 @Transactional 註解來標註事務方法. 
爲了將方法定義爲支持事務處理的, 可以爲方法添加 @Transactional 註解. 根據 Spring AOP 基於代理機制, 只能標註公有方法. 
可以在方法或者類級別上添加 @Transactional 註解. 當把這個註解應用到類上時, 這個類中的所有公共方法都會被定義成支持事務處理的. 
在 Bean 配置文件中只需要啓用 元素, 併爲之指定事務管理器就可以了. 
如果事務處理器的名稱是 transactionManager, 就可以在 元素中省略 transaction-manager 屬性. 這個元素會自動檢測該名稱的事務處理器. 
用 @Transactional 註解聲明式地管理事務配置文件示例代碼

    <context:property-placeholder location="classpath:db.properties" order="1" />
    <context:component-scan base-package="com.iboray.spring4test.tx.annotation" />
    <!--配置dataSource-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
     </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置transactionManager-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--啓用註解事務-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

事務傳播屬性

當事務方法被另一個事務方法調用時, 必須指定事務應該如何傳播. 例如: 方法可能繼續在現有事務中運行, 也可能開啓一個新事務, 並在自己的事務中運行. 
事務的傳播行爲可以由傳播屬性指定. Spring 定義了 7 種類傳播行爲.

Spring 支持的事務傳播行爲

這裏寫圖片描述

REQUIRED 傳播行爲

當 bookService 的 purchase() 方法被另一個事務方法 checkout() 調用時, 它默認會在現有的事務內運行. 這個默認的傳播行爲就是 REQUIRED. 因此在 checkout() 方法的開始和終止邊界內只有一個事務. 這個事務只在 checkout() 方法結束的時候被提交, 結果用戶一本書都買不了 
事務傳播屬性可以在 @Transactional 註解的 propagation 屬性中定義 
這裏寫圖片描述

REQUIRES_NEW 傳播行爲

另一種常見的傳播行爲是 REQUIRES_NEW. 它表示該方法必須啓動一個新事務, 並在自己的事務內運行. 如果有事務在運行, 就應該先掛起它. 
這裏寫圖片描述

在 Spring 2.x 事務通知中配置傳播屬性

在 Spring 2.x 事務通知中, 可以像下面這樣在 元素中設定傳播事務屬性 
這裏寫圖片描述

併發事務所導致的問題

當同一個應用程序或者不同應用程序中的多個事務在同一個數據集上併發執行時, 可能會出現許多意外的問題 
併發事務所導致的問題可以分爲下面三種類型: 
髒讀: 對於兩個事物 T1, T2, T1 讀取了已經被 T2 更新但 還沒有被提交的字段. 之後, 若 T2 回滾, T1讀取的內容就是臨時且無效的. 
不可重複讀:對於兩個事物 T1, T2, T1 讀取了一個字段, 然後 T2 更新了該字段. 之後, T1再次讀取同一個字段, 值就不同了. 
幻讀:對於兩個事物 T1, T2, T1 從一個表中讀取了一個字段, 然後 T2 在該表中插入了一些新的行. 之後, 如果 T1 再次讀取同一個表, 就會多出幾行.

事務的隔離級別

從理論上來說, 事務應該彼此完全隔離, 以避免併發事務所導致的問題. 然而, 那樣會對性能產生極大的影響, 因爲事務必須按順序運行. 
在實際開發中, 爲了提升性能, 事務會以較低的隔離級別運行. 
事務的隔離級別可以通過隔離事務屬性指定

Spring 支持的事務隔離級別

這裏寫圖片描述 
事務的隔離級別要得到底層數據庫引擎的支持, 而不是應用程序或者框架的支持. 
Oracle 支持的 2 種事務隔離級別:READ_COMMITED , SERIALIZABLE 
Mysql 支持 4 中事務隔離級別.

設置隔離事務屬性

用 @Transactional 註解聲明式地管理事務時可以在 @Transactional 的 isolation 屬性中設置隔離級別. 
這裏寫圖片描述 
在 Spring 2.x 事務通知中, 可以在 元素中指定隔離級別 
這裏寫圖片描述

設置回滾事務屬性

默認情況下只有未檢查異常(RuntimeException和Error類型的異常)會導致事務回滾. 而受檢查異常不會. 
事務的回滾規則可以通過 @Transactional 註解的 rollbackFor 和 noRollbackFor 屬性來定義. 這兩個屬性被聲明爲 Class[] 類型的, 因此可以爲這兩個屬性指定多個異常類. 
1. rollbackFor: 遇到時必須進行回滾 
2. noRollbackFor: 一組異常類,遇到時必須不回滾 
這裏寫圖片描述 
在 Spring 2.x 事務通知中, 可以在 元素中指定回滾規則. 如果有不止一種異常, 用逗號分隔. 
這裏寫圖片描述

超時和只讀屬性

由於事務可以在行和表上獲得鎖, 因此長事務會佔用資源, 並對整體性能產生影響. 
如果一個事物只讀取數據但不做修改, 數據庫引擎可以對這個事務進行優化. 
超時事務屬性: 事務在強制回滾之前可以保持多久. 這樣可以防止長期運行的事務佔用資源. 
只讀事務屬性: 表示這個事務只讀取數據但不更新數據, 這樣可以幫助數據庫引擎優化事務.

設置超時和只讀事務屬性

超時和只讀屬性可以在 @Transactional 註解中定義.超時屬性以秒爲單位來計算. 
這裏寫圖片描述 
在 Spring 2.x 事務通知中, 超時和只讀屬性可以在 元素中進行指定. 
這裏寫圖片描述

練習-註解方式

STEP1 準備數據庫及實體類

//手機
public class Phone {
    private int id; //手機編號
    private int stock; //庫存
    getter setter...
}
//餘額
public class Balance {
    private int id; 
    private BigDecimal money; //餘額
    getter setter...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

STEP2 手機和餘額的repository

@Repository
public class BalanceRepository  {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void sell(Integer id,BigDecimal m){
        System.out.println("正在計算扣款....");
        String sql="UPDATE balance SET money = ( money - ? ) WHERE id = ?";
        Object[] objects = new Object[]{m,id};
        int result = jdbcTemplate.update(sql,objects);
        if (result == 1){
            System.out.println("賬戶已扣款");
        }
    }

}

@Repository
public class PhoneRepository {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void buy(Integer id){
        int stock = calculationStock(id);
        Thread t  = Thread.currentThread();
        try {
            t.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String sql="UPDATE phone SET stock = ( stock - 1 ) WHERE id = ?";
        Object[] objects = new Object[]{id};
        int result = jdbcTemplate.update(sql,objects);
        if (result == 1){
            System.out.println("庫存已減少");
        }
    }
    public int calculationStock(Integer bookId){
        System.out.println("正在覈算庫存....");
        String sql = "SELECT stock FROM phone where id = ?";
        Integer stock = 0;
        try {
            stock = jdbcTemplate.queryForObject(sql,new Object[]{bookId},Integer.class);
        } catch (DataAccessException e) {
            throw new StockException("庫存異常");
        }
        System.out.println("ID爲 : [ "+bookId+" ] 的庫存爲:"+stock);
        if (stock == 0)
            throw new StockException("庫存異常");
        return stock;
    }


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

STEP3 service

public interface BalanceService {

    /**
     *
     * @param balanceId 賬戶ID
     * @param bookId 圖書ID
     * @param money 圖書價格
     */
    public void selling(int balanceId, int bookId, BigDecimal money);
}

@Service(value = "balanceService")
public class BalanceServiceImpl implements BalanceService {

    @Autowired
    private BalanceRepository balanceRepository;

    @Autowired
    private PhoneRepository phoneRepository;

    @Transactional(propagation = Propagation.REQUIRED
            ,isolation = Isolation.READ_COMMITTED
            ,timeout = 2)
    @Override
    public void selling(int balanceId,int bookId, BigDecimal money) {

        //先扣錢
        balanceRepository.sell(balanceId,money);

        //再交貨
        phoneRepository.buy(bookId);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

STEP4 自定義異常

public class StockException extends RuntimeException {

    public StockException(String message) {
        super(message);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

STEP5 配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       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-4.1.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">

    <context:property-placeholder location="classpath:db.properties" order="1" />
    <context:component-scan base-package="com.iboray.spring4test.tx.annotation" />
    <!--配置dataSource-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
     </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置transactionManager-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--啓用註解事務-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

STEP6 測試 
數據庫情況 
phone表 
這裏寫圖片描述 
balance表 
這裏寫圖片描述

測試1 正常情況

public static void main(String[] args){
        ApplicationContext act = new ClassPathXmlApplicationContext("applicationContext-tx-annotation.xml");
        BalanceService balanceService = (BalanceService) act.getBean("balanceService");
        balanceService.selling(1,2,new BigDecimal(30));
    }
  • 1
  • 2
  • 3
  • 4
  • 5

執行結果

正在計算扣款....
賬戶已扣款
正在覈算庫存....
ID爲 : [ 2 ] 的庫存爲:4
庫存已減少
  • 1
  • 2
  • 3
  • 4
  • 5

數據庫 
這裏寫圖片描述

情況2 異常情況

//將這裏的圖書ID換爲沒有庫存的ID
        balanceService.selling(1,1,new BigDecimal(30));
  • 1
  • 2

執行結果

正在計算扣款....
賬戶已扣款
正在覈算庫存....
ID爲 : [ 1 ] 的庫存爲:0
Exception in thread "main" com.iboray.spring4test.tx.xml.StockException: 庫存異常
  • 1
  • 2
  • 3
  • 4
  • 5

數據庫 
這裏寫圖片描述

練習-XML方式

說明:數據庫,實體類,repository,service接口類和測試類都一樣。 
STEP1 
將BalanceServiceImpl中事務註解去掉,修改爲

@Service(value = "balanceService")
public class BalanceServiceImpl implements BalanceService {

    @Autowired
    private BalanceRepository balanceRepository;

    @Autowired
    private PhoneRepository phoneRepository;

    @Override
    public void selling(int balanceId,int bookId, BigDecimal money) {

        //先扣錢
        balanceRepository.sell(balanceId,money);

        //再交貨
        phoneRepository.buy(bookId);

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

STEP2 修改配置文件

    <context:property-placeholder location="classpath:db.properties" order="1" />
    <context:component-scan base-package="com.iboray.spring4test.tx.xml" />
    <!--配置dataSource-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
     </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置transactionManager-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置advice-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!--配置aspect-->
    <aop:config>
        <aop:pointcut id="default" expression="execution(* com.iboray.spring4test.tx.xml.service.*.*(..))" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="default"></aop:advisor>
    </aop:config>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

測試正常和異常情況均和註解測試結果一致。

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