Spring-SpringAOP原理,手寫Spring事務框架

1、Spring核心知識

Spring是一個開源框架,Spring是於2003年興起的一個輕量級的Java開發框架,由Rod Johnson在其著做Expert One-On-One J2EE Development and Design中闡述的部分理念和原型衍生而來。它是爲了解決企業應用開發的複雜性而建立的。框架的主要優點之一就是其分層架構,分層架構容許使用者選擇使用哪個組件,同時爲J2EE應用程序開發提供集成的框架。Spring使用基本的JavaBean來完成之前只可能由EJB完成的事情。然而,Spring的用途不只限於服務器端的開發。從簡單性、可測試性和鬆耦合的角度而言,任何Java應用均可以從Spring中受益。Spring的核心是控制反轉(IoC)和麪向切面(AOP)。簡單來講,Spring是一個分層的JavaSE/EEfull-stack(一站式)輕量級開源框架。java

爲何說Spring是一個一站式的輕量級開源框架呢?EE開發可分紅三層架構,針對JavaEE的三層結構,每一層Spring都提供了不一樣的解決技術。mysql

  • WEB層:SpringMVC
  • 業務層:Spring的IoC
  • 持久層:Spring的JDBCTemplate(Spring的JDBC模板,ORM模板用於整合其餘的持久層框架)

從上面的簡要介紹中,咱們要知道Spring的核心有兩部分:程序員

  • IoC:控制反轉。spring

    • 舉例來講,在以前的操做中,比方說有一個類,咱們想要調用類裏面的方法(不是靜態方法),就要建立類的對象,使用對象調用方法實現。對於Spring來講,Spring建立對象的過程,不是在代碼裏面實現的,而是交給Spring來進行配置實現的。
  • AOP:面向切面編程。

2、SpringAOP原理

一、AOP編程技術

1.一、什麼是AOP編程

AOP: Aspect Oriented Programming 面向切面編程。sql

面向切面編程(也叫面向方面):Aspect Oriented Programming(AOP),是目前軟件開發中的一個熱點。利用AOP能夠對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度下降,提升程序的可重用性,同時提升了開發的效率。數據庫

AOP是OOP的延續,是(Aspect Oriented Programming)的縮寫,意思是面向切面(方面)編程。express

主要的功能是:日誌記錄,性能統計,安全控制,事務處理,異常處理等等。編程

主要的意圖是:將日誌記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,經過對這些行爲的分離,咱們但願能夠將它們獨立到非指導業務邏輯的方法中,進而改  變這些行爲的時候不影響業務邏輯的代碼。segmentfault

能夠經過預編譯方式和運行期動態代理實如今不修改源代碼的狀況下給程序動態統一添加功能的一種技術。AOP實際是GoF設計模式的延續,設計模式孜孜不倦追求的是調用者和被調用者之間的解耦,AOP能夠說也是這種目標的一種實現。設計模式

假設把應用程序想成一個立體結構的話,OOP的利刃是縱向切入系統,把系統劃分爲不少個模塊(如:用戶模塊,文章模塊等等),而AOP的利刃是橫向切入系統,提取各個模塊可能都要重複操做的部分(如:權限檢查,日誌記錄等等)。因而可知,AOP是OOP的一個有效補充。

注意:AOP不是一種技術,其實是編程思想。凡是符合AOP思想的技術,均可以當作是AOP的實現。

Aop, aspect object programming 面向切面編程

功能: 讓關注點代碼與業務代碼分離!

關注點

關注點,重複代碼就叫作關注點;

切面

關注點造成的類,就叫切面(類)!

面向切面編程,就是指 對不少功能都有的重複的代碼抽取,再在運行的時候網業務方法上動態植入「切面類代碼」。

切入點

執行目標對象方法,動態植入切面代碼。

能夠經過切入點表達式,指定攔截哪些類的哪些方法; 給指定的類在運行的時候植入切面類代碼。

1.二、AOP底層實現原理

代理設計模式,什麼是代理模式

