动态代理

动态概述

  1. 利用反射机制在运行时创建代理类,对代理类进行增强。

JDK动态代理

  1. //四则运算接口
  2. public interface Calculator {
  3. int add(int i, int j);
  4. int sub(int i, int j);
  5. int mul(int i, int j);
  6. int div(int i, int j);
  7. }
  1. //四则运算实现类
  2. public class CalculatorImpl implements Calculator {
  3. @Override
  4. public int add(int i, int j) {
  5. return i + j;
  6. }
  7. @Override
  8. public int sub(int i, int j) {
  9. return i - j;
  10. }
  11. @Override
  12. public int mul(int i, int j) {
  13. return i * j;
  14. }
  15. @Override
  16. public int div(int i, int j) {
  17. return i / j;
  18. }
  19. }
  1. /**
  2. * java.lang.reflect Proxy 使用JDK动态代理的对象必须实现一个或多个接口
  3. * static newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
  4. * 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
  5. */
  6. public class CalculatorProxy {
  7. /**
  8. * 获取Calculator代理对象
  9. * @param calculator 被代理对象接口
  10. * @return 代理对象
  11. *
  12. * newProxyInstance(ClassLoader loader, 类加载器
  13. * Class<?>[] interfaces, 类的接口
  14. * InvocationHandler h) 方法执行器-切面类
  15. */
  16. public static Calculator getProxy(final Calculator calculator) {
  17. return (Calculator) Proxy.newProxyInstance(
  18. calculator.getClass().getClassLoader(),
  19. calculator.getClass().getInterfaces(),
  20. new InvocationHandler() {
  21. /**
  22. *
  23. * @param proxy 代理对象,JDK专用
  24. * @param method 当前将要执行的目标对象的方法
  25. * @param args 目标方法调用时外界传入的参数
  26. * @return 目标方法执行后的返回值
  27. */
  28. @Override
  29. public Object invoke(Object proxy, Method method, Object[] args) {
  30. Object result = null;//结果集接收
  31. try {
  32. System.out.println("["+method.getName()+"]方法开始执行,参数为"+ Arrays.asList(args));
  33. //利用反射执行目标方法,指明执行方法的对象,返回执行结果
  34. result = method.invoke(calculator, args);
  35. System.out.println("["+method.getName()+"]方法执行完毕,结果为"+ result);
  36. } catch (Exception e) {
  37. System.err.println("["+method.getName()+"]方法出现异常,异常信息为"+e);
  38. } finally {
  39. System.out.println(method.getName()+"最终执行完毕");
  40. }
  41. return result;
  42. }
  43. }
  44. );
  45. }
  46. }
  1. @Test
  2. public void test1() {
  3. //获得代理的Calculator对象
  4. Calculator calculator = CalculatorProxy.getProxy(new CalculatorImpl());
  5. calculator.div(2, 2);
  6. }

CGLib动态代理

  1. CGLib是一个高性能开源的代码生成包,它采用非常底层的字节码技术,对指定的目标类生成重写一个子类,对子类进行增强。
  1. <dependency>
  2. <groupId>cglib</groupId>
  3. <artifactId>cglib</artifactId>
  4. <version>3.3.0</version>
  5. </dependency>
