spring的AOP和事務

動態代理

代理模式:給一個對象提供一個代理,並由代理對象來控制真實對象的訪問(調用者並不知道真實對象是什麼)。
代理模式分靜態代理和動態代理。這裏只討論動態代理,通俗的講,動態代理就是在不修改代碼的基礎對被代理對象進行方法的加強html

基於接口的動態代理

JDK自帶的動態代理就是基於接口的動態代理,被代理對象至少要實現一個接口,不然就沒法使用代理。底層仍是基於Java的反射來建立代理對象的。
JDK動態代理主要涉及兩個類,java.lang.reflect.Proxyjava.lang.reflect.InvocationHandler 。下面來看個例子。java

被代理類的接口IProductspring

public interface IProduct {
    void saleProduct(Float money);
}
複製代碼

被代理類Product實現上面這個接口數據庫

public class Product implements IProduct{
    @Override
    public void saleProduct(Float money) {
        System.out.println("賣出一個產品,收到金額:" + money);
    }
}
複製代碼

編寫一個客戶端類獲取一個Product的動態代理的對象,來控制被代理對象的訪問。 InvocationHandler的實現主要經過內部類實現(能夠直接使用lambda表達式更簡潔)。編程

public class Client {
    public static void main(String[] args){
        Product product = new Product();
        // 基於接口的動態代理(jdk自帶的)
        // proxyProduct就是Proxy.newProxyInstance生成的代理對象,須要強轉爲被代理對象的接口類。
        // 參數: 
        // ClassLoader:代理對象的類加載器。使用和被代理對象相同的類加載器,直接調用getClass().getClassLoader()獲取。
        // Class<?>[]:被代理對象的接口的字節碼。直接調用getClass().getInterfaces()獲取就行
        // InvocationHandler:進行代碼的加強。直接使用內部類或者new一個InvocationHandler的實現類(在實現類中進行代碼加強)
        IProduct proxyProduct = (IProduct) Proxy.newProxyInstance(product.getClass().getClassLoader()
                , product.getClass().getInterfaces(), new InvocationHandler() {
                    /** * 進行代理加強的方法 * 參數: * proxy:當前的代理對象 * method:被代理對象當前執行的方法 * args:被代理對象當前執行的方法的參數 */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object res = null;
                        // 獲取方法執行的參數
                        Float money = (Float)args[0];
                        // 判斷當前方法是不是銷售方法
                        if ("saleProduct".equals(method.getName())){
                            // 返回值與被代理對象方法的相同
                            res = method.invoke(product, money * 0.8f);
                        }
                        return res;
                    }
                });
        // 調用這個代理對象,執行被代理對象的方法。
        proxyProduct.saleProduct(1000f);
    }
}
複製代碼

基於子類的動態代理

CGLIB動態代理是一個基於ASM字節碼操做框架的代碼生成庫,它被普遍用於基於代理的AOP框架提供方法攔截。
本質上說,對於須要被代理的類,它只是動態的生成一個子類以覆蓋非final的方法(因此它不能代理final類或final方法),同時綁定鉤子回調自定義的攔截器。它比使用Java反射的JDK動態代理方法更快。
CGLIB動態代理的核心是net.sf.cglib.proxy.Enhancer類(用於建立被代理對象子類),net.sf.cglib.proxy.MethodInterceptor是最經常使用的回調類型(它是net.sf.cglib.proxy.Callback的一個子接口)。
使用前須要引入CGLIB的jar依賴。數組

<!--cglib動態代理的依賴-->
<dependency>
     <groupId>cglib</groupId>
     <artifactId>cglib</artifactId>
     <version>3.2.12</version>
</dependency>
複製代碼

下面來看一個示例(被代理對象仍是和上面的相同,這裏實現一個客戶端類)。springboot

