天天看點

Java架構之Spring 02-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屬性中定義。

Java架構之Spring 02-AOP-動态代理-AspectJ-JdbcTemplate-事務

  ①REQUIRED傳播行為

    當一個事務方法調用另一個事務方法時,它預設會在現有的事務内運作。是以在整個事務方法的開始和終止邊界内隻有一個事務。即:如果目前存在事務,就使用目前事務。如果目前沒事務,就建立一個新的事務,去使用。

  ②. REQUIRES_NEW傳播行為

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

事務的隔離級别

  • 一個事務與其他事務隔離的程度稱為隔離級别。資料庫規定了多種事務隔離級别, 不同隔離級别對應不同的幹擾程度, 隔離級别越高, 資料一緻性就越好, 但并發性越弱。主要為避免各種并發問題。
  • 資料庫提供的 4 種事務隔離級别:
Java架構之Spring 02-AOP-動态代理-AspectJ-JdbcTemplate-事務

  讀未送出(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>