1. 程式人生 > 實用技巧 >《精通Spring 4.x企業應用開發實戰》筆記

《精通Spring 4.x企業應用開發實戰》筆記

概述

Spring的優點

  • 方便解耦,簡化開發。通過IOC容器,使用者可以將物件之間的依賴關係交給spring控制,避免硬編碼的耦合。使用者不必再為單例模式類、屬性檔案解析這些底層的需求寫程式碼,可以更專注與上層應用。
  • AOP程式設計的支援。方便進行面向切面的程式設計。
  • 宣告式事務的支援。方便靈活地進行事務管理。
  • 方便程式測試。可以用非容易依賴的程式設計方式進行幾乎所有的測試工作。
  • 方便整合各種優秀的框架。
  • 降低Java EE API的使用難度。

Spring的架構

  1. IOC
    spring核心模組實現了IOC功能,它將類與類之間的依賴從程式碼中脫離出來,用配置檔案的方式進行依賴關係的描述,由容器負責依賴類的建立、拼接、管理、獲取等工作。BeanFactory是Spring框架的核心介面,實現容器的核心功能。

    Context模組擴充套件了BeanFactory的功能,添加了國際化、bean生命週期控制郵件服務等功能。ApplicationContext是Context模組的核心介面。

    表示式語言是統一表達式語言的一個擴充套件,該表示式語言用於查詢和管理執行期物件,支援設定物件屬性,呼叫物件方法等。該模組還提供了邏輯表示式運算等功能,可以方便地通過表示式串和IOC容器進行互動。

  2. AOP
    是進行橫切邏輯程式設計的思想。還整合了AspectJ這種AOP語言級框架。instrument允許在JVM啟動時開啟一個代理類,通過代理類在執行期間修改位元組碼,改變一個類的共功能,從而實現了AOP。

  3. 資料訪問和整合
    Spring在DAO層後面,建立了一套面向DAO層異常體系,為整合各種持久層框架提供了基礎。Spring還通過模板化技術對各種資料訪問技術進行薄層封裝,使得資料訪問過程簡化。

  4. Web及遠端操作
    該模組建立在ApplicationContext模組之上,提供了Web應用的各種工具類。將Spring容器註冊到了Web容器中。還可以整合了MVC框架。

  5. Web以遠端訪問
    Spring自己提供了一個完整的MVC框架。

  6. WebSocket
    提供了一個在Web應用中高效、雙向、即時的通訊。

Spring4.0的新特性

  • 全面支援Java 8。
  • @Lazy延遲注入,@Order注入列表進行排序。
  • 支援用Groovy定義Bean。
  • 引入了@RestController註解,方便REST開發。
  • 支援WebSocket。
  • 新增動態語言支援。
  • 多執行緒併發處理支援。

IoC容器

什麼是IoC

IoC(Inverse of Control)字面意思就是控制反轉,其中控制表示物件例項的控制權,反轉表示這種控制權轉移給了第三方,也就是spring。

IOC的注入方法有:

  • 建構函式注入
  • 屬性注入
  • 介面注入

Spring通過一個配置檔案面熟Bean及Bean之間的依賴關係,利用Java的反射機制例項化Bean並建立Bean之間的依賴關係。Spring的IoC容器在完成這些底層工作的基礎上,還提供了Bean例項快取、生命週期管理、Bean例項代理、事件釋出、欄位裝載等高階服務。

BeanFactory和ApplicationContenxt

BeanFactory是Spring框架最核心的介面,它提供了高階IOC的配置機制。ApplicationContenxt建立在BeanFactory之上,提供了個更多面嚮應用的功能,比如國際化支援和框架事件體系,更易於建立實際應用。

我們通常稱BeanFactory為IOC容器,ApplicationContenxt為應用上下文。

二者用途的劃分:BeanFactory是Spring框架的基礎設施,面向Spring本身;
ApplicationContext面向使用Spring框架的開發者。一個是底層,一個是上層。

BeanFactory

是一個通用類工廠,不同於傳統的類工廠,BeanFactory可以建立並管理各種類物件。

BeanFactory的類體系結構如下圖所示:

BeanFactory介面位於頂端,主要的方法就是getBean(String beanName),顧名思義,就是從容器中返回特定名稱的Bean。

