zoukankan      html  css  js  c++  java
  • 手写spring事务框架-蚂蚁课堂

    1、视频参加C:UsersAdministratorDesktop蚂蚁3期【www.zxit8.com】 0017-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Spring事务框架017-(每特教育&每特学院&蚂蚁课堂)-3期-源码分析-手写Spring事务框架

    aop最主要的目的是解决 代码复用问题,例如现在一个controller类中存在1000个方法,我们要统计每个方法的执行时间,如果给每个方法都编写代码,代码就要重复写1000次,使用aop就只需要写一次就可以了

    spring事务是基于aop的@rouand环绕通知实现的,在实际执行方法之前开启事务,在实际方法执行之后提交事务,本质是就是环绕通知的另外一种实现形式

    AOP是OOP的延续,是(Aspect Oriented Programming)的缩写,意思是面向切面(方面)编程。
      主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。
      主要的意图是:将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改 变这些行为的时候不影响业务逻辑的代码。

      可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。AOP实际是GoF设计模式的延续,设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。
    假设把应用程序想成一个立体结构的话,OOP的利刃是纵向切入系统,把系统划分为很多个模块(如:用户模块,文章模块等等),而AOP的利刃是横向切入系统,提取各个模块可能都要重复操作的部分(如:权限检查,日志记录等等)。由此可见,AOP是OOP的一个有效补充。
    注意:AOP不是一种技术,实际上是编程思想。凡是符合AOP思想的技术,都可以看成是AOP的实现。

    Aop, aspect object programming 面向切面编程
    功能: 让关注点代码与业务代码分离!

    Aop本质是就是解决代码复用的问题,代码不用重复写多次

    spring是基于aop实现的,aop底层是通过动态代码实现的

    动态代理分为jdk动态代理和cGlib代理,不清楚的看自己的博客,默认情况下推荐使用cGLIB的方法效率高,jdk、动态底层基于反射,性能测试的时候cpu会出现占用率过大的性能问题

    spring框架中JDK和CGLIB动态代理区别

    代理模式应用场景

    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动态代理,必须是面向接口,目标业务类必须实现接口

    package com.itmayiedu.service;
    
    //user 服务层
    public interface UserService {
    
        public void add();
    }
    package com.itmayiedu.service.impl;
    
    import com.itmayiedu.service.UserService;
    
    //user 服务层public class UserServiceImpl implements UserService {
    
        // spring 事务封装呢? aop技术
        public void add() {
            System.out.println("往数据库添加数据...");
        }
    
    }
    package com.itmayiedu.proxy001;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    import com.itmayiedu.service.MemberService;
    import com.itmayiedu.service.impl.MemberServiceImpl;
    
    // 每次生成动态代理类对象时,实现了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("使用jdk动态代理 开启事务");
            result = method.invoke(target, args);
            System.out.println("使用jdk动态代理 提交事务");
            return result;
        }
    
        public static void main(String[] args) throws NoSuchMethodException, SecurityException, InstantiationException,
                IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            // 被代理对象
            // UserService userService = new UserServiceImpl();
            MemberService memberServiceImpl = new MemberServiceImpl();
            InvocationHandlerImpl invocationHandlerImpl = new InvocationHandlerImpl(memberServiceImpl);
            ClassLoader loader = memberServiceImpl.getClass().getClassLoader();
            Class<?>[] interfaces = memberServiceImpl.getClass().getInterfaces();
            // 主要装载器、一组接口及调用处理动态代理实例
            MemberService newProxyInstance = (MemberService) Proxy.newProxyInstance(loader, interfaces,
                    invocationHandlerImpl);
            newProxyInstance.memberAdd();
        }
    
    }

      日志打印输出为

    动态代理与静态代理的区别,静态代理需要编写UserDaoProxy代理类对象,动态代理不用编写,上面jdk动态代理必须面向接口要先定义接口,然后编写接口的实现子类,才能使用jdk的动态代理

    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中。

    1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP

    2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP

    3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

    JDK动态代理只能对实现了接口的类生成代理,而不能针对类 。
    CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 。
    因为是继承,所以该类或方法最好不要声明成final ,final可以阻止继承和多态。

    @Component
    @Aspect
    public class AopLog {
    
        // 前置通知
        @Before("execution(* com.itmayiedu.service.UserService.add(..))")
        public void begin() {
            System.out.println("前置通知");
        }
    
        //
        // 后置通知
        @After("execution(* com.itmayiedu.service.UserService.add(..))")
        public void commit() {
            System.out.println("后置通知");
        }
    
        // 运行通知
        @AfterReturning("execution(* com.itmayiedu.service.UserService.add(..))")
        public void returning() {
            System.out.println("运行通知");
        }
    
        // 异常通知
        @AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))")
        public void afterThrowing() {
            System.out.println("异常通知");
        }
    
        // 环绕通知
        @Around("execution(* com.itmayiedu.service.UserService.add(..))")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            System.out.println("环绕通知开始");
            proceedingJoinPoint.proceed();
            System.out.println("环绕通知结束");
        }
    }

     这里重点要强调一个知识点

    对于环绕通知,如果被执行的目标方法抛出了异常,然后目标方法没有使用try catch对异常进行处理,下来会执行

    @AfterThrowing标注的异常处理方法。环绕通知中的 System.out.println("环绕通知结束");就这句不再会被执行,这里一定要注意
     proceedingJoinPoint.proceed();
     System.out.println("环绕通知结束");

     

    如果目标方法出现了异常,手动使用了try  catch对异常进行了处理,没有把异常抛出去给aop框架进行处理,那么就不会执行

    @AfterThrowing标注的异常处理方法,
    环绕通知中的 System.out.println("环绕通知结束");就这句会再会被执行,这里一定要注意

    得出的结论:在业务层中编写代码的时候,如果使用了aop切面编程,业务层中的代码千万不要自己手动使用try catch 进行处理,这里会导致spring 的事务实效,当方法中的方法产生异常的
    时候不会钓鱼@AfterThrowing的方法,导致事务失败的时候不能rollBack。这里一定要注意呀

    接下来我们自己手动实效spring 事务

    第一种方法编程方式实现的事务
    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.itmayiedu"></context:component-scan>
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 -->
    
        <!-- 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="root"></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>
    在spring中使用具有@Transactional 需要在在配置文件中开启对事务注解的支持,上面我们没有配置自己对事务的支持,我们要手写一个事务框架来实现对事务的支持

    <!-- 开启spring对事务的注解支持 --> <tx:annotation-driven/>

    
    
    上面我们没有配置自己对事务的支持,我们要手写一个事务框架来实现对事务的支持

    我们使用c3p0的数据源

    Spring声明式事务管理器类:

         Jdbc技术:DataSourceTransactionManager

         Hibernate技术:HibernateTransactionManager

    手写Spring事务框架

    编程事务实现

    概述

    所谓编程式事务指的是通过编码方式实现事务,即类似于JDBC编程实现事务管理。管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

    案例

    使用编程事务实现手动事务

    使用编程事务实现,手动事务 begin、commit、rollback

    package com.itmayiedu.transaction;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
    
    //编程事务(需要手动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);
        }
    
    }
    dataSourceTransactionManager就是上面c3p0对应的jdbc数据源的事务管理器,
    DefaultTransactionAttribute采用数据库默认的隔离级别
    接下来我们看如何在业务层中使用编程方式的事务
    package com.itmayiedu.service.impl;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.interceptor.TransactionAspectSupport;
    
    import com.itmayiedu.dao.UserDao;
    import com.itmayiedu.service.UserService;
    import com.itmayiedu.transaction.TransactionUtils;
    
    //user 服务层
    @Service
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserDao userDao;
        @Autowired
        private TransactionUtils transactionUtils;
    
        // spring 事务封装呢? aop技术
         public void add() {
         TransactionStatus transactionStatus = null;
         try {
         // 开启事务
            transactionStatus = transactionUtils.begin();
             userDao.add("test001", 20);
             System.out.println("开始报错啦!@!!");
             int i = 1 / 0;
             System.out.println("################");
             userDao.add("test002", 21);
         // 提交事务
         if (transactionStatus != null)
                ransactionUtils.commit(transactionStatus);
         } catch (Exception e) {
         e.getMessage();
         // 回滚事务
            if (transactionStatus != null)
             transactionUtils.rollback(transactionStatus);
             }
         }
    
       
    
    }
     在上面的方法中例如add方法实现事务我们手动使用transactionStatus = transactionUtils.begin();实现事务,如果存在1000个业务方法,我们就要手动写1000次
    transactionStatus = transactionUtils.begin();,我们可以使用aop的方法来实现上面的事务
    我们来看下实际的代码
    package com.itmayiedu.aop;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.interceptor.TransactionAspectSupport;
    
    import com.itmayiedu.transaction.TransactionUtils;
    
    //切面类  基于手手动事务封装
    @Component
    @Aspect
    public class AopTransaction {
        @Autowired
        private TransactionUtils transactionUtils;
    
        // TransactionUtils 不要实现为单例子: 如果为单例子的话可能会发生线程安全问题
        // // 异常通知
        @AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))")
        public void afterThrowing() {
            System.out.println("回滚事务");
            // 获取当前事务 直接回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    
        // 环绕通知 在方法之前和之后处理事情
        @Around("execution(* com.itmayiedu.service.UserService.add(..))")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    
            // 调用方法之前执行
            System.out.println("开启事务");
            TransactionStatus transactionStatus = transactionUtils.begin();
            proceedingJoinPoint.proceed();// 代理调用方法 注意点: 如果调用方法抛出溢出不会执行后面代码
            // 调用方法之后执行
            System.out.println("提交事务");
            transactionUtils.commit(transactionStatus);
        }
    }
     第一使用aop的@Around的环绕通知,在方法执行之前添加事务TransactionStatus transactionStatus = transactionUtils.begin();,在方法  proceedingJoinPoint.proceed();执行完成之后提交事务 transactionUtils.commit(transactionStatus);

    然后时候aop的@AfterThrowing来处理方法抛出异常之后,对事务进行回滚,对事务进行回滚一般使用 transactionUtils.rollback(transactionStatus),需要知道当前事务的状态transactionStatus,这里如何获得当前事务的状态了,很多人在TransactionStatus transactionStatus = transactionUtils.begin()
    将TransactionStatus transactionStatus当前事务定义为类AopTransaction的成员变量,这里有问题,因为AopTransaction 类是线程不安全的,可能在多线程会引起问题,可以使用下面的方法

    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();获得当前线程下事务的状态,并且将当前事务进行回滚

    package com.itmayiedu.service.impl;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.interceptor.TransactionAspectSupport;
    
    import com.itmayiedu.dao.UserDao;
    import com.itmayiedu.service.UserService;
    import com.itmayiedu.transaction.TransactionUtils;
    
    //user 服务层
    @Service
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserDao userDao;
        @Autowired
        private TransactionUtils transactionUtils;
    
    public void add() {
            // 注意事项: 在使用spring事务的时候,service 不要try 最将异常抛出给外层aop 异常通知接受回滚
    
                userDao.add("test001", 20);
                int i = 1 / 0;
                System.out.println("################");
                userDao.add("test002", 21);
            
        }
    
    }

    执行上面的方法,AopTransaction 就实现了对add方法进行了事务的管理了。特别要注意的是在业务层中编写代码的时候,如果使用了aop切面编程,业务层中的代码千万不要自己手动使用try catch 进行处理,这里会导致spring 的事务实效,当方法中的方法产生异常的
    时候,因为自己定义了try catch拦截了异常,异常不会抛出给aop框架处理,就不会调用@AfterThrowing的方法,而是会继续调用transactionUtils.commit(transactionStatus)提交事务,不会执行@AfterThrowing的方法中的 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();、
    所以在业务层代码中,千万不要编写下面的代码:
    package com.itmayiedu.service.impl;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.interceptor.TransactionAspectSupport;
    
    import com.itmayiedu.dao.UserDao;
    import com.itmayiedu.service.UserService;
    import com.itmayiedu.transaction.TransactionUtils;
    
    //user 服务层
    @Service
    public class UserServiceImpl implements UserService {
        @Autowired
        private UserDao userDao;
        @Autowired
        private TransactionUtils transactionUtils;
    
    public void add() {
            // 注意事项: 在使用spring事务的时候,service 不要try 最将异常抛出给外层aop 异常通知接受回滚
            try {
                userDao.add("test001", 20);
                int i = 1 / 0;
                System.out.println("################");
                userDao.add("test002", 21);
            } catch (Exception e) {
                e.printStackTrace();
                TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            }
        }
    
    }

    这种写法在spring框架使用事务管理的时候会导致事务失败,无法保证一致性,上面代码执行异常的时候,应该讲 userDao.add("test001", 20);从数据库中回滚,但是因为自己使用了try catch处理了异常
    ,不会执行不会执行@AfterThrowing的方法中的 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();、而是会继续执行aop环绕通知中的transactionUtils.commit(transactionStatus)提交事务,导致userDao.add("test001", 20)保存在数据库中。


    package com.itmayiedu.aop;
    import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.AfterThrowing;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import org.springframework.transaction.TransactionStatus;import org.springframework.transaction.interceptor.TransactionAspectSupport;
    import com.itmayiedu.transaction.TransactionUtils;
    //切面类  基于手手动事务封装@Component@Aspectpublic class AopTransaction {@Autowiredprivate TransactionUtils transactionUtils;
    // TransactionUtils 不要实现为单例子: 如果为单例子的话可能会发生线程安全问题// // 异常通知@AfterThrowing("execution(* com.itmayiedu.service.UserService.add(..))")public void afterThrowing() {System.out.println("回滚事务");// 获取当前事务 直接回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}
    // 环绕通知 在方法之前和之后处理事情@Around("execution(* com.itmayiedu.service.UserService.add(..))")public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    // 调用方法之前执行System.out.println("开启事务");TransactionStatus transactionStatus = transactionUtils.begin();proceedingJoinPoint.proceed();// 代理调用方法 注意点: 如果调用方法抛出溢出不会执行后面代码// 调用方法之后执行System.out.println("提交事务");transactionUtils.commit(transactionStatus);}}

     

     

  • 相关阅读:
    mybatis Column 'XXX' in where clause is ambiguous 错误
    IDEA 代码提示不区分大小写
    接口安全问题
    spring 事务问题
    js问题: is not a function
    Uncaught TypeError: form.attr is not a function 解决办法
    springmvc 跳转页面或者返回json
    ajax 跳转页面时添加header
    maven工程 添加本地jar依赖
    mysql 创建备份表
  • 原文地址:https://www.cnblogs.com/kebibuluan/p/10705522.html
Copyright © 2011-2022 走看看