Spring初級學習筆記

1、Spring簡介

1.1 Spring是什麼

Spring 是一個開源框架.java

Spring 爲簡化企業級應用開發而生. 使用 Spring 可使簡單的 JavaBean 實現之前只有 EJB 才能實現的功能.正則表達式

Spring 是一個 IOC(DI) 和 AOP 容器框架.spring

具體描述 Spring:sql

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

1.2 Spring模塊

image

2、Spring中的Bean配置

2.1 IOC和DI

IOC(Inversion of Control):其思想是反轉資源獲取的方向. 傳統的資源查找方式要求組件向容器發起請求查找資源. 做爲迴應, 容器適時的返回資源. 而應用了 IOC 以後, 則是<u>容器主動地將資源推送給它所管理的組件, 組件所要作的僅是選擇一種合適的方式來接受資源</u>. 這種行爲也被稱爲查找的被動形式數據庫

DI(Dependency Injection) — IOC 的另外一種表述方式:即<u>組件以一些預先定義好的方式(例如: setter 方法)接受來自如容器的資源注入.</u> 相對於 IOC 而言,這種表述更直接。編程

IOC的演化過程設計模式

需求: 生成 HTML 或 PDF 格式的不一樣類型的報表. 數組

一、分離接口與實現 安全

image

二、採用工廠設計模式 併發

image

三、採用反轉控制

image

2.2 配置Bean

配置 bean

  • 配置形式:基於 XML 文件的方式;基於註解的方式
  • Bean 的配置方式:經過全類名(反射)、經過工廠方法(靜態工廠方法 & 實例工廠方法)、FactoryBean
  • IOC 容器 BeanFactory & ApplicationContext 概述
  • 依賴注入的方式:屬性注入;構造器注入
  • 注入屬性值細節
  • 自動轉配
  • bean 之間的關係:繼承;依賴
  • bean 的做用域:singleton;prototype;WEB 環境做用域
  • 使用外部屬性文件
  • spEL
  • IOC 容器中 Bean 的生命週期
  • Spring 4.x 新特性:泛型依賴注入

2.2.1 Spring容器

在 Spring IOC 容器讀取 Bean 配置建立 Bean 實例以前, 必須對它進行實例化. 只有在容器實例化後, 才能夠從 IOC 容器裏獲取 Bean 實例並使用.

Spring 提供了兩種類型的 IOC 容器實現.

  • BeanFactory: IOC 容器的基本實現.
  • ApplicationContext: 提供了更多的高級特性. 是 BeanFactory 的子接口.
  • BeanFactory 是 Spring 框架的基礎設施,面向 Spring 自己;ApplicationContext 面向使用 Spring 框架的開發者,幾乎全部的應用場合都直接使用 ApplicationContext 而非底層的 BeanFactory
  • 不管使用何種方式, 配置文件時相同的.

2.2.2 ApplicationContext

image

ApplicationContext 的主要實現類:

  • ClassPathXmlApplicationContext:從類路徑下加載配置文件
  • FileSystemXmlApplicationContext: 從文件系統中加載配置文件

ConfigurableApplicationContext 擴展於 ApplicationContext,新增長兩個主要方法:refresh() 和 close(), 讓 ApplicationContext 具備啓動、刷新和關閉上下文的能力

ApplicationContext 在初始化上下文時就實例化全部單例的 Bean。

WebApplicationContext 是專門爲 WEB 應用而準備的,它容許從相對於 WEB 根目錄的路徑中完成初始化工做

2.2.3 從IOC容器中獲取Bean的方法

調用 ApplicationContext 的 getBean() 方法

image

2.2.4 依賴注入的方式

  • 屬性注入

    • 屬性注入即經過 setter 方法注入Bean 的屬性值或依賴的對象
    • 屬性注入使用 <property> 元素, 使用 name 屬性指定 Bean 的屬性名稱,value 屬性或 <value> 子節點指定屬性值
    • 屬性注入是實際應用中最經常使用的注入方式
  • 構造器注入

    • 經過構造方法注入Bean 的屬性值或依賴的對象,它保證了 Bean 實例在實例化後就可使用。
    • 構造器注入在 <constructor-arg> 元素裏聲明屬性, <constructor-arg> 中沒有 name 屬性