//四则运算实现类
public class CalculatorImpl {
    public int add(int i, int j) {
        return i + j;
    }
    public int sub(int i, int j) {
        return i - j;
    }
    public int mul(int i, int j) {
        return i * j;
    }
    public int div(int i, int j) {
        return i / j;
    }
}
//日志切面类
public class MyAspect {
    private Method method;//目标方法
    public MyAspect(Method method) {
        this.method = method;
    }
    public void before(Object[] args) {
        System.out.println("["+method.getName()+"]方法开始执行,参数为"+ Arrays.asList(args));
    }
    public void exception(Exception e) {
        System.err.println("["+method.getName()+"]方法出现异常,异常信息为"+e);
    }
    public void after(Object result) {
        System.out.println("["+method.getName()+"]方法执行完毕,结果为"+ result);
    }
}
public class CalculatorEnhancer implements MethodInterceptor {
    /**
     * 此方法主要描述如何增强目标类
     * @param proxy             代理对象引用
     * @param method            被代理对象的方法的引用
     * @param args              被代理方法的参数
     * @param methodProxy       代理对象对代理对象方法的描述
     * @return                  返回执行结果
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) {
        //引入增强切面类
        MyAspect aspect = new MyAspect(method);
        aspect.before(args);
        //1.调用代理对象方法,代理对象是目标对象的子类,执行父类方法
        //methodProxy.invokeSuper(proxy, args);
        Object invoke = null;
        try {
            //2.直接调用代理对象执行目标方法
            //invoke = method.invoke(target, args);
            invoke = methodProxy.invokeSuper(proxy, args);
        } catch (Exception e) {
            aspect.exception(e);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        aspect.after(invoke);
        return invoke;//返回执行结果
    }
}
public class CalculatorProxy {
    //获取Calculator代理对象
    public Object getProxy(Object target) {
        //使用CGLib增强被代理对象,创建代理工厂
        Enhancer enhancer = new Enhancer();
        //生成目标对象类的子类进行增强,指定目标类的类型
        enhancer.setSuperclass(target.getClass());
        //制定增强方案,实现MethodInterceptor接口,重写intercept方法增强
        enhancer.setCallback(new CalculatorEnhancer());//Callback指定实现MethodInterceptor的增强描述类
        return enhancer.create();//生成目标类对象的代理增强对象并返回
    }
}
public class App1 {
    @Test
    public void test1() {
        CalculatorProxy calculatorProxy = new CalculatorProxy();
        CalculatorImpl proxy = (CalculatorImpl) calculatorProxy.getProxy(new CalculatorImpl());
        proxy.add(1,2);
        proxy.div(3,0);
    }
}

AOP

AOP概述

AOP(面向切面编程):
通过预编译方式和程序运行期间动态代理,在不修改源码的前提下能对方法进行增强的技术。

AOP术语

1.连接点(joinpoint)
    连接点描述的是程序执行的某个特定位置,比如某个方法调用前,调用后,出现异常等。具有边界特征的特定点称为连接点。
2.切入点(pointcut)
    切入点是连接点的过滤条件,AOP通过切入点定位到特定的连接点,一个切入点可以切入多个连接点。
3.通知(Advice)
    切面在切入点采取的具体行为称为通知。切面的核心逻辑都写在通知上,通知是切面功能的具体实现。
4.通知器(Advisor)
    通知器由一个切入点和一个通知组成,对方法进行增强。
5.切面(Aspect)
    与通知器类似都是通知+切入点。区别在于,切面中的类无需实现通知接口,但需要在配置文件中指定类中的方法名,
    而通知器仅需指定类名即可,因为通知器中的类都实现了通知接口,很明确的知道通知方法是哪个。
6.目标(target)
    被通知的对象(方法)。
7.代理(Proxy)
    向目标对象应用通知之后创建的对象。
8.织入(weaving)
    把切面类应用到目标类创建新的代理对象的过程。
9.引入(introduction)
    允许我们向现有的类中新增属性和方法。

Maven依赖

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.3</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>

计算器日志(注解)

<!--  扫描目标类和切面类  -->
<context:component-scan base-package="com.example"/>
<!--  开启注解的AOP功能  -->
<aop:aspectj-autoproxy/>
//累加计算器
@Component
public class AccumulationCalculator {
    public int count(int n) {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        return sum;
    }
}
/**
 * @Berfore         前置通知
 * @AfterReturning  后置通知
 * @AfterThrowing   异常通知
 * @After           返回通知
 * @Around          环绕通知
 * 切入点表达式      value = "execution(public int com.example.AccumulationCalculator.count(int))"
 * 权限修饰符(不写默认匹配所有权限)    返回值类型    全类名    方法名(参数类型1,参数类型2,...)
 * 切入点表达式通配符    *:匹配一个或多个(一层路径)   ..:匹配任意多个参数和任意类型(多层路径)
 * JoinPoint:获取目标方法参数信息,getSignature获取方法签名,getArgs获取方法参数
 * returning = "result"(接收返回值)      throwing = "e":返回异常信息
 */
@Aspect
@Component
public class AccumulationCalculatorEnhancer {
    //运算前计时器
    private static Long beforeTime = 0L;
    /**
     * 抽取可重用切入点表达式,声明一个空方法
     * 标注PointCut注解,指定切入点
     * 在通知方法的Value指定切入点方法
     */
    @Pointcut(value = "execution(public int com.example.service.AccumulationCalculator.count(int))")
    public void aopEdge() {}
    //计算开始前
    @Before(value = "aopEdge()")
    public void countBefore(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();//获取目标方法签名
        Object[] args = joinPoint.getArgs();//获取参数
        System.out.println("["+signature.getName()+"]准备执行,参数为"+ Arrays.asList(args));
        beforeTime = System.currentTimeMillis();
    }
    //计算正常结束
    @AfterReturning(value = "aopEdge()", returning = "result")
    public void countAfter(JoinPoint joinPoint, Object result) {
        System.out.println("本次计算消耗" + (System.currentTimeMillis() - beforeTime)+"毫秒");
        Signature signature = joinPoint.getSignature();//获取目标方法签名
        System.out.println("["+signature.getName()+"]正常执行完毕,结果为"+ result);
    }
    //计算异常处理
    @AfterThrowing(value = "aopEdge()", throwing = "e")
    public void countException(JoinPoint joinPoint, Exception e) {
        Signature signature = joinPoint.getSignature();//获取目标方法签名
        System.err.println("["+signature.getName()+"]发生了异常,异常信息为"+e);
    }
    //程序最终结束
    @After(value = "aopEdge()")
    public void countFinally() {
        System.out.println("程序运行完毕");
    }
    /**
     * 环绕通知(等同于动态代理)
     * @param proceedingJoinPoint 切入点进程切入点(非常强大)
     * 环绕前置 -> 普通前置 -> 目标方法 -> 环绕返回/异常 -> 环绕后置 -> 普通后置 -> 普通返回/异常
     * 如果环绕通知捕获了异常必须放行异常,否则普通通知无法捕获异常
     */
    @Around(value = "aopEdge()")
    public Object countAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object invoke = null;//接收返回值
        System.err.println("[Around]前置");
        try {
            Object[] args = proceedingJoinPoint.getArgs();//获取参数
            invoke = proceedingJoinPoint.proceed(args);//利用反射执行目标方法
            System.err.println("[Around]正常返回");
        } catch (Exception e) {
            System.err.println("[Around]异常");
        } finally {
            System.err.println("[Around]后置");
        }
        return invoke;//返回反射调用后的返回值
    }
}
@RunWith(SpringRunner.class)
@ContextConfiguration(locations = {"classpath:application1.xml"})
public class App1 {
    @Autowired
    private AccumulationCalculator accumulationCalculator;
    @Test
    public void test1() {
        System.out.println("对象信息:"+accumulationCalculator.getClass());
        System.out.println(accumulationCalculator.count(100));
    }
}

