1. 程式人生 > >Java框架之Spring02-AOP-動態代理-AspectJ-JdbcTemplate-事務

Java框架之Spring02-AOP-動態代理-AspectJ-JdbcTemplate-事務

AOP

動態代理

  代理設計模式的原理:使用一個代理將原本物件包裝起來,然後用該代理物件”取代”原始物件。任何對原始物件的呼叫都要通過代理。代理物件決定是否以及何時將方法呼叫轉到原始物件上。

代理模式的三要素:

  • 代理主題介面

  • 代理者

  • 被代理者

代理模式的主要優點

  • 代理模式在客戶端與目標物件之間起到一箇中介作用和保護目標物件的作用;

  • 代理物件可以擴充套件目標物件的功能;

  • 代理模式能將客戶端與目標物件分離,在一定程度上降低了系統的耦合度;

其主要缺點

  • 在客戶端和目標物件之間增加一個代理物件,會造成請求處理速度變慢;

  • 增加了系統的複雜度;

動態代理的方式

   靜態代理類只能替一個主題介面進行代理工作

       基於介面實現動態代理: JDK動態代理

       基於繼承實現動態代理: Cglib、Javassist動態代理

JDK動態代理步驟:
* 1、編寫主題介面
* 2、編寫被代理類
* 3、編寫代理工作處理器:即代理類要替被代理類做什麼事情(有參構造器)
* 要求:必須實現InvocationHandler,重寫
* Object invoke(Object proxy, Method method, Object[] args)
* 第一個引數:代理類物件
* 第二個引數:被代理類和代理類 要執行的方法
* 第三個引數:要執行方法的實參列表
* 這個invoke方法不是程式設計師呼叫,當代理類物件執行對應的代理方法時,自動呼叫的
* 4、建立代理類及其物件
* 需要:Proxy:提供用於建立動態代理類和例項的靜態方法,它還是由這些方法建立的所有動態代理類的超類。
* static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
* 第一個引數:被代理類的類載入器,我們希望被代理和代理類使用同一個類載入器
* 第二個引數:被代理類實現的介面們
* 第三個引數:代理工作處理器物件
* 5、呼叫被代理的方法

  注意:代理物件和實現類物件,都實現了相同的介面。屬於兄弟關係。(不能強制轉換為,實現類物件)

AOP概述

  1) AOP(Aspect-Oriented Programming,面向切面程式設計):是一種新的方法論,是對傳統 OOP(ObjectOrientedProgramming),面向物件程式設計)的補充。

  面向物件  縱向繼承機制

  面向切面  橫向抽取機制

  2) AOP程式設計操作的主要物件是切面(aspect),而切面用於橫切關注點。
  3) 在應用AOP程式設計時,仍然需要定義公共功能,但可以明確的定義這個功能應用在哪裡,以什麼方式應用,並且不必修改受影響的類。這樣一來橫切關注點就被模組化到特殊的類裡——這樣的類我們通常稱之為“切面”。

    4) AOP的好處

    ① 每個事物邏輯位於一個位置,程式碼不分散,便於維護和升級

    ② 業務模組更簡潔,只包含核心業務程式碼

AOP術語

1.橫切關注點
  從每個方法中抽取出來的同一類非核心業務。
2.切面(Aspect)
  封裝橫切關注點資訊的類,每個關注點體現為一個通知方法。
3.通知(Advice)
  切面必須要完成的各個具體工作
4.目標(Target)
  被通知的物件
5.代理(Proxy)
  向目標物件應用通知之後建立的代理物件
6. 連線點(Joinpoint)
  橫切關注點在程式程式碼中的具體體現,對應程式執行的某個特定位置。例如:類某個方法呼叫前、呼叫後、方法捕獲到異常後等。
7. 切入點(pointcut):

  定位連線點的方式。每個類的方法中都包含多個連線點,所以連線點是類中客觀存在的事物。
  如果把連線點看作資料庫中的記錄,那麼切入點就是查詢條件——AOP可以通過切入點定位到特定的連線點。
  切點通過org.springframework.aop.Pointcut 介面進行描述,它使用類和方法作為連線點的查詢條件。

AspectJ

 啟用AspectJ註解支援

1、匯入JAR包

2、引入aop名稱空間