BeanFactory初始化:

  1. XmlBeanDefinitionReader 通過Resorce裝載Spring配置資訊並啟動IOC容器;
  2. BeanFactory.getBean()方法就能從IOC容器中獲取Bean;
  3. Bean的初始化發生在第一個呼叫的時候。

ApplicationContext

如果說BeanFactory是Spring的心臟,那麼ApplicationContext就是完整的身軀。ApplicationContext由BeanFactory派生而來,提供了更多面向實際應用的功能。

ApplicationContext的類體系結構:
ApplicationContext主要的實現類是ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,前者預設從類路徑載入配置檔案,後者預設從檔案系統中狀態配置檔案。

父子容器

Spring的IOC容器可以建立父子層級關聯的容器體系,子容器可以訪問父容器的Bean,反之不行。SpringMVC中,展示層就是位於一個子容器,而持久層和業務層就位於父容器中。

Bean的生命週期

BeanFactory中Bean的生命週期

如圖所示:

其中帶星的步驟是由InstantiationAwareBeanPostProcessor和BeanPostProcessor這兩個介面實現的,它們的實現類就是後處理器,不依賴於Bean,由外部根據自己的需求實現。使用者可以通過編寫後處理器,對感興趣的Bean進行加工處理。

ApplicationContext 中 Bean 的生命週期

和上面的BeanFactory 類似,不同的是,添加了ApplicationContextAware介面。

ApplicationContext和BeanFactory另一個最大不同之處在於:前者會利用Java的反射機制自動識別配置檔案中定義的BeanPostProcessor,並自動將它們註冊到應用上下文中;而後者血藥通過程式碼中手工嗲用addBeanpostProcessor()方法進行註冊。

Bean的作用域

  • singleton
    單例模式,在容器中僅存在一個bean例項,模式人餓漢模式。
  • prototype
    每次從容器中呼叫Bean時,都會返回一個新的例項。
  • request
    每次HTTP請求都會建立一個新的Bean,該作用域僅適用於WebApplicationContext。
  • session
    同一個HTTP Session共享一個Bean,不同的Session就會用不同Bean,也是僅用於WebApplicationContext。
  • globalSession
    用一個全域性Session共用一個Bean,一般用於Portlet環境,僅適用於WebApplicationContext。

工作機制

Bean例項的組裝過程如下:

  1. 通過BeanDefinitionReader讀取Resource的配置檔案,然後將每個bean解析成一個BeanDefinition物件,並儲存到BeanDefinitionRegistry中。
  2. 容器掃描beanDefinitionRegistry中的BeanDefinition,使用Java反射機制自動是識別出Bean工廠後處理器,然後呼叫這些Bean工廠後處理器對BeanDefinition進行加工處理。處理的任務主要分為解析標籤,通過反射機制找打所有屬性編輯器的Bean並自動將它們註冊到Spring容器的屬性編輯器登錄檔中。
  3. 獲得加工後的BeanDefinition,並呼叫InstantiationStragegy著手進行Bean例項化。
  4. 在例項化Bean的時候,Spring容器通過使用BeanWrapper對Bean進行封裝,最後完成Bean屬性注入工作。
  5. 利用容器中註冊的Bean後處理器,對已經完成屬性設定的Bean進行後續加工,直接裝備出一個準備就緒的Bean。

AOP

什麼是AOP

AOP(Aspect Oriented Programing)面向切面的程式設計。就是通過橫向抽取機制,將程式中無法縱向繼承的重複性程式碼進行提取。由Spring負責將程式碼進行編織,我們只需要規定切入位置即可。AOP代理主要分為靜態代理和動態代理,靜態代理的代表為AspectJ;而動態代理則以Spring AOP為代表。

為什麼不能將程式碼提取到一個父類或者一個依賴類中呢?

  • 如果用一個父類或者新增依賴的方式,還是需要將程式碼新增到切面處,這樣有大量的程式碼重複,不利於程式碼的長期維護。
  • 核心程式碼和輔助日誌等非業務性程式碼混合在一起,非業務程式碼分散了對方法核心邏輯的注意力,影響了方法的可讀性。
  • 如果需要移除非業務性程式碼,則面臨著不小的工作量。如果需要獲取記錄更多的資訊,例如類名,則需要手工新增漏掉的類名。如果決定記錄異常,也面臨著同樣的問題,必須在很多地方重複記錄。
  • 總之,業務程式碼和非業務程式碼進行交織,不利於開發。

