zoukankan      html  css  js  c++  java
  • 手写spring事务框架, 揭秘AOP实现原理。

    AOP面向切面编程:主要是通过切面类来提高代码的复用,降低业务代码的耦合性,从而提高开发效率。主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。

    AOP实现原理:aop是通过cglib的动态代理实现的。

       jdk动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

       cglib动态代理:将代理对象类的class文件加载进来,通过ASM字节码技术修改其字节码生成子类来处理。

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

    一:AOP运行过程

    项目结构

    1.1 导入相关包

      <dependencies>
            <!-- 引入Spring-AOP等相关Jar -->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>3.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>3.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aop</artifactId>
                <version>3.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-orm</artifactId>
                <version>3.0.6.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjrt</artifactId>
                <version>1.6.1</version>
            </dependency>
            <dependency>
                <groupId>aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.5.3</version>
            </dependency>
            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>2.1_2</version>
            </dependency>
            <!-- 数据库连接池 -->
            <dependency>
                <groupId>com.mchange</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.5.2</version>
            </dependency>
            <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.37</version>
            </dependency>
        </dependencies>

    1.2 配置包扫描和切面代理

    <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" xmlns:tx="http://www.springframework.org/schema/tx"
        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
             http://www.springframework.org/schema/tx
              http://www.springframework.org/schema/tx/spring-tx.xsd">
        
        <!-- 扫描指定路径 -->
        <context:component-scan base-package="com.wulei"/>
        <!-- 开启切面代理 -->
        <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>

    1.3 编写AOP切面

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Component//注入到spring
    @Aspect//申明切面类
    public class AopLog {
         
         // aop 编程里面有几个通知: 前置通知 后置通知 运行通知 异常通知 环绕通知
         @Before("execution(* com.wulei.service.UserService.add(..))")
         public void before() {
         System.out.println("前置通知: 在方法之前执行...");
         }
        
         // 后置通知 在方法运行后执行
         @After("execution(* com.wulei.service.UserService.add(..))")
         public void after() {
             System.out.println("后置通知: 在方法之后执行...");
         }
        
         // 运行通知
         @AfterReturning("execution(* com.wulei.service.UserService.add(..))")
         public void returning() {
             System.out.println("运行通知:");
         }
        
         // 异常通知
         @AfterThrowing("execution(* com.wulei.service.UserService.add(..))")
         public void afterThrowing() {
             System.out.println("异常通知: 异常抛出执行");// 异常被try()cacth{}捕捉到则不执行。
         }
        
         // 环绕通知 在方法之前和之后处理事情
         @Around("execution(* com.wulei.service.UserService.add(..))")
         public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
            // 调用方法之前执行
            System.out.println("环绕通知: 调用方法之前执行");
            proceedingJoinPoint.proceed();
            /* 代理调用方法 , 如果调用方法抛出异常就不会执行后面代码。
             * 
             * 在使用spring事务的时候  service最好不要try, 将异常抛出给aop 异常通知处理回滚!
             * 否则业务逻辑出错,而aop却正常执行,就会造成事务失效的情况。
             */
            
             // 调用方法之后执行
             System.out.println("环绕通知: 调用方法之后执行");
         }
    }

    1.4 编写Service

    @Service
    public class UserService {
        public void add() {
            System.out.println("正在添加数据");
            int i = 1/0;
            // 如果出现异常就会触发AOP异常通知,如果异常被try()catch{}住,则会不触发异常通知继续走完环绕通知。
        }
    }

    1.5 测试

    public class Main {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            UserService userService = (UserService) applicationContext.getBean("userService");
            userService.add();
        }
    }
    =================
    【控制台输出】
        前置通知: 在方法之前执行...
        Exception in thread "main" 环绕通知: 调用方法之前执行
        正在添加数据
        后置通知: 在方法之后执行...
        异常通知: 异常抛出执行
        java.lang.ArithmeticException: / by zero
            at com.wulei.service.UserService.add(UserService.java:16)
     

    二:手写编程式事务

    2.1 在spring.xml配置好自己的数据源。
    2.2 编写dao层

    /*
     CREATE TABLE `t_users` (
       `name` varchar(20) NOT NULL,
       `age` int(5) DEFAULT NULL,
       PRIMARY KEY (`name`)
     ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
     */
    @Repository
    public class UserDao {
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        public int add(String name, Integer age) {
            String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);";
            int result = jdbcTemplate.update(sql, name, age);
            System.out.println("插入成功");
            return result;
        }
    }

    2.3 手写编程式事务具体逻辑

    @Component
    public class MyTransaction {
        
        // 获取数据源
        @Autowired
        private DataSourceTransactionManager dataSourceTransactionManager;
        
        // 开启事务
        public TransactionStatus begin() {
            // getTransaction()这里的参数是用的事务默认的传播属性
            TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
            System.out.println("开启事务");
            // 得到事务状态
            return transaction;
        }
    
        // 提交事务
        public void commit(TransactionStatus transaction) {
            dataSourceTransactionManager.commit(transaction);
            System.out.println("提交事务");
        }
    
        // 回滚事务
        public void rollback(TransactionStatus transaction) {
            dataSourceTransactionManager.rollback(transaction);
            System.out.println("事务回滚");
        }
    }

    2.4 编写service,然后运行测试。

    @Service
    public class UserService {
    
        @Autowired
        private UserDao userDao;
        @Autowired
        private MyTransaction myTransaction;
        
        public void add() {
            TransactionStatus transactionStatus = null;
            try {
                //1. 开启事务
                transactionStatus = myTransaction.begin();
                userDao.add("test001", 20);
                int i = 1 / 0;
                userDao.add("test002", 21);
                //2. 执行成功就提交事务
                myTransaction.commit(transactionStatus);
            } catch (Exception e) {
                //3. 出现异常就回滚
                myTransaction.rollback(transactionStatus);
            }
        }
    }

    ========================
    【控制台输出】   此时查看数据库可以发现,由于我们手动回滚所以没有插入数据。
      前置通知: 在方法之前执行...
      环绕通知: 调用方法之前执行
      开启事务
      插入成功
      事务回滚
      后置通知: 在方法之后执行...
      环绕通知: 调用方法之后执行
      运行通知:

    三:AOP重构编程式事务

    3.1 通过aop实现spring事务

    @Component
    @Aspect
    // 基于AOP的环绕通知和异常通知实现Spring事务
    public class AopTransaction {
    
        @Autowired
        private MyTransaction myTransaction;
        
        // 环绕通知  在方法之前和之后处理事情
        @Around("execution(* com.wulei.service.UserService.add(..))")
        public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    
            // 开启事务    调用方法之前执行 
            TransactionStatus transactionStatus = myTransaction.begin();
            proceedingJoinPoint.proceed();// 代理调用方法 注意点: 如果调用方法抛出溢出不会执行后面代码
            // 提交事务  调用方法之后执行
            myTransaction.commit(transactionStatus);
        }
        // 异常通知
        @AfterThrowing("execution(* com.wulei.service.UserService.add(..))")
        public void afterThrowing() {
            System.out.println("回滚当前事务");
            // 获取当前事务 直接回滚
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }

    3.2 编写service, 然后运行测试

    @Service
    public class UserService {
    
        @Autowired
        private UserDao userDao;
        
        public void add() {
            
            userDao.add("test001", 20);
            int i = 1 / 0;
            userDao.add("test002", 21);//try {
            //    userDao.add("test001", 20);
            //    int i = 1 / 0;
            //    userDao.add("test002", 21);
            //} catch (Exception e) {
            //    // 回滚当前事务。
            //    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            //}
        }
    }
    ===================
    【控制台输出】   此时查看数据库可以发现,触发异常通知回滚所以没有插入数据。
      开启事务
      插入成功
      回滚当前事务

    注意:在环绕通知中begin()开启了事务,如果程序出现了异常,环绕通知就不会commit()提交事务,若此时异常被try捕捉,异常通知又无法rollback()来回滚,若不手动回滚就会造成事务失效。

  • 相关阅读:
    vba合并报表(搬一下)
    自选股票并绘制图像
    python代码--利用python进行数据分析里第十四章缺失代码
    python时间戳(1)
    python学习--列替换问题
    python学习--练习题28
    报错记录--004--panda删除添加问题
    python学习--练习题21-27
    python学习--练习题14-20
    python学习--练习题111213--分阶段判断问题、输出指定日期格式问题、统计字符问题
  • 原文地址:https://www.cnblogs.com/wlwl/p/10032206.html
Copyright © 2011-2022 走看看