3、配置:<aop:aspectj-autoproxy> 

  當Spring IOC容器偵測到bean配置檔案中的<aop:aspectj-autoproxy>元素時,會自動為與AspectJ切面匹配的bean建立代理

 用AspectJ註解宣告切面

     在Spring中宣告AspectJ切面為bean例項

  初始化AspectJ切面之後,容器就會為那些與 AspectJ切面相匹配的bean建立代理

  在AspectJ註解中,切面只是一個帶有@Aspect註解的Java類

  通知是標註有某種註解的Java方法

  5種類型的通知註解:@Before(value="切入點表示式")

    ① @Before:前置通知,在方法執行之前執行

    ② @After:後置通知,在方法執行之後執行,即無論連線點是正常返回還是丟擲異常,後置通知都會執行

    ③ @AfterRunning:返回通知,在方法返回結果之後執行   (如果異常,不執行 )

      在返回通知中訪問連線點的返回值,如果只想在連線點返回的時候記錄日誌,應使用返回通知代替後置通知

      ①在返回通知中,只要將returning屬性新增到@AfterReturning註解中,就可以訪問連線點的返回值。

      ②必須在通知方法的簽名中新增一個同名引數。在執行時Spring AOP會通過這個引數傳遞返回值

      ③原始的切點表示式需要出現在pointcut屬性中

    ④ @AfterThrowing:異常通知,在方法丟擲異常之後執行  (如果無異常,不執行)

      將throwing屬性新增到@AfterThrowing註解中,在異常通知方法可以捕獲到任何錯誤和異常。

      也可以將引數宣告為其他異常的引數型別。然後通知就只在丟擲這個型別及其子類的異常時才被執行

    ⑤ @Around:環繞通知,圍繞著方法執行

      能夠全面地控制連線點,甚至可以控制是否執行連線點。

      連線點的引數型別必須是ProceedingJoinPoint。它是 JoinPoint的子介面,允許控制何時執行,是否執行連線點。

      需要明確呼叫ProceedingJoinPoint的proceed()方法來執行被代理的方法。

      注意:環繞通知的方法需要返回目標方法執行之後的結果,即呼叫 joinPoint.proceed();的返回值,否則會出現空指標異常

    @Around(value = "rePointCut()")
    public Object aroundMethod(ProceedingJoinPoint pjp) {
        Object obj = null;
        try {
            //前置通知
            System.out.println("前置通知");
            obj = pjp.proceed();    //呼叫目標物件的方法
            //返回通知
            System.out.println("返回通知,結果:" + obj);
        } catch (Throwable e) {
            //異常通知
            System.out.println("異常通知,ex:" + e);
            e.printStackTrace();
        } finally {
            //後置通知
            System.out.println("後置通知");
        }
        return obj;
    }

 

切入點表示式

通過表示式的方式定位一個或多個具體的連線點。

語法格式

execution([許可權修飾符] [返回值型別] [簡單類名/全類名] [方法名]([引數列表]))

 

表示式:    @Pointcut(value="execution(* com.spring.*.*(..))")
含義:       ArithmeticCalculator介面中宣告的所有方法。
    第一個“*”代表任意修飾符及任意返回值。
   第二個“*”代表,任意類的全類名稱|任意類名 第三個“*”代表任意方法。 “..”匹配任意數量、任意型別的引數。 若目標類、介面與該切面類在同一個包中可以省略包名。

 

切入點表示式可以通過 “&&”、“||”、“!”等操作符結合起來。

重用切入點

  在AspectJ切面中,可以通過@Pointcut註解將一個切入點宣告成簡單的方法。切入點的方法體通常是空的

  切入點方法的訪問控制符同時也控制著這個切入點的可見性。

  在引入這個切入點時,必須將類名也包括在內。如果類沒有與這個切面放在同一個包中,還必須包含包名。

  其他通知可以通過方法名稱引入該切入點

//提取表示式
@Pointcut(value="execution(* com.spring.aspectj.*.*(..))")
public void rePointCut() {}
@Before(value="rePointCut()"):當前類中重用切入點表示式

 

指定切面的優先順序

在同一個連線點上應用不止一個切面時,除非明確指定,否則它們的優先順序是不確定的

使用@Order註解,序號出現在註解中

 

@Aspect
@Order(0) //int型別,數值越小,優先順序越高。     
public class TestAspect{}

 