相關術語:

  • 連線點:程式執行中特定的位置,例如某個方法呼叫前後。
  • 切點:定位特定的連線點。
  • 增強:是織入目標類連線點的一段程式程式碼。
  • 目標物件:增加邏輯織入的目標類。
  • 引介:一種特殊的增強,它為類新增一些屬性和方法。
  • 織入: 將增強新增到目標類的具體連線點上的過程。有編譯器織入,類裝載期織入,- 動態代理織入。Spring採用動態代理織入,AspectJ採用編譯器和裝載期織入。
  • 代理:一個類被織入後,就產生了一個代理類,裡面就包含了原始類邏輯和織入邏輯。
  • 切面:切面由切點和增強組成,包括橫切邏輯和連線的定義。

AOP工具

  1. AspectJ
    是語言級的AOP實現,能夠再編譯期提供橫向程式碼的織入。有一個專用的編譯器來生成位元組碼。
  2. Spring AOP
    純Java的實現,不需要專門的編譯過程,也不需要特殊的類載入器,通過執行期間代理方式織入程式碼。
  3. AspectWerkz
    和AspectJ已經合併。擁有一種特殊的類載入器,可以再執行期間或者類載入期間織入程式碼。
  4. JBoss AOP

為什麼不直接用JDK的動態代理技術實現AOP

  • 目標類的所有方法都被新增橫向邏輯,有時候我們希望在特定的方法新增特定的邏輯。
  • 動態代理技術用硬編碼的方式指定了織入程式碼。
  • 手工編寫代理例項的建立過程,在為不同類建立代理時,需要分別編寫相應的建立程式碼,無法做到通用。

SpringAOP解決以上三點問題:

  • Spring AOP 通過切點指定哪些類的哪些方法上織入橫向邏輯。通過增強描述邏輯和方法的具體織入點(方法前,方法後)。
  • 通過切面將節點和增強組裝起來。

建立切面

Spring支援兩種方法匹配器:

  • 靜態方法匹配器
    僅對方法名和入參型別及順序進行匹配。
  • 動態方法匹配器
    還會在執行期間檢查方法入參的值。

建立切面的步驟:

  1. 編寫增強實現類,然後放入容器;
  2. 建立切面實現類及其bean,需要將增強類bean新增進去;
  3. 通過一個bean定義公共配置資訊;
  4. 將目標類放入容器,然後上一步的bean作為其父容器組合。

增強型別

  • 前置增強
  • 後置增強
  • 環繞增強
  • 異常丟擲增強
  • 引介增強,在目標類中新增一些新方法和屬性

切點型別

  • 靜態方法切點:匹配方法名和引數。
  • 動態方法切點:還會匹配方法引數的值。
  • 註解切點
  • 表示式切點
  • 流程切點:它根據程式執行堆疊的資訊檢視目標方法是否由某一個方法直接或間接發起呼叫,以此判斷是否為匹配連線點。就是將呼叫某個方法的地方作為切點。
  • 複合切點

切面型別

  • 一般切面:Advisor,僅包含一個Advice,因為Advice包含了橫切程式碼和連線點資訊,所以,Advice本身就是一個簡單的切面,只不過它代表的是橫切的連線點所有目標類的所有方法,太寬泛了,一般不會直接使用。
  • 切點切面:PointcutAdvisor,包含了Advice和Pointcut兩個類,這樣就可以通過類、方法名與i及方法方位等資訊靈活地定義切面地連線點,提供準確地切面。
  • 引介切面:IntroductionAdvisor,是對應引介增強的特殊切面。

切面類的關係如圖所示:

PointcutAdvisor有六個具體的實現類:

  • DefualtPointcutAdvisor:最常用的切面型別,可以用過任意的切點和增強定義一個切面,唯一不支援的是引介切面型別,一般可以通過擴充套件該類實現自定義切面。
  • NameMatchMethodPointcurAdvisor:通過該類可以定義按方法名定義切點的切面。
  • RegexpMethodPointcutAdvisor:對按正則表示式匹配方法名進行切點定義和切面。
  • StaticMethodMatcherPointcutAdvisor:靜態方法匹配器切點定義的切面。
  • AspectJExpressionPointcurAdvisor:用於AspectJ切點表示式定義切點的切面。
  • AspectJPointcurAdvisor:用AspectJ語法定義的切點的切面。