计算器日志(XML)

public class AccumulationCalculatorEnhancer {
    //运算前计时器
    private static Long beforeTime = 0L;
    //计算开始前
    public void countBefore(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();//获取目标方法签名
        Object[] args = joinPoint.getArgs();//获取参数
        System.out.println("["+signature.getName()+"]准备执行,参数为"+ Arrays.asList(args));
        beforeTime = System.currentTimeMillis();
    }
    //计算正常结束
    public void countAfter(JoinPoint joinPoint, Object result) {
        System.out.println("本次计算消耗" + (System.currentTimeMillis() - beforeTime)+"毫秒");
        Signature signature = joinPoint.getSignature();//获取目标方法签名
        System.out.println("["+signature.getName()+"]正常执行完毕,结果为"+ result);
    }
    //计算异常处理
    public void countException(JoinPoint joinPoint, Exception e) {
        Signature signature = joinPoint.getSignature();//获取目标方法签名
        System.err.println("["+signature.getName()+"]发生了异常,异常信息为"+e);
    }
    //程序最终结束
    public void countFinally() {
        System.out.println("程序运行完毕");
    }
    public Object countAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Object invoke = null;//接收返回值
        System.err.println("[Around]前置");
        try {
            Object[] args = proceedingJoinPoint.getArgs();//获取参数
            invoke = proceedingJoinPoint.proceed(args);//利用反射执行目标方法
            System.err.println("[Around]正常返回");
        } catch (Exception e) {
            System.err.println("[Around]异常");
        } finally {
            System.err.println("[Around]后置");
        }
        return invoke;//返回反射调用后的返回值
    }
}
<?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: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/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--  向容器添加目标类(累加计算器)  -->
    <bean id="accumulationCalculator" class="com.example.service.AccumulationCalculator"/>
    <!--  向容器添加切面类(累加计算器增强类)  -->
    <bean id="accumulationCalculatorEnhancer" class="com.example.aspect.AccumulationCalculatorEnhancer"/>
    <!--  Spring AOP配置  -->
    <aop:config>
        <!--  抽取公共切入点表达式  -->
        <aop:pointcut id="aopEdge" 
                      expression="execution(public int com.example.service.AccumulationCalculator.count(int))"/>
        <!--  告诉Spring容器AccumulationCalculatorEnhancer是切面类  -->
        <aop:aspect ref="accumulationCalculatorEnhancer" order="1">
            <aop:before method="countBefore" pointcut-ref="aopEdge"/>
            <aop:after-returning method="countAfter" pointcut-ref="aopEdge" returning="result"/>
            <aop:after-throwing method="countException" pointcut-ref="aopEdge" throwing="e"/>
            <aop:after method="countFinally" pointcut-ref="aopEdge"/>
            <aop:around method="countAround" pointcut-ref="aopEdge"/>
        </aop:aspect>
    </aop:config>