public class Client {
    public static void main(String[] args){
        Product product = new Product();
        /** * 基於子類的動態代理(cglib) * Enhancer的create方法,經過Enhancer來建立代理對象,MethodInterceptor來加強方法 * 參數: * Class:被代理對象的字節碼 * Callback:用於加強方法。通常使用這個接口的子接口MethodInterceptor。 */
        Product proxyProduct =  (Product) Enhancer.create(product.getClass(), new MethodInterceptor() {
            /** * 執行被代理對象的任何方法都會通過此方法 * @param proxy 當前代理對象 * @param method 被代理對象要執行的方法 * @param args1 要執行的方法的參數 * 以上三個參數與基於接口的動態代理(jdk)的參數一致 * @param methodProxy 當前執行方法的代理對象 通常使用這個對象來調用原始對象的方法,由於它性能更高 * @return 與被代理對象要執行的方法的返回值一致 * @throws Throwable */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args1, MethodProxy methodProxy) throws Throwable {
                Object res = null;
                // 獲取方法執行的參數
                Float money = (Float)args1[0];
                // 判斷當前方法是不是銷售方法
                if ("saleProduct".equals(method.getName())){
                    // 修改原始方法的參數
                    args1[0] = money * 0.8f;
                    // 與JDK動態代理的一個區別,使用MethodProxy對象來調用原始對象的方法
                    res = methodProxy.invoke(product, args1);
                }
                return res;
            }
        });
        // 經過代理對象執行被代理對象的方法
        proxyProduct.saleProduct(1000f);
    }
}
複製代碼

spring AOP

spring AOP是基於動態代理實現的,spring默認使用JDK動態代理實現AOP,也能夠強制使用CGLIB。AOP是IoC的一種補充,IoC不依賴與AOP,可是AOP依賴與IoC。mybatis

AOP的一些概念

  • JoinPoint(鏈接點):指被攔截到的點。在spring AOP中,就是指那些被攔截的方法。
  • PointCut(切入點):是指篩選出的鏈接點(由於全部的切入點都是鏈接點,可是有的鏈接點並不須要進行攔截加強方法)
  • Advice(通知/加強):須要完成的工做叫作通知,就是寫的業務邏輯代碼中的公共代碼,好比事務、日誌等。通知的類型有五種。
    • Before(前置通知):在鏈接點(攔截的方法)執行前的通知。
    • AfterReturning(後置通知):在鏈接點正常完成返回後的通知。
    • AfterThrowing(異常通知):若是方法執行過程當中拋出了異常,則執行此通知。
    • After(最終通知):不管方法執行是否成功,最終都要執行這個通知。
    • Around(環繞通知):圍繞鏈接點的通知,它是最經常使用的通知,由於它能夠包括以上四種通知。
  • Aspect(切面):其實就是通知和切點的結合,通知和切點共同定義了切面的所有內容,它是幹什麼的,何時在哪執行。
  • Introduction(引入):在不改變一個現有類代碼的狀況下,爲該類添加屬性和方法,能夠在無需修改現有類的前提下,讓它們具備新的行爲和狀態。
  • Target(目標):被通知的對象。也就是須要加入額外代碼的對象,也就是真正的業務邏輯被組織織入切面。
  • Weaving(織入):切面在指定的鏈接點被織入到目標對象中,在目標對象的生命週期裏有多個點(編譯期,類加載期,運行期)能夠進行織入,spring是在運行期織入的。

@Aspectj支持

Spring 使用 AspectJ 提供的 library 解釋與 AspectJ 5 相同的註釋,用於切入點解析和匹配。可是,AOP 運行時仍然是純粹的 Spring AOP,而且不依賴於 AspectJ 編譯器或編織器。(也就是spring只是利用Aspectj來對切入點進行解析和匹配,真正的AOP的執行仍是使用Spring AOP)。app

在springboot中使用spring AOP很是的方便,只需引入一個spring支持AspectJ的starter依賴便可。框架

<dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
複製代碼