無法增強的問題

如果採用JDK動態代理的AOP,就必須保證要攔截的目標方法在介面中有定義,否則無法攔截。

如果曹勇CGLib動態代理,就需要保證攔截目標方法可被子類訪問,也就是目標方法不能是final或者私有。

在實際專案中存在兩個方法呼叫的問題,同時又希望它們都能夠被增強。在內部方法呼叫時,讓其通過代理類呼叫內部方法。

註解方式使用

  1. 在增強類中標註@Aspect
  2. 在需要織入的方法上增加註解,常用的有@Before @After @ Around @AfterReturning
  3. 通過Spring的xml配置檔案,將目標類、增強類和自動代理建立器Bean加入容器。如果是使用基於Schema的AOP名稱空間,就只需要新增基於aspectJ切面驅動器 <aop:aspectj-autoproxy/>,這個標籤還有一個proxy-target-class屬性,預設為false,表示使用JDK的動態代理技術,如果設定為true,那麼就會採用CGLib的動態代理技術。如果被代理的目標類沒有宣告介面,那麼就會自動使用CGLib。

目標織入篩選方法主要有四個型別:

  • 方法切點函式:通過描述目標方法的資訊定義連線點。
  • 方法入參切點函式:通過描述目標方法傳入引數的資訊定義連線點。
  • 目標類切點函式:通過描述目標類型別的資訊定義連線點。
  • 代理類切點函式:通過描述目標類的代理類的資訊定義連線點。

target和this的區別在於target不匹配通過引介切產生的代理物件。

切面不同定義方式的具體實現比較如圖所示。

工作機制

Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理。JDK動態代理通過反射來接收被代理的類,並且要求被代理的類必須實現一個介面。JDK動態代理的核心是InvocationHandler介面和Proxy類。

如果目標類沒有實現介面,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個程式碼生成的類庫,可以在執行時動態的生成某個類的子類,注意,CGLIB是通過繼承的方式做的動態代理,因此如果某個類被標記為final,那麼它是無法使用CGLIB做動態代理的。

事務管理

Spriing中對一些Bean中非執行緒安全的狀態物件採用ThreadLocal進行封裝,讓它們也成為執行緒安全的物件,這樣,有狀態的Bean就能夠一單例的方式在多執行緒中正常工作。

事務傳播機制

什麼是事務傳播?
如果我們呼叫Spring中Service介面方法,它執行在Spring的事務環境中,這個方法還會在內部呼叫其他的Service介面,這樣就會產生服務介面方法巢狀呼叫的情況,Spring通過事務傳播機制控制當前事務的如何傳播到被巢狀呼叫的目標服務介面方法中。

Spring中7中事務事務傳播行為。

使用註解配置宣告事務

除了可以用XML進行配置,當然還可以用註解,就是在Bean上新增@Transactional,對需要事務增強的介面、實現類或方法進行註解。

然後需要在Spring配置檔案中通過一行配置來通知Spring容器對標註事務的bean進行加工處理。 <tx:annotation-driven transaction-manager="managerID"/>

@Transactional預設的事務傳播行為式Propagatio_Required,事務隔離級別是Isolation_Default,讀寫事務屬性是讀寫事務,回滾設定是任何執行期間異常都會引發回滾,任何檢查型異常不會引發回滾。可以通過註解時括號中的引數進行修改。

哪些方法不能實施Spring AOP事務

基於JDK動態代理的,可以實施介面動態代理的方法只能是使用public或者public final修飾符的方法,其他的方法(包括static)不能被動態代理,也就不能被AOP增強了,就不能實施事務。

基於CGLib動態代理的,由於使用final、static、private修飾符的方法都不能被子類覆蓋,就無法實施AOP增強。

對然這些方法不能啟動新事務,但是可以外層方法的事務上下文還是可以傳播到這些方法中的。

整合其他ORM框架

Mybatis

每個MyBatis的程式都以一個SqlSessionFactory物件的例項為核心。SqlSessionFactory可以在XML配置檔案中構建例項。