經過代理控制對象的訪問,能夠詳細訪問某個對象的方法,在這個方法調用處理,或調用後處理。既(AOP微實現) ,AOP核心技術面向切面編程。
file

代理模式應用場景

SpringAOP、事物原理、日誌打印、權限控制、遠程調用、安全代理 能夠隱蔽真實角色

代理的分類

  • 靜態代理(靜態定義代理類)
  • 動態代理(動態生成代理類)
  • Jdk自帶動態代理
  • Cglib 、javaassist(字節碼操做庫)

靜態代理,什麼是靜態代理

由程序員建立或工具生成代理類的源碼,再編譯代理類。所謂靜態也就是在程序運行前就已經存在代理類的字節碼文件,代理類和委託類的關係在運行前就肯定了。

靜態代理代碼

public interface IUserDao {
    void save();
}
public class UserDao implements IUserDao {
    public void save() {
        System.out.println("已經保存數據...");
    }
}
代理類
public class UserDaoProxy implements IUserDao {
    private IUserDao target;

    public UserDaoProxy(IUserDao iuserDao) {
        this.target = iuserDao;
    }

    public void save() {
        System.out.println("開啓事物...");
        target.save();
        System.out.println("關閉事物...");
    }

}

動態代理,什麼是動態代理

  1. 代理對象,不須要實現接口
  2. 代理對象的生成,是利用JDK的API,動態的在內存中構建代理對象(須要咱們指定建立代理對象/目標對象實現的接口的類型)
  3. 動態代理也叫作:JDK代理,接口代理

JDK動態代理

  • 1)原理:是根據類加載器和接口建立代理類(此代理類是接口的實現類,因此必須使用接口 面向接口生成代理,位於java.lang.reflect包下)
  • 2)實現方式:

    1. 經過實現InvocationHandler接口建立本身的調用處理器 IvocationHandler handler = new InvocationHandlerImpl(…);
    2. 經過爲Proxy類指定ClassLoader對象和一組interface建立動態代理類Class clazz = Proxy.getProxyClass(classLoader,new Class[]{…});
    3. 經過反射機制獲取動態代理類的構造函數,其參數類型是調用處理器接口類型Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});
    4. 經過構造函數建立代理類實例,此時需將調用處理器對象做爲參數被傳入Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));

缺點:jdk動態代理,必須是面向接口,目標業務類必須實現接口

// 每次生成動態代理類對象時,實現了InvocationHandler接口的調用處理器對象 
public class InvocationHandlerImpl implements InvocationHandler {
    private Object target;// 這其實業務實現類對象,用來調用具體的業務方法
    // 經過構造函數傳入目標對象
    public InvocationHandlerImpl(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;
        System.out.println("調用開始處理");
        result = method.invoke(target, args);
        System.out.println("調用結束處理");
        return result;
    }

    public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
            IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        // 被代理對象
        IUserDao userDao = new UserDao();
        InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(userDao);
        ClassLoader loader = userDao.getClass().getClassLoader();
        Class<?>[] interfaces = userDao.getClass().getInterfaces();
        // 主要裝載器、一組接口及調用處理動態代理實例
        IUserDao newProxyInstance = (IUserDao) Proxy.newProxyInstance(loader, interfaces, invocationHandlerImpl);
        newProxyInstance.save();
    }

}

CGLIB動態代理

原理:利用asm開源包,對代理對象類的class文件加載進來,經過修改其字節碼生成子類來處理。 

什麼是CGLIB動態代理

使用cglib[Code Generation Library]實現動態代理,並不要求委託類必須實現接口,底層採用asm字節碼生成框架生成代理類的字節碼

CGLIB動態代理相關代碼:

public class CglibProxy implements MethodInterceptor {
    private Object targetObject;
    // 這裏的目標類型爲Object,則能夠接受任意一種參數做爲被代理類,實現了動態代理
    public Object getInstance(Object target) {
        // 設置須要建立子類的類
        this.targetObject = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("開啓事物");
        Object result = proxy.invoke(targetObject, args);
        System.out.println("關閉事物");
        // 返回代理對象
        return result;
    }
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        UserDao userDao = (UserDao) cglibProxy.getInstance(new UserDao());
        userDao.save();
    }
}