在spring中也很簡單,在引用spring AOP的基礎上再引入一個aspectjweaver依賴就行。

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.2</version>
</dependency>
複製代碼

在spring中,要使用 Java @Configuration啓用 @AspectJ 支持,還需添加@EnableAspectJAutoProxy註解(在springboot中不用)。

下面來看一個簡單的示例(基於springboot)。
先寫個Service類(包括接口和實現類)。

public interface AccountService {
    Account findById(Integer aid);
}

@Service
public class AccountServiceImpl implements AccountService {
    private static final Logger log = LoggerFactory.getLogger(AccountServiceImpl.class);
    @Override
    public Account findById(Integer aid) {
        log.info("正在執行方法");
        return accountMapper.findById(aid);
    }
}
複製代碼

一個配置類,對service中的方法進行切面。

@Aspect // 聲明切面, 這個bean將被spring自動檢測,並用於配置Spring AOP
@Component // 將這個類註冊爲一個bean,交給spring來管理(因此spring AOP依賴於IoC)
public class Logger {

    private static org.slf4j.Logger log = LoggerFactory.getLogger(Logger.class);

    /** * 通用化切入點表達式,其餘切入點能夠直接引用這個 */
    @Pointcut("execution(* tkmybatis.service.impl.*.*(..))")
    public void pointCut(){
    }

    /** * 前置通知,在切入點方法執行以前執行 */
    @Before("pointCut()")
    public void printLogBefore(){
        log.info("我在方法執行前");
    }

    /** * 後置通知,方法正常返回後執行 */
    @AfterReturning("pointCut()")
    public void printLogAfterReturning(){
        log.info("我在方法執行正常返回後");
    }

    /** * 異常通知,方法發生異常時執行 */
    @AfterThrowing("pointCut()")
    public void printLogAfterThrow(){
        log.info("我在方法執行拋出異常後");
    }

    /** * 最終通知,不管方法是否執行正常,它都會在最後執行 */
    @After("pointCut()")
    public void printLogAfter(){
        log.info("我在方法執行最後");
    }

    @Around("pointCut()")
    public Object printAround(ProceedingJoinPoint pjp){
        Object res;
        try{
            Object[] args = pjp.getArgs();
            log.info("我在方法執行前");
            res = pjp.proceed(args);
            log.info("我在方法執行正常返回後2");
            return res;
        }catch (Throwable t){
            log.info("我在方法執行拋出異常後2");
            throw new RuntimeException(t);
        }finally {
            log.info("我在方法執行最後");
        }
    }
}
複製代碼

對於切入點表達式execution(* tkmybatis.service.impl.*.*(..)) ,這裏作一下解釋。
execution表達式原本有三個部分(可見類型 返回值 鏈接點的全限定名),這裏的可見類型能夠省略。

  • *表明返回值能夠爲任何值。
  • tkmybatis.service.impl表明service實現類所在的包(進一步還能夠直接用* .來替代,表示全部包)。
  • .* 表明這個包下的全部類。下一個.* 表明這個類下的全部方法(還能夠用方法前綴來表示,好比find*)。
  • 括號裏的 .. 表明參數能夠爲任意參數,可爲多個或無。

更多高級用法詳見Spring官方文檔 Spring AOP

Spring 事務管理

Spring支持聲明式的事務管理和編程式的事務管理。聲明式事務管理使業務代碼不受污染,一個普通的POJO對象,只要加上註解就能夠得到徹底的事務支持。Spring推薦使用聲明式的事務管理。
而聲明式的事務管理就是基於Spring AOP的,其本質就是對要執行的方法進行攔截,在方法執行前加入一個事務,方法執行完成後根據狀況判斷是提交事務仍是回滾事務(拋出了異常)。

事務的隔離級別

經過org.springframework.transaction.annotation.Isolation 這個枚舉類來指定。