</beans>

多切面切入

//切入面类B:两个切面同时切入一个方法时默认会比较切面类类名ASCII值,先执行的切面会在最后结束通知,中间其他切面先结束。
//可以通过在类上添加@Order(int)值改变其切面执行优先级,数值越小优先级越高,默认值Integer.MAX_VALUE
@Aspect
@Component
public class AspectB {
    //运算前计时器
    private static Long beforeTime = 0L;
    /**
     * 抽取可重用切入点表达式,声明一个空方法
     * 标注PointCut注解,指定切入点
     * 在通知方法的Value指定切入点方法
     */
    @Pointcut(value = "execution(public int com.example.service.AccumulationCalculator.count(int))")
    public void aopEdge() {}
    //计算开始前
    @Before(value = "aopEdge()")
    public void countBefore(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();//获取目标方法签名
        Object[] args = joinPoint.getArgs();//获取参数
        System.err.println("["+signature.getName()+"]准备执行,参数为"+ Arrays.asList(args));
        beforeTime = System.currentTimeMillis();
    }
    //计算正常结束
    @AfterReturning(value = "aopEdge()", returning = "result")
    public void countAfter(JoinPoint joinPoint, Object result) {
        System.err.println("本次计算消耗" + (System.currentTimeMillis() - beforeTime)+"毫秒");
        Signature signature = joinPoint.getSignature();//获取目标方法签名
        System.err.println("["+signature.getName()+"]正常执行完毕,结果为"+ result);
    }
    //计算异常处理
    @AfterThrowing(value = "aopEdge()", throwing = "e")
    public void countException(JoinPoint joinPoint, Exception e) {
        Signature signature = joinPoint.getSignature();//获取目标方法签名
        System.err.println("["+signature.getName()+"]发生了异常,异常信息为"+e);
    }
    //程序最终结束
    @After(value = "aopEdge()")
    public void countFinally() {
        System.err.println("程序运行完毕");
    }
}

AOP1.png

Spring 事务

Maven依赖

<dependencies>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.3</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.3</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.37</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.3</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.3</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <!-- 增加maven编译插件,设置编译版本,防止刷新后变为jdk5-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.6.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
    </plugins>
</build>

dp.properties

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.jdbcUrl=jdbc:mysql://47.172.193.151:3306/st01
dataSource.username=root
dataSource.password=123456

applicationContext.xml

<!--  扫描Dao组件和Service组件  -->
<context:component-scan base-package="com.example"/>
<!--  加载mysql配置文件  -->
<context:property-placeholder location="classpath:dp.properties"/>
<!--  配置数据源  -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${dataSource.driverClassName}"/>
    <property name="url" value="${dataSource.jdbcUrl}"/>
    <property name="username" value="${dataSource.username}"/>
    <property name="password" value="${dataSource.password}"/>
</bean>
<!--  配置JdbcTemplate操作数据库,引用Druid数据源  -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="druidDataSource"/>
</bean>
<!--  配置事务管理器(事务切面)  -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--  控制数据源  -->
    <property name="dataSource" ref="druidDataSource"/>
</bean>
<!--  开启基于注解驱动的事务控制,依赖tx名称空间  -->
<tx:annotation-driven transaction-manager="tm"/>

Dao