CGLIB動態代理與JDK動態區別

java動態代理是利用反射機制生成一個實現代理接口的匿名類,在調用具體方法前調用InvokeHandler來處理。
而cglib動態代理是利用asm開源包,對代理對象類的class文件加載進來,經過修改其字節碼生成子類來處理。
Spring中。

  • 一、若是目標對象實現了接口,默認狀況下會採用JDK的動態代理實現AOP
  • 二、若是目標對象實現了接口,能夠強制使用CGLIB實現AOP
  • 三、若是目標對象沒有實現了接口,必須採用CGLIB庫,spring會自動在JDK動態代理和CGLIB之間轉換

JDK動態代理只能對實現了接口的類生成代理,而不能針對類 。
CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法 。
由於是繼承,因此該類或方法最好不要聲明成final ,final能夠阻止繼承和多態。

1.三、AOP編程使用

註解版本實現AOP:

spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="com.codeobj"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 開啓事物註解 AopLog生效 -->

</beans>

常見用法

@Aspect                            指定一個類爲切面類        
@Pointcut("execution(* com.itmayiedu.service.UserService.add(..))")  指定切入點表達式
@Before("pointCut_()")                前置通知: 目標方法以前執行
@After("pointCut_()")                後置通知:目標方法以後執行(始終執行)
@AfterReturning("pointCut_()")         返回後通知: 執行方法結束前執行(異常不執行)
@AfterThrowing("pointCut_()")            異常通知:  出現異常時候執行
@Around("pointCut_()")                環繞通知: 環繞目標方法執行

簡單示例

// 切面類
@Component
@Aspect
public class AopLog {
    // aop 編程中有幾個通知: 前置通知 後置通知 運行通知 異常通知 環繞通知

    @Before("execution(* com.codeobj.service.UserServices.add(..))")
    public void before(){
        System.out.println("前置通知 在方法以前執行");
    }
    @After("execution(* com.codeobj.service.UserServices.add(..))")
    public void after(){
        System.out.println("後置通知 在方法執行後執行");
    }
    @AfterReturning("execution(* com.codeobj.service.UserServices.add(..))")
    public void returning(){
        System.out.println("運行通知");
    }
    @AfterThrowing("execution(* com.codeobj.service.UserServices.add(..))")
    public void afterThrowing(){
        System.out.println("異常通知");
    }

    // 環繞通知在方法以前和以後處理事務
    @Around("execution(* com.codeobj.service.UserServices.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 調用方法以前執行
        System.out.println("環繞通知 調用方法以前執行");
        proceedingJoinPoint.proceed(); //代理調用方法 注意點:若是調用方法拋出異常,不會執行後面的代碼
        System.out.println("環繞通知 調用方法以後執行");
        // 調用方法以後執行
    }

}

XML方式實現AOP

Xml實現aop編程:
    1) 引入jar文件  【aop 相關jar, 4個】
    2) 引入aop名稱空間
    3)aop 配置
        * 配置切面類 (重複執行代碼造成的類)
        * aop配置
            攔截哪些方法 / 攔截到方法後應用通知代碼
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- dao 實例 -->

    <bean id="userService" class="com.codeobj.service.UserService"></bean>
    <!-- 切面類 -->
    <bean id="aop" class="com.itmayiedu.aop2.AopLog2"></bean>
    <!-- Aop配置 -->
    <aop:config>
        <!-- 定義一個切入點表達式: 攔截哪些方法 -->
        <aop:pointcut expression="execution(* com.codeobj.service.UserService.*(..))"
            id="pt" />
        <!-- 切面 -->
        <aop:aspect ref="aop">
            <!-- 環繞通知 -->
            <aop:around method="around" pointcut-ref="pt" />
            <!-- 前置通知: 在目標方法調用前執行 -->
            <aop:before method="begin" pointcut-ref="pt" />
            <!-- 後置通知: -->
            <aop:after method="after" pointcut-ref="pt" />
            <!-- 返回後通知 -->
            <aop:after-returning method="afterReturning"
                pointcut-ref="pt" />
            <!-- 異常通知 -->
            <aop:after-throwing method="afterThrowing"
                pointcut-ref="pt" />
        </aop:aspect>
    </aop:config>