2.2.5 注入屬性值的細節

  • 字面值

    • 字面值:可用字符串表示的值,能夠經過 <value> 元素標籤或 value 屬性進行注入。
    • 基本數據類型及其封裝類、String 等類型均可以採起字面值注入的方式
    • 若字面值中包含特殊字符,可使用 <![CDATA[]]> 把字面值包裹起來。
  • 引用其餘Bean

    • ref引用

image

    • 內部Bean

      • 當 Bean 實例僅僅給一個特定的屬性使用時, 能夠將其聲明爲內部 Bean. 內部 Bean 聲明直接包含在 <property> 或 <constructor-arg> 元素裏, 不須要設置任何 id 或 name 屬性
      • 內部 Bean 不能使用在任何其餘地方,即不能被外部引用
    • 注入參數詳解:null 值和級聯屬性

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

      • List及Set

        • 在 Spring中能夠經過一組內置的 xml 標籤(例如: <list>, <set> 或 <map>) 來配置集合屬性.
        • 配置 java.util.List 類型的屬性, 須要指定 <list> 標籤, 在標籤裏包含一些元素. 這些標籤能夠經過 <value> 指定簡單的常量值, 經過 <ref> 指定對其餘 Bean 的引用. 經過<bean> 指定內置 Bean 定義. 經過 <null/> 指定空元素. 甚至能夠內嵌其餘集合.
        • 數組的定義和 List 同樣, 都使用 <list>
        • 配置 java.util.Set 須要使用 <set> 標籤, 定義元素的方法與 List 同樣.
      • 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. 須要注意的是, 必須在 <beans> 根元素裏添加 util schema 定義
    • 使用 p 命名空間

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

    2.2.6 自動裝配

    • XML 配置裏的 Bean 自動裝配

      • Spring IOC 容器能夠自動裝配 Bean. 須要作的僅僅是在 <bean> 的 autowire 屬性裏指定自動裝配的模式
      • byType(根據類型自動裝配): 若 IOC 容器中有多個與目標 Bean 類型一致的 Bean. 在這種狀況下, Spring 將沒法斷定哪一個 Bean 最合適該屬性, 因此不能執行自動裝配.
      • byName(根據名稱自動裝配): 必須將目標 Bean 的名稱和屬性名設置的徹底相同.
      • constructor(經過構造器自動裝配): 當 Bean 中存在多個構造器時, 此種自動裝配方式將會很複雜. 不推薦使用
    • XML 配置裏的 Bean 自動裝配的缺點

      • 在 Bean 配置文件裏設置 autowire 屬性進行自動裝配將會裝配 Bean 的全部屬性. 然而, 若只但願裝配個別屬性時, autowire 屬性就不夠靈活了.
      • autowire 屬性要麼根據類型自動裝配, 要麼根據名稱自動裝配, 不能二者兼而有之.
      • 通常狀況下,在實際的項目中不多使用自動裝配功能,由於和自動裝配功能所帶來的好處比起來,明確清晰的配置文檔更有說服力一些

    2.2.7 Bean之間的關係:繼承、依賴

    • 繼承 Bean 配置

      • Spring 容許繼承 bean 的配置, 被繼承的 bean 稱爲父 bean. 繼承這個父 Bean 的 Bean 稱爲子 Bean

        <bean id="address" class="com.meituan.spring.bean.Address" p:city="BeiJing" p:street="WuDaoKou"></bean>
        
        <bean id="address2" parent="address" p:street="DaZhongSi"></bean>
      • 子 Bean 從父 Bean 中繼承配置, 包括 Bean 的屬性配置
      • 子 Bean 也能夠覆蓋從父 Bean 繼承過來的配置
      • 父 Bean 能夠做爲配置模板, 也能夠做爲 Bean 實例. 若只想把父 Bean 做爲模板, 能夠設置 <bean> 的abstract 屬性爲 true, 這樣 Spring 將不會實例化這個 Bean
      • 並非 <bean> 元素裏的全部屬性都會被繼承. 好比: autowire, abstract 等.
      • 也能夠忽略父 Bean 的 class 屬性, 讓子 Bean 指定本身的類, 而共享相同的屬性配置. 但此時 abstract 必須設爲 true
    • 依賴 Bean 配置

      • Spring 容許用戶經過 depends-on 屬性設定 Bean 前置依賴的Bean,前置依賴的 Bean 會在本 Bean 實例化以前建立好
      • 若是前置依賴於多個 Bean,則能夠經過逗號,空格或的方式配置 Bean 的名稱

    2.2.8 Bean的做用域

    • 在 Spring 中, 能夠在 <bean> 元素的 scope 屬性裏設置 Bean 的做用域.
    • 默認狀況下, Spring 只爲每一個在 IOC 容器裏聲明的 Bean 建立惟一一個實例, 整個 IOC 容器範圍內都能共享該實例:全部後續的 getBean() 調用和 Bean 引用都將返回這個惟一的 Bean 實例.該做用域被稱爲 singleton, 它是全部 Bean 的默認做用域.

      image

    2.2.9 使用外部屬性文件

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

      • Spring 2.5 以後: 可經過 <context:property-placeholder> 元素簡化:

        <beans> 中添加 context Schema 定義

        在配置文件中加入以下配置:

        image

    2.2.10 Spring表達式語言:SpEL

    • 簡介

      • Spring 表達式語言(簡稱SpEL):是一個支持運行時查詢和操做對象圖的強大的表達式語言。
      • 語法相似於 EL:SpEL 使用 #{…} 做爲定界符,全部在大框號中的字符都將被認爲是 SpEL
      • SpEL 爲 bean 的屬性進行動態賦值提供了便利
      • 經過 SpEL 能夠實現:

        • 經過 bean 的 id 對 bean 進行引用
        • 調用方法以及引用對象中的屬性
        • 計算表達式的值
        • 正則表達式的匹配
    • 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、屬性和方法

      • 引用其餘對象:

        image

      • 引用其餘對象的屬性

        image

      • 調用其餘方法,還能夠鏈式操做 image

        image

      • 調用靜態方法或靜態屬性:經過 T() 調用一個類的靜態方法,它將返回一個 Class Object,而後再調用相應的方法或屬性:

        image

    • SpEL還支持運算符號

    2.2.11 IOC 容器中 Bean 的生命週期

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

      • Spring IOC 容器能夠管理 Bean 的生命週期, Spring 容許在 Bean 生命週期的特定點執行定製的任務.
      • Spring IOC 容器對 Bean 的生命週期進行管理的過程:

        • 經過構造器或工廠方法建立 Bean 實例
        • 爲 Bean 的屬性設置值和對其餘 Bean 的引用
        • 調用 Bean 的初始化方法
        • Bean 可使用了
        • 當容器關閉時, 調用 Bean 的銷燬方法
      • 在 Bean 的聲明裏設置 init-methoddestroy-method 屬性, 爲 Bean 指定初始化和銷燬方法.
    • 建立 Bean 後置處理器

      • Bean 後置處理器容許在調用初始化方法先後對 Bean 進行額外的處理.
      • Bean 後置處理器對 IOC 容器裏的全部 Bean 實例逐一處理, 而非單一實例. 其典型應用是: 檢查 Bean 屬性的正確性或根據特定的標準更改 Bean 的屬性.
      • 對Bean 後置處理器而言, 須要實現org.springframework.beans.factory.config.BeanPostProcessor接口. 在初始化方法被調用先後, Spring 將把每一個 Bean 實例分別傳遞給上述接口的如下兩個方法:

        image

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

      Spring IOC 容器對 Bean 的生命週期進行管理的過程:

      • 經過構造器或工廠方法建立 Bean 實例
      • 爲 Bean 的屬性設置值和對其餘 Bean 的引用
      • 將 Bean 實例傳遞給 Bean 後置處理器的 postProcessBeforeInitialization 方法
      • 調用 Bean 的初始化方法
      • 將 Bean 實例傳遞給 Bean 後置處理器的 postProcessAfterInitialization方法
      • Bean 可使用了
      • 當容器關閉時, 調用 Bean 的銷燬方法

    2.2.12 經過工廠方法配置Bean

    • 經過調用靜態工廠方法建立 Bean

      • 調用靜態工廠方法建立 Bean是將對象建立的過程封裝到靜態方法中. 當客戶端須要對象時, 只須要簡單地調用靜態方法, 而不一樣關心建立對象的細節.
      • 要聲明經過靜態方法建立的 Bean, 須要在 Bean 的 class 屬性裏指定擁有該工廠的方法的類, 同時在 factory-method屬性裏指定工廠方法的名稱. 最後, 使用 <constrctor-arg> 元素爲該方法傳遞方法參數.
      <!--
          class屬性:指向靜態工廠方法的全類名
          factory-method:指向靜態工廠方法的名字
          constructor-arg:若是工廠方法須要傳入參數,則使用constructor-arg來配置參數
      -->
      <bean id="car" class="com.meituan.spring.beans.factory.StaticCarFacotory" factory-method="getCar">
          <constructor-arg value="audi"></constructor-arg>
      </bean>
    • 經過調用實例工廠方法建立 Bean

      • 實例工廠方法: 將對象的建立過程封裝到另一個對象實例的方法裏. 當客戶端須要請求對象時, 只須要簡單的調用該實例方法而不須要關心對象的建立細節.
      • 要聲明經過實例工廠方法建立的 Bean

        • 在 bean 的 factory-bean 屬性裏指定擁有該工廠方法的 Bean
        • 在 factory-method 屬性裏指定該工廠方法的名稱
        • 使用 construtor-arg 元素爲工廠方法傳遞方法參數
        <!--配置工廠的實例-->
        <bean id="carFactory" class="com.meituan.spring.beans.facotry.InstanceCarFactory"></bean>
        
        <!--經過實例工廠方法來配置bean-->
        <!--
            factory-bean:指向實例工廠方法的bean
            factory-method:指向靜態工廠方法的名字
            constructor-arg:若是工廠方法須要傳入參數,則使用constructor-arg來配置參數
        -->
        <bean id="car" factory-bean="carFactory" factory-method="getCar">
            <constructor-arg value="ford"></constructor-arg>
        </bean>

    2.2.13 經過註解配置bean

    • 在 classpath 中掃描組件

      • 組件掃描(component scanning): Spring 可以從 classpath 下自動掃描, 偵測和實例化具備特定註解的組件.
      • 特定組件包括:

        • @Component: 基本註解, 標識了一個受 Spring 管理的組件
        • @Respository: 標識持久層組件
        • @Service: 標識服務層(業務層)組件
        • @Controller: 標識表現層組件
      • 對於掃描到的組件, Spring 有默認的命名策略: 使用非限定類名, 第一個字母小寫. 也能夠在註解中經過 value 屬性值標識組件的名稱
      • 當在組件類上使用了特定的註解以後, 還須要在 Spring 的配置文件中聲明 <context:component-scan>

        • base-package 屬性指定一個須要掃描的基類包,Spring 容器將會掃描這個基類包裏及其子包中的全部類.
        • 當須要掃描多個包時, 可使用逗號分隔.
        • 若是僅但願掃描特定的類而非基包下的全部類,可以使用 resource-pattern 屬性過濾特定的類,示例:

          image

        • <context:include-filter> 子節點表示要包含的目標類
        • <context:exclude-filter> 子節點表示要排除在外的目標類
        • <context:component-scan> 下能夠擁有若干個 <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 註解

    2.2.14 泛型依賴注入

    image

    3、AOP

    image

    需求1-日誌:在程序執行期間追蹤正在發生的活動

    需求2-驗證:但願計算器只能處理正數的運算

    image

    問題:

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

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

    代理設計模式的原理: 使用一個代理將對象包裝起來, 而後用該代理對象取代原始對象. 任何對原始對象的調用都要經過代理. 代理對象決定是否以及什麼時候將方法調用轉到原始對象上.

    image

    代理類

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    
    public class ArithmeticCalculatorLoggingProxy {
        private ArithmeticCalculator target;
    
        public void setTarget(ArithmeticCalculator target) {
            this.target = target;
        }
    
        public ArithmeticCalculatorLoggingProxy(ArithmeticCalculator target) {
            this.target = target;
        }
    
        public ArithmeticCalculator getLogginProxy() {
            ArithmeticCalculator proxy = null;
    
            //代理對象由哪個類加載器負責加載
            ClassLoader loader = target.getClass().getClassLoader();
            //代理對象的類型,即其中有哪些方法
            Class[] interfaces = new Class[]{ArithmeticCalculator.class};
            //當調用代理對象中期的方法時,該執行的代碼
            InvocationHandler h = new InvocationHandler() {
                /**
                 * @param proxy  正在返回的那個代理對象,通常狀況下,在invoke方法中ftjbn 不使用該對象
                 * @param method 正在被調用的方法
                 * @param args   調用方法時,傳入的參數
                 * @return
                 * @throws Throwable
                 */
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    String methodName = method.getName();
                    System.out.println("The method " + methodName + "() begins with " + Arrays.asList(args));
                    Object result = method.invoke(target, args);
                    System.out.println("The method " + methodName + "() ends with " + result);
                    return result;
                }
            };
    
            proxy = (ArithmeticCalculator) Proxy.newProxyInstance(loader, interfaces, h);
    
            return proxy;
        }
    }

    測試類

    public static void main(String[] args) {
            ArithmeticCalculator target = new ArithmeticCalculatorImpl();
            ArithmeticCalculatorLoggingProxy proxy = new ArithmeticCalculatorLoggingProxy(target);
            ArithmeticCalculator logginProxy = proxy.getLogginProxy();
            logginProxy.add(1, 2);
        }

    3.2 AOP簡介

    • AOP(Aspect-Oriented Programming, 面向切面編程): 是一種新的方法論, 是對傳統 OOP(Object-Oriented Programming, 面向對象編程) 的補充.
    • AOP 的主要編程對象是切面(aspect), 而切面模塊化橫切關注點.
    • 在應用 AOP 編程時, 仍然須要定義公共功能, 但能夠明確的定義這個功能在哪裏, 以什麼方式應用, 而且沒必要修改受影響的類. 這樣一來橫切關注點就被模塊化到特殊的對象(切面)裏.
    • AOP 的好處:

      • 每一個事物邏輯位於一個位置, 代碼不分散, 便於維護和升級
      • 業務模塊更簡潔, 只包含核心業務代碼.

    image

    3.3 AOP術語

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

    3.4 Spring AOP

    • 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 建立代理.

    3.5 用 AspectJ 註解聲明切面

    • 要在 Spring 中聲明 AspectJ 切面, 只須要在 IOC 容器中將切面聲明爲 Bean 實例. 當在 Spring IOC 容器中初始化 AspectJ 切面以後, Spring IOC 容器就會爲那些與 AspectJ 切面相匹配的 Bean 建立代理.
    • 在 AspectJ 註解中, 切面只是一個帶有 @Aspect 註解的 Java 類.
    • 通知是標註有某種註解的簡單的 Java 方法.
    • AspectJ 支持 5 種類型的通知註解:

      • @Before: 前置通知, 在方法執行以前執行
      • @After: 後置通知, 在方法執行以後執行
      • @AfterRunning: 返回通知, 在方法返回結果以後執行
      • @AfterThrowing: 異常通知, 在方法拋出異常以後
      • @Around: 環繞通知, 圍繞着方法執行

    3.6 實現步驟

    一、添加依賴

    <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>4.3.13.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
                <version>4.3.2.RELEASE</version>
            </dependency>

    二、在配置文件中添加aop的命名空間

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

    三、加入配置

    <!--使AspectJ註解起做用:自動爲匹配的類生成代理對象-->
        <aop:aspectj-autoproxy/>

    四、建立切面

    • 切面首先是IOC中的bean,即加上@Component註解
    • 切面還須要加上@Aspect註解

    五、在類中聲明各類通知

    AspectJ 支持 5 種類型的通知註解:

    • @Before: 前置通知, 在方法執行以前執行
    • @After: 後置通知, 在方法執行以後執行
    • @AfterRunning: 返回通知, 在方法返回結果以後執行
    • @AfterThrowing: 異常通知, 在方法拋出異常以後
    • @Around: 環繞通知, 圍繞着方法執行

    之前置通知爲例:

    //把這個類聲明爲一個切面
    @Component
    @Aspect
    public class LoggingAspect {
    
        //聲明該方法是一個前置通知:在目標方法開始以前執行
        @Before("execution(public int com.meituan.spring.aop.helloworld.ArithmeticCalculatorImpl.*(int, int))")
        public void beforeMethod(JoinPoint joinPoint) {
            Signature signature = joinPoint.getSignature();
            List<Object> args = Arrays.asList(joinPoint.getArgs());
            System.out.println("The method " + signature.getName()  + args + " begins");
        }
    }

    3.7 後置通知、返回通知、異常通知、環繞通知

    一個切面能夠包括一個或者多個通知.

    • 後置通知

      後置通知是在鏈接點完成以後執行的, 即鏈接點返回結果或者拋出異常的時候, 下面的後置通知記錄了方法的終止.

      後置通知不能訪問目標方法執行的結果

    • 返回通知

      • 不管鏈接點是正常返回仍是拋出異常, 後置通知都會執行. 若是隻想在鏈接點返回的時候記錄日誌, 應使用返回通知代替後置通知.
      • 在返回通知中訪問鏈接點的返回值

        • 在返回通知中, 只要將 returning 屬性添加到 @AfterReturning 註解中, 就能夠訪問鏈接點的返回值. 該屬性的值即爲用來傳入返回值的參數名稱.
        • 必須在通知方法的簽名中添加一個同名參數. 在運行時, Spring AOP 會經過這個參數傳遞返回值.
        • 原始的切點表達式須要出如今 pointcut 屬性中
    • 異常通知

      • 只在鏈接點拋出異常時才執行異常通知
      • 將 throwing 屬性添加到 @AfterThrowing 註解中, 也能夠訪問鏈接點拋出的異常. Throwable 是全部錯誤和異常類的超類. 因此在異常通知方法能夠捕獲到任何錯誤和異常.
      • 若是隻對某種特殊的異常類型感興趣, 能夠將參數聲明爲其餘異常的參數類型. 而後通知就只在拋出這個類型及其子類的異常時才被執行.

        image

    • 環繞通知

      • 環繞通知是全部通知類型中功能最爲強大的, 可以全面地控制鏈接點. 甚至能夠控制是否執行鏈接點.
      • 對於環繞通知來講, 鏈接點的參數類型必須是 ProceedingJoinPoint . 它是 JoinPoint 的子接口, 容許控制什麼時候執行, 是否執行鏈接點.
      • 在環繞通知中須要明確調用 ProceedingJoinPoint 的 proceed() 方法來執行被代理的方法. 若是忘記這樣作就會致使通知被執行了, 但目標方法沒有被執行.
      • 注意: 環繞通知的方法須要返回目標方法執行以後的結果, 即調用 joinPoint.proceed(); 的返回值, 不然會出現空指針異常

        image

    3.8 切面的優先級

    • 在同一個鏈接點上應用不止一個切面時, 除非明確指定, 不然它們的優先級是不肯定的.
    • 切面的優先級能夠經過實現 Ordered 接口或利用 @Order 註解指定.
    • 實現 Ordered 接口, getOrder() 方法的返回值越小, 優先級越高.
    • 若使用 @Order 註解, 序號出如今註解中

    3.9 重用切入點定義

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

      image

    4、Spring對JDBC的支持

    4.1 JdbcTemplate 簡介

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

    4.2 簡化JDBC模板查詢

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

    4.3 注入JDBC模板示例代碼

    image

    4.4 在 JDBC 模板中使用具名參數

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

      image

    5、Spring中的事務管理

    5.1 事務簡介

    • 事務管理是企業級應用程序開發中必不可少的技術, 用來確保數據的完整性和一致性.
    • 事務就是一系列的動做, 它們被當作一個單獨的工做單元. 這些動做要麼所有完成, 要麼所有不起做用
    • 事務的四個關鍵屬性(ACID)

      • 原子性(atomicity): 事務是一個原子操做, 由一系列動做組成. 事務的原子性確保動做要麼所有完成要麼徹底不起做用.
      • 一致性(consistency): 一旦全部事務動做完成, 事務就被提交. 數據和資源就處於一種知足業務規則的一致性狀態中.
      • 隔離性(isolation): 可能有許多事務會同時處理相同的數據, 所以每一個事物都應該與其餘事務隔離開來, 防止數據損壞.
      • 持久性(durability): 一旦事務完成, 不管發生什麼系統錯誤, 它的結果都不該該受到影響. 一般狀況下, 事務的結果被寫到持久化存儲器中.

    5.2 事務管理中的問題

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

      image

    獲取鏈接、開啓事務==》前置通知

    提交事務==》返回通知

    回滾事務==》異常通知

    finally部分==》後置通知

    5.3 Spring的事務管理

    • 做爲企業級應用程序框架, Spring 在不一樣的事務管理 API 之上定義了一個抽象層. 而應用程序開發人員沒必要了解底層的事務管理 API, 就可使用 Spring 的事務管理機制.
    • Spring 既支持1)編程式事務管理(即編程), 也支持2)聲明式的事務管理(即配置文件).
    • 編程式事務管理: 將事務管理代碼嵌入到業務方法中來控制事務的提交和回滾. 在編程式管理事務時, 必須在每一個事務操做中包含額外的事務管理代碼.
    • 聲明式事務管理: <u>大多數狀況下比編程式事務管理更好用</u>. 它將事務管理代碼從業務方法中分離出來, 以聲明的方式來實現事務管理. 事務管理做爲一種橫切關注點, 能夠經過 AOP 方法模塊化. Spring 經過 Spring AOP 框架支持聲明式事務管理.

    5.3 Spring中的事務管理器

    • Spring 從不一樣的事務管理 API 中抽象了一整套的事務機制. 開發人員沒必要了解底層的事務 API, 就能夠利用這些事務機制. 有了這些事務機制, 事務管理代碼就能獨立於特定的事務技術了.
    • Spring 的核心事務管理抽象是org.springframework.transaction.PlatformTransactionManager;它爲事務管理封裝了一組獨立於技術的方法. 不管使用 Spring 的哪一種事務管理策略(編程式或聲明式), 事務管理器都是必須的.

    5.4 實現步驟

    1. 配置事務管理器

      <bean id="transactionManager" 
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource"></property>
      </bean>
    2. 啓動事務註解

      <tx:annotation-driven transaction-manager="transactionManager" />
    3. 在對應的方法上添加註解@Transactional

    5.5 事務的傳播行爲

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

    image

    • 使用方法

      @Transactional(propagation=Propagation.REQUIRED)

    5.5.1 REQUIRED 傳播行爲

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

    image

    5.5.2 REQUIRES_NEW 傳播行爲

    另外一種常見的傳播行爲是 REQUIRES_NEW. 它表示該方法必須啓動一個新事務, 並在本身的事務內運行. 若是有事務在運行, 就應該先掛起它.

    image

    5.6 事務的隔離級別

    5.6.1 併發事務所致使的問題

    • 當同一個應用程序或者不一樣應用程序中的多個事務在同一個數據集上併發執行時, 可能會出現許多意外的問題
    • 併發事務所致使的問題能夠分爲下面三種類型:

      • 髒讀: 對於兩個事物 T1, T2, T1 讀取了已經被 T2 更新但 尚未被提交的字段. 以後, 若 T2 回滾, T1讀取的內容就是臨時且無效的.
      • 不可重複讀:對於兩個事物 T1, T2, T1 讀取了一個字段, 而後 T2 更新了該字段. 以後, T1再次讀取同一個字段, 值就不一樣了.
      • 幻讀:對於兩個事物 T1, T2, T1 從一個表中讀取了一個字段, 而後 T2 在該表中插入了一些新的行. 以後, 若是 T1 再次讀取同一個表, 就會多出幾行.

    5.6.2 隔離級別

    • 從理論上來講, 事務應該彼此徹底隔離, 以免併發事務所致使的問題. 然而, 那樣會對性能產生極大的影響, 由於事務必須按順序運行.
    • 在實際開發中, 爲了提高性能, 事務會以較低的隔離級別運行.
    • 事務的隔離級別能夠經過隔離事務屬性指定

    5.6.3 Spring 支持的事務隔離級別

    image

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

    5.6.4 設置隔離事務屬性

    用 @Transactional 註解聲明式地管理事務時能夠在 @Transactional 的 isolation 屬性中設置隔離級別.

    image

    5.6.5 設置回滾事務屬性

    • 默認狀況下只有未檢查異常(RuntimeException和Error類型的異常)會致使事務回滾. 而受檢查異常不會.
    • 事務的回滾規則能夠經過 @Transactional 註解的 rollbackFor 和 noRollbackFor 屬性來定義. 這兩個屬性被聲明爲 Class[] 類型的, 所以能夠爲這兩個屬性指定多個異常類.

      • rollbackFor: 遇到時必須進行回滾
      • noRollbackFor: 一組異常類,遇到時必須不回滾

    5.7 超時和只讀屬性

    • 因爲事務能夠在行和表上得到鎖, 所以長事務會佔用資源, 並對總體性能產生影響.
    • 若是一個事物只讀取數據但不作修改, 數據庫引擎能夠對這個事務進行優化.
    • 超時事務屬性: 事務在強制回滾以前能夠保持多久. 這樣能夠防止長期運行的事務佔用資源.
    • 只讀事務屬性: 表示這個事務只讀取數據但不更新數據, 這樣能夠幫助數據庫引擎優化事務.
    相關文章
    相關標籤/搜索