public interface EmployeeDao {
    /**
     * 根据员工姓名更新员工余额
     * @param name  员工姓名
     * @param money 更新金额数目
     */
    void updateBalanceByName(String name, Double money);
}
@Repository
public class EmployeeDaoImpl implements EmployeeDao {
    private JdbcTemplate jdbcTemplate;//引入JdbcTemplate
    public EmployeeDaoImpl(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    @Override
    public void updateBalanceByName(String name, Double money) {
        String sql = "update employee set balance = balance - ? where emp_name = ?;";
        jdbcTemplate.update(sql, money, name);
    }
}

Service

public interface EmployeeService {
    /**
     * 模拟交易场景
     * @param consumer  消费者
     * @param business  商家
     * @param money     交易金额
     */
    void transferAccounts(String consumer, String business, Double money);
}
@Service
public class EmployeeServiceImpl implements EmployeeService {
    private EmployeeDao employeeDao;//引入Dao组件
    public EmployeeServiceImpl(EmployeeDao employeeDao) {
        this.employeeDao = employeeDao;
    }
    /**
     * 声明式事务参数细节
     * timeout int:单位是秒
     * readonly boolean:设置事务只读属性
     * noRollbackFor Class[]:设置哪些异常可以不回滚,指定类反射实例
     * noRollbackForClassName String[]:设置哪些异常可以不回滚,全类名
     * RollbackFor Class[]:设置哪些异常可以回滚,指定类反射实例
     * RollbackForClassName String[]:设置哪些异常可以回滚,全类名
     * isolation:隔离级别,默认值是默认数据库隔离级别
     *      -1  Isolation.DEFAULT:MySQL默认隔离级别可重复读
     *      1   Isolation.READ_UNCOMMITTED:读未提交
     *      2   Isolation.READ_COMMITTED:读已提交
     *      4   Isolation.REPEATABLE_READ:可重复读
     *      8   Isolation.SERIALIZABLE:串行化
     * propagation:事务传播行为
     * 当一个事务方法被另外一个事务方法调用时,要指定事务如何传播。
     * 1.Propagation.REQUIRED       如果有事务运行,当前方法就在该事务内,否则自己开一个事务
     * 2.Propagation.REQUIRED_NEW   当前方法必须自己开一个事务,之前运行的事务挂起
     * 3.Propagation.SUPPORTS       如果有事务运行就运行在该事务内,否则就不使用事务
     * 4.Propagation.NOT_SUPPORTS   该方法不运行在事务内,如果有事务运行就挂起
     * 5.Propagation.MANDATORY      该方法必须运行在事务内,如果没有事务抛异常
     * 6.Propagation.NEVER          当前方法不应该运行在事务内,否则抛异常
     * 注意:
     *      1.如果子事务REQUIRED继承一个主事务,那么会继承所有配置,REQUIRED_NEW可以自定义配置
     *      2.如果主事务和子事务在同一个类,那么子事务会被看成普通方法,和主事务是一个事务,不走
     *        代理对象,如果子事务设置REQUIRED_NEW不会生效
     */
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void transferAccounts(String consumer, String business, Double money) {
        employeeDao.updateBalanceByName(consumer, money);//消费者付款
        //System.out.println(3 / 0);//异常模拟
        employeeDao.updateBalanceByName(business, -money);//商家收到钱
    }
}

Spring Test

@RunWith(value = SpringRunner.class)
@ContextConfiguration(locations = {"classpath:application1.xml"})
public class App1 {
    @Autowired
    private EmployeeService employeeService;
    @Test
    public void test() {
        employeeService.transferAccounts("Jack","Maria",300.00);
    }
}

事务控制(XML)

<!--  扫描Dao组件和Service组件  -->
<context:component-scan base-package="com.example"/>
<!--  加载mysql配置文件  -->
<context:property-placeholder location="classpath:dp.properties"/>
<!--  配置数据源  -->
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="${dataSource.driverClassName}"/>
    <property name="url" value="${dataSource.jdbcUrl}"/>
    <property name="username" value="${dataSource.username}"/>
    <property name="password" value="${dataSource.password}"/>
</bean>
<!--  配置JdbcTemplate操作数据库,引用Druid数据源  -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="druidDataSource"/>
</bean>
<!--  注入EmployeeDao,依赖注入jdbcTemplate  -->
<bean id="employeeDao" class="com.example.dao.impl.EmployeeDaoImpl">
    <constructor-arg name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!--  注入EmployeeService,依赖注入EmployeeDao  -->
<bean id="employeeService" class="com.example.service.impl.EmployeeServiceImpl">
    <constructor-arg name="employeeDao" ref="employeeDao"/>
</bean>
<!--  配置事务管理器(事务切面)  -->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--  控制数据源  -->
    <property name="dataSource" ref="druidDataSource"/>
</bean>
<!--  AOP切面配置  -->
<aop:config>
    <!--  配置事务切入点  -->
    <aop:pointcut id="txPoint" expression="execution(public void com.example.service.impl.EmployeeServiceImpl.transferAccounts(String, String, Double))"/>
    <!--  事务增强:引用事务管理器和切入点  -->
    <aop:advisor advice-ref="txManager" pointcut-ref="txPoint"/>
</aop:config>
<!--  指定配置那个事务管理器,指定需要使用事务的切入点方法,配置事务属性  -->
<tx:advice id="txManager" transaction-manager="tm">
    <tx:attributes>
        <tx:method name="transferAccounts"
                   isolation="READ_COMMITTED"
                   timeout="3"
                   propagation="REQUIRES_NEW"
                   no-rollback-for="FileSystemException.class"
                   rollback-for="NullPointerException.class"
                   read-only="true"
                   />
    </tx:attributes>
</tx:advice>

事务传播图解

  • 如果异常发生在主事务

TX1.png

  • 如果异常发生在子事务

TX2.png