</beans>
public class AopLog2 {

    // 前置通知
    public void begin() {
        System.out.println("前置通知");
    }

    //
    // 後置通知
    public void commit() {
        System.out.println("後置通知");
    }

    // 運行通知
    public void returning() {
        System.out.println("運行通知");
    }

    // 異常通知
    public void afterThrowing() {
        System.out.println("異常通知");
    }

    // 環繞通知
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("環繞通知開始");
        proceedingJoinPoint.proceed();
        System.out.println("環繞通知結束");
    }
}

AOP編程應用場景

日誌記錄,性能統計,安全控制,事務處理,異常處理

1.四、Spring事務使用

事務基本特性

  • ⑴ 原子性(Atomicity)

  原子性是指事務包含的全部操做要麼所有成功,要麼所有失敗回滾,所以事務的操做若是成功就必需要徹底應用到數據庫,若是操做失敗則不能對數據庫有任何影響。

  • ⑵ 一致性(Consistency)

 一致性是指事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態,也就是說一個事務執行以前和執行以後都必須處於一致性狀態。
  拿轉帳來講,假設用戶A和用戶B二者的錢加起來一共是5000,那麼無論A和B之間如何轉帳,轉幾回帳,事務結束後兩個用戶的錢相加起來應該還得是5000,這就是事務的一致性。

  • ⑶ 隔離性(Isolation)

  隔離性是當多個用戶併發訪問數據庫時,好比操做同一張表時,數據庫爲每個用戶開啓的事務,不能被其餘事務的操做所幹擾,多個併發事務之間要相互隔離。
  即要達到這麼一種效果:對於任意兩個併發的事務T1和T2,在事務T1看來,T2要麼在T1開始以前就已經結束,要麼在T1結束以後纔開始,這樣每一個事務都感受不到有其餘事務在併發地執行。
  關於事務的隔離性數據庫提供了多種隔離級別,稍後會介紹到。

  • ⑷ 持久性(Durability)

持久性是指一個事務一旦被提交了,那麼對數據庫中的數據的改變就是永久性的,即使是在數據庫系統遇到故障的狀況下也不會丟失提交事務的操做。
例如咱們在使用JDBC操做數據庫時,在提交事務方法後,提示用戶事務操做完成,當咱們程序執行完成直到看到提示後,就能夠認定事務以及正確提交,即便這時候數據庫出現了問題,也必需要將咱們的事務徹底執行完成,不然就會形成咱們看到提示事務處理完畢,可是數據庫由於故障而沒有執行事務的重大錯誤。

事務控制分類

編程式事務控制
本身手動控制事務,就叫作編程式事務控制。
Jdbc代碼:
    Conn.setAutoCommite(false);  // 設置手動控制事務
Hibernate代碼:
    Session.beginTransaction();    // 開啓一個事務
【細粒度的事務控制: 能夠對指定的方法、指定的方法的某幾行添加事務控制】
(比較靈活,但開發起來比較繁瑣: 每次都要開啓、提交、回滾.)

聲明式事務控制

Spring提供了對事務的管理, 這個就叫聲明式事務管理。
Spring提供了對事務控制的實現。用戶若是想用Spring的聲明式事務管理,只須要在配置文件中配置便可; 不想使用時直接移除配置。這個實現了對事務控制的最大程度的解耦。
Spring聲明式事務管理,核心實現就是基於Aop。
【粗粒度的事務控制: 只能給整個方法應用事務,不能夠對方法的某幾行應用事務。】
(由於aop攔截的是方法。)