在容器中配置下面的Bean就能注入batis-spring。

    <!-- 配置SqlSessionFactory物件 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 注入資料庫連線池 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 載入mybatis的配置檔案 -->
        <property name="configLocation" value="classpath:mybatis/mybatis.xml"/>

        <!-- 掃描Mapper層的配置檔案 -->
        <property name="mapperLocations" value="classpath:cn/example/mapper/*.xml"/>
    </bean>

    <!-- 啟用mybatis的介面代理開發模式(介面和Xml配置必須同名,並且在同一目錄下) -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="cn.tycoding.mapper"/>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

在mybatis的配置xml中,可以通過<setting name="mapUnderscoreToCamelCase" value="true"/>開啟駝峰規則與下劃線間的對映關係,這樣就不需要再用全限定類名來與SQL對映檔案進行對應了。

DAO層中mybatis-spring提供了模板類SqlSessionTemplate來訪問資料庫。

快取

概述

系統中不同層級的快取使用如下圖所示。

Spring提供一種可以在方法級別進行快取的快取抽象,通過使用AOP對方法進行織入,如果已經為特定方法入參執行過該方法,那麼不必執行實際方法就可以返回被快取的結果。

Spring Cache的優點:

  • 開箱即用,並提供基本的cache抽象,方便切換各種底層的cache。
  • 類似於Spring提供的資料庫事務管理,通過cache註解即可實現快取,實現快取邏輯透明,讓開發者關注業務邏輯。
  • 當事務回滾時,快取也會自動回滾。
  • 支援比較複雜的快取邏輯。
  • 提供快取程式設計的一致性抽象,方便程式碼維護。

實體類實現Serializable是一個好習慣,一般快取Java物件需要來進行序列化儲存。

註解配置方式使用

使用Spring Cach步驟

  1. 快取定義,確定需要快取的方法和快取策略,在方法上添加註解,例如@Cacheable(cacheNames="name")
  2. 在xml中配置快取。

常用的快取註解有:

  • @Cacheable
    用於在快取中新增鍵值對,如果快取中存在,那麼就會跳過方法直接返回快取資訊。
  • @CachePut
    需要執行方法並用來更新快取。
  • @CacheEvict
    從快取中移除一個值。
  • @Caching
    是一組註解,一個方法提供基於上面三個註解的陣列。

Spring MVC

架構

整體架構如圖所示:

在整個框架中,DispatcherServlet處於核心的位置,它負責協調和組織不同元件以完成請求處理並返回響應的工作。

SpringMVC處理請求的過程

  1. 客戶端發出HTTP請求,web應用伺服器收到這個請求,如果匹配DispatcherServlet的請求對映路徑,則Web容器將該請求交給DispatcherServlet;
  2. DispatcherServlet收到請求之後,根據HTTP請求資訊及handlerMapping的配置找到處理請求的處理器(Handler)。可以將Handlder看作路由控制器,將Handler作為目標主機。
  3. 當DispatcherServlet得到handlder之後,通過HandlerAdapter對Handler進行封裝,再以同一的介面卡介面呼叫Handler。
  4. 處理器完成業務邏輯的處理之後,將返回一個ModelAndView給DispatcherServlet,其中包含了檢視邏輯名和模型資料資訊。
  5. DispatcherServlet將Model資訊給ViewResolver完成邏輯檢視名到真實檢視物件的解析工作。
  6. DispatcherServlet就是用這個真實物件給ModelAndView中的模型資料進行檢視渲染。
  7. 最終客戶端得到的響應資訊可能是一個普通的HTML頁面、JSON或者是檔案。

配置DispatcherServlet

  1. 配置DispatcherServlet截獲特定的URL
    在web.xml中配置Servlet,並通過<Servlet-mapping>指定器處理的URL。
  2. 配置DispatcherServlet的屬性

基本的註解

  • 在POJO類定義標註@Controller,使得POJO稱為一個能夠處理HTTP請求的控制器。
  • 使用@RequestMapping對映請求。
  • 使用@RequestParam繫結請求引數。
  • 使用@CookieValue繫結請求中的Cookie值。
  • 使用@RequestHeader繫結報文頭部的屬性。
  • 使用@RequestBody繫結報文實體。
  • 使用@ResponseBody表示方法返回響應報文的實體。
  • 使用@RestController整合了@ResponseBody和@Controller。