public enum Isolation {
    // 這是默認值,表示使用底層數據庫的默認隔離級別。對於MySQL的InnoDB存儲引擎,它是REPEATABLE_READ,其它通常的都是READ_COMMITTED
    DEFAULT(-1),
    // 跟沒有同樣,幾乎不使用。
    READ_UNCOMMITTED(1),
    // 只能讀取另外一個事務已提交的事務,能防止髒讀。也是通常數據庫的默認隔離級別。
    READ_COMMITTED(2),
    // 可重複讀(在一個事務內屢次查詢的結果相同,其它事務不可修改該查詢條件範圍內的數據,至關於加了個讀鎖)
    REPEATABLE_READ(4),
    // 全部的事務依次逐個執行,至關於串行化了,效率過低,通常也不使用。
    SERIALIZABLE(8);
}
複製代碼

事務的傳播行爲

若是在開始當前事務以前,一個事務上下文已經存在,此時有若干選項能夠指定一個事務性方法的執行行爲。org.springframework.transaction.annotation.Propagation枚舉類中定義了7表示傳播行爲的枚舉值。

public enum Propagation {

    // 若是當前存在事務,則加入該事務;若是當前沒有事務,則建立一個新的事務。
    REQUIRED(0),
    // 若是當前存在事務,則加入該事務;若是當前沒有事務,則以非事務的方式繼續運行。
    SUPPORTS(1),
    // 若是當前存在事務,則加入該事務;若是當前沒有事務,則拋出異常。
    MANDATORY(2),
    // 建立一個新的事務,若是當前存在事務,則把當前事務掛起。
    REQUIRES_NEW(3),
    // 以非事務方式運行,若是當前存在事務,則把當前事務掛起。
    NOT_SUPPORTED(4),
    // 以非事務方式運行,若是當前存在事務,則拋出異常。
    NEVER(5),
    // 若是當前存在事務,則建立一個事務做爲當前事務的嵌套事務來運行;若是當前沒有事務,則該取值等價於REQUIRED。
    NESTED(6);
}
複製代碼

聲明式事務的使用

聲明式事務能夠直接使用@Transactional註解(這裏只討論這個)來配置,固然也可使用XML配置文件。
先來看一下@Transactional註解都有啥

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    // 別名爲transactionManager,其實這兩是同一個。就是事務的名字。
    @AliasFor("transactionManager")
	String value() default "";
    // 事務的傳播行爲,默認值爲REQUIRED
    Propagation propagation() default Propagation.REQUIRED;
    // 事務的隔離級別,默認爲默認值(也就是使用底層數據庫的隔離級別)
    Isolation isolation() default Isolation.DEFAULT;
    // 超時時間,默認爲 -1,也就是沒有超時時間。
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
    // 是否只讀,默認爲false。
    boolean readOnly() default false;
    // 會觸發事務回滾的異常的字節碼數組
    Class<? extends Throwable>[] rollbackFor() default {};
    // 不會觸發事務回滾的異常的字節碼數組
    Class<? extends Throwable>[] noRollbackFor() default {};
}
複製代碼

這裏直接在實現類上使用,這個註解還能夠在接口(通常不在接口上使用)和方法上使用(可見性必須爲public,不然會被忽略)。
若是是在spring中使用,須要在配置類中加上@EnableTransactionManagement註解(springboot則不須要)。proxy-target-class屬性能夠控制動態代理的類型,若是值爲false或忽略此屬性,則使用JDK的動態代理,能夠設置爲true以強制使用CGLIB來進行動態代理(若是被代理的類沒有實現接口,將會自動使用CGLIB進行動態代理)。

@Service
@Transactional(isolation = Isolation.REPEATABLE_READ, rollbackFor = Exception.class) // 這裏觸發回滾的異常設置爲Exception
public class AccountServiceImpl implements AccountService {
}
複製代碼

參考:
CGLIB動態代理介紹
Spring AOP
Spring AOP的使用
Spring 事務管理
Spring 事務

相關文章
相關標籤/搜索