Spring聲明式事務管理器類:
Jdbc技術:DataSourceTransactionManager
Hibernate技術:HibernateTransactionManager

3、手寫Spring事務框架

一、編程事務實現

概述
所謂編程式事務指的是經過編碼方式實現事務,即相似於JDBC編程實現事務管理。管理使用TransactionTemplate或者直接使用底層的PlatformTransactionManager。對於編程式事務管理,spring推薦使用TransactionTemplate。

二、案例

使用編程事務實現手動事務

1)使用編程事務實現,手動事務 begin、commit、rollback

// 編程事務(須要手動begin 手動回滾 手動提交)
@Component
public class TransactionUtils {
    // 獲取事務源
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    // 開啓事務
    public TransactionStatus begin(){
        // 默認的傳輸級別
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
        return transaction;
    }

    // 提交事務
    public void commit(TransactionStatus transaction){
        dataSourceTransactionManager.commit(transaction);
    }
    // 回滾事務
    public void rollback(TransactionStatus transaction){
        dataSourceTransactionManager.rollback(transaction);
    }
}

@Service
public class UserServicesImpl implements UserServices {

    @Autowired
    private UserDao userDao;

    @Autowired
    private TransactionUtils transactionUtils;

    @Override
    public void add() {


        TransactionStatus transactionStatus = null;
        try {
            // 開啓事務
            transactionStatus = transactionUtils.begin();
            userDao.add("test001", 20);
            System.out.println("#####");
            userDao.add("test002", 21);
            // 提交事務
            if(transactionUtils != null){
                transactionUtils.commit(transactionStatus);
            }
        } catch (Exception e) {
            e.printStackTrace();
            // 回滾事務
            if(transactionUtils != null){
                transactionUtils.rollback(transactionStatus);
            }

        }


    }
}

2)AOP技術封裝手動事務

// 切面類 基於手動事務
@Component
@Aspect
public class AopTransaction {
    @Autowired
    private TransactionUtils transactionUtils;

    // TransactionUtils 不要實現爲單例: 若是爲單例的話,可能會發生線程安全問題

    @AfterThrowing("execution(* com.codeobj.service.UserServices.add(..))")
    public void afterThrowing(){
        System.out.println("回滾事務");
        // 獲取當前事務 直接進行回滾
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }

    // 環繞通知在方法以前和以後處理事務
    @Around("execution(* com.codeobj.service.UserServices.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 調用方法以前執行
        System.out.println("開啓事務");
        TransactionStatus transactionStatus = transactionUtils.begin();
        proceedingJoinPoint.proceed(); //代理調用方法 注意點:若是調用方法拋出異常,不會執行後面的代碼
        System.out.println("提交事務");
        // 調用方法以後執行
        transactionUtils.commit(transactionStatus);
    }
}


@Service
public class UserServicesImpl implements UserServices {

    @Autowired
    private UserDao userDao;

    @Autowired
    private TransactionUtils transactionUtils;

    @Override
    public void add() {

        // 注意事項: 在使用spring事務的時候, service 不要try
        // 將事務異常拋出給外層aop 進行異常通知後回滾
        userDao.add("test001", 20);
        System.out.println("#####");
        userDao.add("test002", 21);
    }
}

3)使用事務注意事項

事務是程序運行若是沒有錯誤,會自動提交事物,若是程序運行發生異常,則會自動回滾。
若是使用了try捕獲異常時.必定要在catch裏面手動回滾。
事務手動回滾代碼
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

4)聲明事務實現

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="com.codeobj"></context:component-scan>
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 開啓事物註解 AopLog生效 -->

    <!-- 1. 數據源對象: C3P0鏈接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>

    <!-- 2. JdbcTemplate工具類實例 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 3.配置事務 配置事務源 -->
    <bean id="dataSourceTransactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

</beans>
我的博客 蝸牛
相關文章
相關標籤/搜索