XML方式配置切面

  基於註解的宣告要優先於基於XML的宣告,通過AspectJ註解,切面可以與AspectJ相容,而基於XML的配置則是Spring專有的,所以不推薦

  在bean配置檔案中,所有的Spring AOP配置都必須定義在<aop:config>元素內部。對於每個切面而言,都要建立一個<aop:aspect>元素來為具體的切面實現引用後端bean例項。

  切面bean必須有一個識別符號,供<aop:aspect>元素引用。

1)宣告切入點

    切入點使用<aop:pointcut>元素宣告。

     ① 定義在<aop:aspect>元素下:只對當前切面有效

         ② 定義在<aop:config>元素下:對所有切面都有效

  基於XML的AOP配置不允許在切入點表示式中用名稱引用其他切入點

2)宣告通知

  通知元素需要使用<pointcut-ref>來引用切入點

     method屬性指定切面類中通知方法的名稱

    <aop:config>
        <aop:pointcut id="myPointcut" expression="execution(* com.spring.aspectj_xml.*.*(..))" />
        <!-- 定義日誌切面 -->
        <aop:aspect id="loggingAspect" ref="loggingAspect" order="1">
            <aop:before method="beforeMethod" pointcut-ref="myPointcut"/>
            <aop:after method="afterMethod" pointcut-ref="myPointcut"/>
            <aop:after-returning method="afterReturnMethod" returning="rs" pointcut-ref="myPointcut"/>
            <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="myPointcut"/>
        </aop:aspect>
    </aop:config>

 

JdbcTemplate

  可以將Spring的JdbcTemplate看作是一個小型的輕量級持久化層框架,JdbcTemplate類是執行緒安全的

  JdbcTemplate所需要的JAR包

    spring-jdbc-4.0.0.RELEASE.jar

    spring-orm-4.0.0.RELEASE.jar

    spring-tx-4.0.0.RELEASE.jar

   資料庫驅動和資料來源

    druid-1.1.9.jar

             mysql-connector-java-5.1.7-bin.jar

配置檔案中配置相關的bean

    <!--    引入jdbc配置檔案-->
        <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
    <!--    裝配Druid資料來源conn-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          p:url="${jdbc.url}"
          p:username="${jdbc.username}"
          p:password="${jdbc.password}"
          p:driverClassName="${jdbc.driverClass}"
    ></bean>
    <!--    通過資料來源裝配JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--    通過資料來源裝配事務管理器-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--    啟用事務管理器-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"></tx:annotation-driven>

 

持久化操作

  1) 增刪改

    update(String sql,Object... args)

  2) 批處理增刪改

    batchUpdate(String sql,List<Object[]> batchArgs)  

    Object[]封裝了SQL語句每一次執行時所需要的引數

               List集合封裝了SQL語句多次執行時的所有引數

    3)獲取單個數值型

    queryForObject(String sql,Class<T> requiredType,Object... args)

  4)獲取單個物件型別

    queryForObject(String sql,RowMapper rowMapper,Object... args)

  5)獲取多個JavaBean型別

    query(String sql,RowMapper rowMapper,Object... args)

RowMapper物件可以使用BeanPropertyRowMapper實現類:注意new物件時指定型別

事務管理

   事務就是一組由於邏輯上緊密關聯而合併成一個整體(工作單元)的多個數據庫操作,這些操作要麼都執行,要麼都不執行。

   事務的四個屬性(ACID)

     ①原子性(atomicity):“原子”的本意是“不可再分”,事務的原子性表現為一個事務中涉及到的多個操作在邏輯上缺一不可。事務的原子性要求事務中的所有操作要麼都執行,要麼都不執行。

     ②一致性(consistency):“一致”指的是資料的一致,具體是指:所有資料都處於滿足業務規則的一致性狀態。

     ③隔離性(isolation):隔離性原則要求多個事務在併發執行過程中不會互相干擾。

     ④永續性(durability):通常情況下,事務對資料的修改應該被寫入到持久化儲存器中。

程式設計式事務

使用原生的JDBC API實現事務管理是所有事務管理方式的基石,但是需要將事務管理程式碼嵌入到業務方法中,事務與業務程式碼相耦合,程式碼相對分散且混亂。所以:建議使用宣告式事務。

         ①獲取資料庫連線Connection物件

         ②取消事務的自動提交

         ③執行操作

         ④正常完成操作時手動提交事務

         ⑤執行失敗時回滾事務

         ⑥關閉相關資源

宣告式事務

  事務管理程式碼的固定模式作為一種橫切關注點,可以通過AOP方法模組化,進而藉助Spring AOP框架實現宣告式事務管理。它將事務管理程式碼從業務方法中分離出來

  Spring的核心事務管理抽象是它為事務管理封裝了一組獨立於技術的方法。無論使用Spring的哪種事務管理策略(程式設計式或宣告式),事務管理器都是必須的。開發人員可以通過配置的方式進行事務管理。

         事務管理器可以以普通的bean的形式宣告在Spring IOC容器中。

事務管理器的主要實現

  1) DataSourceTransactionManager:在應用程式中只需要處理一個數據源,而且通過JDBC存取。

  2) JtaTransactionManager:在JavaEE應用伺服器上用JTA(Java Transaction API)進行事務管理

  3) HibernateTransactionManager:用Hibernate框架存取資料庫

實現

  1) 配置檔案:如上圖

  2) 在需要進行事務控制的方法上加註解 @Transactional

    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            timeout=3,
            readOnly=false,
            noRollbackFor=RuntimeException.class)
    public void purchase(String username, String isbn) {}

 

propagation屬性詳解

事務的傳播行為

  當事務方法被另一個事務方法呼叫時,必須指定事務應該如何傳播。事務的傳播行為可以由傳播屬性指定。Spring定義了7種類傳播行為。

  事務傳播屬性通過在@Transactional註解的propagation屬性中定義。

  ①REQUIRED傳播行為

    當一個事務方法呼叫另一個事務方法時,它預設會在現有的事務內執行。因此在整個事務方法的開始和終止邊界內只有一個事務。即:如果當前存在事務,就使用當前事務。如果當前沒事務,就建立一個新的事務,去使用。

  ②. REQUIRES_NEW傳播行為

    表示該事務方法必須啟動一個新事務,並在自己的事務內執行。如果有事務在執行,就應該先掛起它。即:無論當前是否存在事務,都必須建立新事務,去使用。等新建事務執行結束後,繼續執行被掛起事務

事務的隔離級別

  • 一個事務與其他事務隔離的程度稱為隔離級別。資料庫規定了多種事務隔離級別, 不同隔離級別對應不同的干擾程度, 隔離級別越高, 資料一致性就越好, 但併發性越弱。主要為避免各種併發問題。

  • 資料庫提供的 4 種事務隔離級別:

  讀未提交(1),存在問題:髒讀
  讀已提交(2),存在問題:不可重複讀(建議使用)
  可重複讀(4),存在問題:幻讀(建議使用)
  序列化 (8),存在問題:效率低  

  用@Transactional註解宣告式地管理事務時可以在@Transactional的isolation屬性中設定隔離級別

觸發事務回滾的異常

   捕獲到RuntimeException或Error時回滾,而捕獲到編譯時異常不回滾。

       通過@Transactional 註解

         ① rollbackFor屬性:指定遇到時必須進行回滾的異常型別,可以為多個

         ② noRollbackFor屬性:指定遇到時不回滾的異常型別,可以為多個

事務的超時和只讀屬性

   超時事務屬性:事務在強制回滾之前可以保持多久。這樣可以防止長期執行的事務佔用資源。

       只讀事務屬性: 表示這個事務只讀取資料但不更新資料, 這樣可以幫助資料庫引擎優化事務。

  readOnly=true,
    true:事務只讀,一旦設定只讀屬性,該事務就不能進行:增刪改操作。
    false:設定事務為,不只讀。
  timeout=3,  設定事務的“強制回滾”時間秒。 

基於xml方式配置宣告式事務

    <!-- 配置事務切面 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.tx.component.service.BookShopServiceImpl.purchase(..))"
                      id="txPointCut"/>
        <!-- 將切入點表示式和事務屬性配置關聯到一起 -->
        <aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
    </aop:config>
    <!-- 配置基於XML的宣告式事務  -->
    <tx:advice id="myTx" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 設定具體方法的事務屬性 -->
            <tx:method name="find*" read-only="true"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="purchase"
                       isolation="READ_COMMITTED"
                       no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"
                       propagation="REQUIRES_NEW"
                       read-only="false"
                       timeout="10"/>
        </tx:attributes>
    </tx:advice>