前言

AOP是Aspect-Oriented Programming面向切面编程的缩写;

AOP和IOC一样也是一种编程思想,最终的目的都是为了实现代码在编译期的解耦;

IOC可实现对象与对象之间的解耦,AOP可实现方法和方法之间的解耦(AOP解耦粒度会更细);

当我们把dao层和service层的实现类对象,以及实现类对象需要的依赖,放进Sprig的容器管理之后;

这些实现类对象的某些方法都依赖同一段代码,例如service层和dao层实现类的所有方法都需要增加日志、事务、代码性能测试功能,应该使用AOP;

AOP思想的目的是可以在不修改源代码的基础上,对原有功能进行增强。

 

一、AOP概念

AOP( 面向切面编程 )是一种思想,它的目的就是在不修改源代码的基础上,对原有功能进行增强。

SpringAOP是对AOP思想的一种实现,Spring底层同时支持jdk和cglib动态代理

Spring会根据被代理的类是否有接口自动选择代理方式:

  • 如果有接口,就采用jdk动态代理(当然,也可以强制使用cglib)

  • 没有接口,    就采用cglib的方式

1.代理技术

代理技术是实现AOP思想的手段;

1.1.jdk代理

需要原始对象和代理对象实现同一个接口;

package com.zhanggen;

import com.zhanggen.config.SpringConfig;
import com.zhanggen.log.Loger;
import com.zhanggen.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    //日志对象
    @Autowired
    private Loger loger;
    //目标对象
    @Autowired
    private AccountService accountService;

    @Test
    public void testSaveAndFind() {
        //产生代理对象
        //1.获取目标对象
        //2.编写增强逻辑
        InvocationHandler invocationHandler = new InvocationHandler() {


            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object obj = null;
                try {
                    loger.beforMetod();
                    //调用目标对象的的方法逻辑
                    //如果目标对象的方法有返回值,则返回,没有则返回值是null
                    obj = method.invoke(accountService, args);
                    loger.afterMetod();
                } catch (Exception e) {
                    loger.exceptionMetod();
                }
                return obj;
            }
        };
        //3.创建代理对象
        AccountService instance = (AccountService) Proxy.newProxyInstance(
                accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                invocationHandler
        );
        //4.调用代理对象的方法
        instance.save();
    }

}
jdk代理实现AOP

1.2.cglib代理

需要代理对象继承原始对象;

package com.zhanggen;

import com.zhanggen.config.SpringConfig;
import com.zhanggen.log.Loger;
import com.zhanggen.service.impl.AccountServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.lang.reflect.Method;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    //日志对象
    @Autowired
    private Loger loger;
    //目标对象
    @Autowired
    private AccountServiceImpl accountService;

    @Test
    public void testSaveAndFind() {
        //产生代理对象
        //1.获取目标对象
        //2.编写增强逻辑
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object obj = null;
                try {
                    loger.beforMetod();
                    //调用目标对象的的方法逻辑
                    //如果目标对象的方法有返回值,则返回,没有则返回值是null
                    obj = method.invoke(accountService, args);
                    loger.afterMetod();
                } catch (Exception e) {
                    loger.exceptionMetod();
                }
                return obj;
            }
        };
        //3.使用cglib创建代理对象
        //3-1:创建增强器
        Enhancer enhancer = new Enhancer();
        //3-2:设置父类
        enhancer.setSuperclass(AccountServiceImpl.class);
        //3-3:设置增强逻辑
        enhancer.setCallback(invocationHandler);
        //3-4:得到代理对象
        AccountServiceImpl instance = (AccountServiceImpl) enhancer.create();
        //4.调用代理对象的方法
        instance.save();
    }

}
cgilib代理

 

2.AOP术语

目标对象: 被代理对象

连接点:   目标对象中的所有方法;(全部方法)

切入点:    目标对象中要进行功能增强的方法,可以有1个也可以有多个;(一部分方法)

增强(通知)对象:  需要增强的功能(日志 事务),一般是1个增强对象,增强对象中有增强方法;

织入(动词):将切点方法和增强方法拼接过程

代理对象:经过功能增强之后的对象

切面(名词):切点+增强,切面是一种描述,描述了这样一件事情: 一个什么样的增强方法加入到了 一个什么样的切点方法的 什么位置,本质上就是在描述增强方法和切点方法的执行顺序

 

 

2.AOP实现过程 

开发阶段: 核心功能和边缘功能进行分别开发,双方完全没有任何依赖关系;

 

 

运行阶段:使用动态代理技术,动态对核心功能和边缘功能进行自由组装,形成1个大而全的功能

 

开发阶段分别开发,运行阶段组装运行;

 

3.AOP的优势

  • 代码逻辑清晰:开发核心业务的时候,不必关注增强业务的代码
  • 代码复用性高:增强代码不用重复书写
  • 核心功能代码和边缘功能解耦

 

二、AOP日志案例

使用SpringAop完成在业务(Service)层类中的方法上打印日志

1.创建maven子模块,导入pom依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring</artifactId>
        <groupId>com.zhanggen</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>day03-04-springaop-xml</artifactId>
    <dependencies>
        <!--spring核心-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <!--切点表达式解析坐标-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

        <!--测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
    </dependencies>

</project>
pom.xml

2.创建业务层接口和实现类

定义service层接口

package com.zhanggen.service;

import java.util.List;

public interface AccountService {
    //保存
    void save(Object obj);

    //查询所有
    List findAll();

    //主键查询
    Object findById(Integer id);
}
AccountService.interface

定义service层实现类

package com.zhanggen.service.impl;

import com.zhanggen.service.AccountService;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AccountServiceImpl implements AccountService {
    @Override
    public void save(Object obj) {
        System.out.println("save");
    }

    @Override
    public List findAll() {
        System.out.println("findAll");
        return null;
    }

    @Override
    public Object findById(Integer id) {
        System.out.println("findById");
        return "哈哈哈";
    }
}
AccountServiceImpl.java

3.创建日志类

package com.zhanggen.log;

import org.springframework.stereotype.Component;

@Component
public class Loger {
    public void beforMethod() {
        System.out.println("进入方法前");
    }

    public void afterMethod() {
        System.out.println("进入方法后");
    }
}
Loger.java

4.Spring配置文件

<?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: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/tx
                http://www.springframework.org/schema/tx/spring-tx.xsd
                http://www.springframework.org/schema/aop
                https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置注解扫描-->
    <context:component-scan base-package="com.zhanggen"></context:component-scan>
    <!--表示这是一段aop设置-->
    <aop:config>

        <!--
        配置切点:通过规则表达式选择出1个切点方法
        id="标识"
        expression="execution(切点表达式)"
        -->
        <aop:pointcut id="pt"
                      expression="execution(java.util.List com.zhanggen.service.impl.AccountServiceImpl.findAll())"></aop:pointcut>
        <!--
            配置1个切面:增强方法和切点方法的执行顺序
            增强方法:ref="loger"+ method="beforMethod"
            切点方法:pointcut-ref="pt"
            执行顺序: aop:before 执行顺序(增强方法在切点方法之前运行)

            -->
        <aop:aspect ref="loger">
            <aop:before method="beforMethod" pointcut-ref="pt"></aop:before>
            <aop:after method="afterMethod" pointcut-ref="pt"></aop:after>
        </aop:aspect>


    </aop:config>

</beans>
applicationContext.xml

5.测试

package com.zhanggen;

import com.zhanggen.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")

public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void Test1() {
        accountService.findAll();
    }
}
AccountServiceTest.java

 

三、APO配置项(XML)

在spring配置文件中配置AOP的配置项介绍;

1.切点表达式

如果service层所有实现类(目标对象)的所有方法(切点),都需要增加日志功能,如何给多个类和方法进行功能增强呢?

切点表达式本质是一组匹配规则,用于在目标对象的连接点中挑选出1个或者多个切点

1.1.匹配1个切点

切点表达式(expression):通过规则表达式,从目标对象中选择出1个切点方法

id="标识"

        <aop:pointcut id="pt"
                      expression="execution(java.util.List com.zhanggen.service.impl.AccountServiceImpl.findAll())">
     </
aop:pointcut>

1.2.模糊匹配多个切点

 *占位符:表示匹配1个或多个

..占位符:表示匹配0个或多个

<aop:pointcut id="pt"
                      expression="execution(* com.zhanggen.service.impl.AccountServiceImpl.*(..))"></aop:pointcut>

 

2.四大通知

四大通知描述的就是增强方法在切点方法的什么位置上执行

  • 前置通知(before) :增强方法在切点运行之前执行
  • 后置通知(after-returning):增强方法在切点正常运行结束之后执行
  • 异常通知(after-throwing):增强方法在切点发生异常的时候执行
  • 最终通知(after):增强方法在切点的最终执行

4大通知相当于如下代码执行流程;

try {
  前置通知(before) :增强方法在切点运行之前执行
  // 切点方法执行
  后置通知(after-returning):增强方法在切点正常运行结束之后执行
}catch (Exception e){
    异常通知(after-throwing):
  增强方法在切点发生异常的时候执行 }
finally { 最终通知(after):增强方法在切点的最终执行 }

 

3.环绕通知

它是一种特殊的通知,他允许以Java编码的形式,替代以上4大通知;

3.1.实现环绕通知方法

package com.zhanggen.log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

@Component
public class Loger {
    public void beforMethod() {
        System.out.println("进入方法前");
    }

    public void afterMethod() {
        System.out.println("进入方法后");
    }

    //环绕通知方法:可以替代以上所有方法
    public Object aroundMethod(ProceedingJoinPoint pjp) {
        Object obj = null;
        try {
            System.out.println("进入方法前");
            obj = pjp.proceed();
            System.out.println("进入方法后");
        } catch (Throwable throwable) {
            System.out.println("方法出现异常结束");
        } finally {
            System.out.println("方法正常结束");
        }
        return obj;
    }
}
Loger.java

3.2.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: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/tx
                http://www.springframework.org/schema/tx/spring-tx.xsd
                http://www.springframework.org/schema/aop
                https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置注解扫描-->
    <context:component-scan base-package="com.zhanggen"></context:component-scan>
    <!--表示这是一段aop设置-->
    <aop:config>
        <!--
        配置切点:通过规则表达式选择出1个切点方法
        id="标识"
        expression="execution(切点表达式)"
        -->
        <aop:pointcut id="pt"
                      expression="execution(* com.zhanggen.service.impl.AccountServiceImpl.*(..))"></aop:pointcut>
        <!--
            配置1个切面:增强方法和切点方法的执行顺序
            增强方法:ref="loger"+ method="beforMethod"
            切点方法:pointcut-ref="pt"
            执行顺序: aop:before 执行顺序(增强方法在切点方法之前运行)

            -->
        <aop:aspect ref="loger">
            <!--<aop:before method="beforMethod" pointcut-ref="pt"></aop:before>-->
            <!--<aop:after method="afterMethod" pointcut-ref="pt"></aop:after>-->
            <aop:around method="aroundMethod" pointcut-ref="pt"></aop:around>
        </aop:aspect>


    </aop:config>

</beans>
applicationContext.xml

3.3.环绕通知获取切点执行细节

pjp对象代表当前切点,可以通过这个对象获取到正在调用类的方法、参数、返回值、异常信息、调用时间,作为日志信息的一部分;

package com.zhanggen.log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Date;

@Component
public class Loger {
    public void beforMethod() {
        System.out.println("进入方法前");
    }

    public void afterMethod() {
        System.out.println("进入方法后");
    }

    //环绕通知方法:可以替代以上所有方法
    //pjp对象代表切点:可以通过这个对象获取到正在调用类的 方法、参数、返回值、异常信息、调用时间;
    public Object aroundMethod(ProceedingJoinPoint pjp) {
        //日志字符串可变长度字符串
        StringBuilder stringBuilder = new StringBuilder();
        Object obj = null;
        try {
            System.out.println("进入方法前");
            obj = pjp.proceed();
            stringBuilder.append("类:" + pjp.getTarget().getClass().getName());
            Signature signature = (MethodSignature) pjp.getSignature();
            stringBuilder.append("方法名称:" + signature.getName());
            stringBuilder.append("方法参数:" + Arrays.toString(pjp.getArgs()));
            stringBuilder.append("方法返回值:" + obj);
            stringBuilder.append("调用时间:" + new Date());
            System.out.println("进入方法后");
        } catch (Throwable e) {
            System.out.println("方法出现异常结束");
            stringBuilder.append("异常信息:" + e.getMessage());
        } finally {
            System.out.println("方法正常结束");
        }
        //打印日志
        System.out.println(stringBuilder.toString());
        return obj;
    }
}
Loger.java

 

四、APO配置(注解)

使用注解的方式替换xml配置文件中的配置,AOP的注解配置在增强方法上;

1.激活切面自动代理

配置文件中配置激活切面自动代理

<?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: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/tx
                http://www.springframework.org/schema/tx/spring-tx.xsd
                http://www.springframework.org/schema/aop
                https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置注解扫描-->
    <context:component-scan base-package="com.zhanggen"></context:component-scan>
    <!--激活切面自动代理-->
    <aop:aspectj-autoproxy/>


</beans>
applicationContext.xml

配置类中配置激活切面自动代理

package com.zhanggen.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;


//注解扫描
@ComponentScan("com.zhanggen")
//激活切面自动代理
@EnableAspectJAutoProxy
public class SpringConfig {
}
SpringConfig.java

2.四大通知

package com.zhanggen.log;

import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Aspect //切面:增强方法和切点方法的执行顺序
@Component
public class Loger {
    // 定义切点:默认id是方法名称
    @Pointcut("execution(* com.zhanggen.service.impl.AccountServiceImpl.*(..))")
    public void pt() {
    }

    //代表此注解标注的方法会在切点之前运行
    @Before("pt()")
    public void beforMethod() {
        System.out.println("进入方法前");
    }

    @AfterReturning("pt()")
    public void afterReturningMethod() {
        System.out.println("方法正常运行结束后");
    }

    @AfterReturning("pt()")
    public void afterThrowingMethod() {
        System.out.println("方法出现异常后");
    }

    @After("pt()")
    public void afterMethod() {
        System.out.println("方法运行到最后");
    }

    //环绕通知方法:可以替代以上所有方法
    //pjp对象代表切点:可以通过这个对象获取到正在调用类的 方法、参数、返回值、异常信息、调用时间;
//    public Object aroundMethod(ProceedingJoinPoint pjp) {
//        //日志字符串可变长度字符串
//        StringBuilder stringBuilder = new StringBuilder();
//        Object obj = null;
//        try {
//            System.out.println("进入方法前");
//            obj = pjp.proceed();
//            stringBuilder.append("类:" + pjp.getTarget().getClass().getName());
//            Signature signature = (MethodSignature) pjp.getSignature();
//            stringBuilder.append("方法名称:" + signature.getName());
//            stringBuilder.append("方法参数:" + Arrays.toString(pjp.getArgs()));
//            stringBuilder.append("方法返回值:" + obj);
//            stringBuilder.append("调用时间:" + new Date());
//            System.out.println("进入方法后");
//        } catch (Throwable e) {
//            System.out.println("方法出现异常结束");
//            stringBuilder.append("异常信息:" + e.getMessage());
//        } finally {
//            System.out.println("方法正常结束");
//        }
//        //打印日志
//        System.out.println(stringBuilder.toString());
//        return obj;
//    }
}
Loger.java

注解版四大通知一起使用,执行顺序有问题,不建议使用

3.环绕通知

package com.zhanggen.log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MemberSignature;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Date;

@Aspect //切面:增强方法和切点方法的执行顺序
@Component
public class Loger {
    // 定义切点:默认id是方法名称
    @Pointcut("execution(* com.zhanggen.service.impl.AccountServiceImpl.*(..))")
    public void pt() {
    }
    //环绕通知注解配置
    @Around("pt()")
    public Object aroundMethod(ProceedingJoinPoint pjp) {
        //日志字符串可变长度字符串
        StringBuilder stringBuilder = new StringBuilder();
        Object obj = null;
        try {
            System.out.println("进入方法前");
            obj = pjp.proceed();
            stringBuilder.append("类:" + pjp.getTarget().getClass().getName());
            Signature signature = (MemberSignature) pjp.getSignature();
            stringBuilder.append("方法名称:" + signature.getName());
            stringBuilder.append("方法参数:" + Arrays.toString(pjp.getArgs()));
            stringBuilder.append("方法返回值:" + obj);
            stringBuilder.append("调用时间:" + new Date());
            System.out.println("进入方法后");
        } catch (Throwable e) {
            System.out.println("方法出现异常结束");
            stringBuilder.append("异常信息:" + e.getMessage());
        } finally {
            System.out.println("方法正常结束");
        }
        //打印日志
        System.out.println(stringBuilder.toString());
        return obj;
    }
}
环绕通知注解配置

4.测试

package com.zhanggen;

import com.zhanggen.config.SpringConfig;
import com.zhanggen.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration("classpath:applicationContext.xml") //配置文件
@ContextConfiguration(classes = SpringConfig.class)         //配置类
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void Test1() {
        accountService.save(1);
        System.out.println("===========================>");
        accountService.findAll();
        System.out.println("===========================>");
        accountService.findById(1);
    }
}
AccountServiceTest.java

 

五、转账案例

两个用户可以相互转账功能,利用AOP的环绕通知,手动实现service层事务操作;

1.创建模块,导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring</artifactId>
        <groupId>com.zhanggen</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>day05-01-transfer</artifactId>
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.15</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>

</project>
pom.xml

 

2.创建实体类

package com.zhanggen.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Account {
    private Integer aid;
    private String name;
    private Float balance;
}
Account.java

 

3.创建dao接口

package com.zhanggen.dao;

public interface AccountDao {
    //减钱
    void diff(String name, Float amount);

    //加钱
    void add(String name, Float amount);
}
AccountDao.interface

 

4.创建dao实现类

package com.zhanggen.dao.impl;

import com.zhanggen.dao.AccountDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class AccountDaoImpl implements AccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void diff(String name, Float amount) {
        System.out.println(name);
        jdbcTemplate.update("update account set balance=balance-? where name=?", amount, name);
    }

    @Override
    public void add(String name, Float amount) {
        jdbcTemplate.update("update account set balance=balance+? where name=?", amount, name);
        System.out.println(name);
    }
}
AccountDaoImpl.java

 

5.创建service接口

package com.zhanggen.service;

public interface AccountService {
    //转账
    void transfer(String sourceAcountName,String targetAcountName,Float amount);

}
AccountService.interface

 

6.创建service实现类

package com.zhanggen.service.impl;

import com.zhanggen.dao.AccountDao;
import com.zhanggen.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    @Override
    public void transfer(String sourceAcountName, String targetAcountName, Float amount) {
        accountDao.diff(sourceAcountName, amount);
        accountDao.add(targetAcountName, amount);
    }
}
AccountServiceImpl.java

 

7.加入Spring的配置

<?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: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/tx
                http://www.springframework.org/schema/tx/spring-tx.xsd
                http://www.springframework.org/schema/aop
                https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置注解扫描-->
    <context:component-scan base-package="com.zhanggen"></context:component-scan>
    <!--数据源-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://192.168.56.18:3306/dbForJava?characterEncoding=utf8"></property>
        <property name="username" value="zhanggen"></property>
        <property name="password" value="123.com"></property>
    </bean>
    <!--jdbcTemplate对象-->
    <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="druidDataSource"></constructor-arg>
    </bean>

</beans>
applicationContext.xml

 

8.测试

package com.zhanggen;
import com.zhanggen.service.AccountService;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.junit.runner.RunWith;;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml") //配置文件
public class AcountTransferTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() {
        accountService.transfer("张根", "刘龙华", 20F);
    }
}
AcountTransferTest.java

 

 

六、Spring的事务管理方式

Spring支持两种事务管理方式:编程式事务和声明式事务

  • 编程式事务就是将业务代码和事务代码放在一起书写,它的耦合性太高开发中基本不使用

  • 声明式事务其实就是将事务代码和业务代码隔离开发,然后通过一段配置让他们组装运行,最后达到事务控制的目的(使用);

其中声明式事务就是通过AOP原理实现的

 

1.Spring事务管理相关的API

Spring把事务处理的功能封装到1个工具类中称为事务管理器;

无论使用编程式事务还是声明式事务管理,事务管理器有以下API;

但是这些API基本都是由Spring是管理器底层进行调用;

我们通过注解/xml配置Sping事务管理器即可;

 

1.1.PlatformTransactionManager

PlatformTransactionManager这是Spring进行事务管理的一个根接口,我们要使用它的实现类做事务管理

Mybatis和jdbcTemplate都可以使用它的一个子类(DataSourceTransactionManager)做事务管理

 

 

1.2.TransactionDefinition

TransactionDefinition这个API是用来做事务定义的;

   

 

1.2.1.隔离级别

1.2.2.传播行为

事务传播行为指的就是当一个业务方法【被】另一个业务方法调用时,应该如何进行事务控制

a(){// a会将当前自己是否开启了事务这样一个状态传递给b
    
    b();//转账  b必须有事务        required
    b();//记录日志  b有无事务都行   supports
}

b(){ //自己   事务如何控制
    
}

传播行为配置项

1.2.3.只读性

只读事务(增 删 改不能使用,只能查询使用)

换句话说,只读事务只能用于查询方法

1.2.4.超时时长

事务超时时间, 此属性需要底层数据库的支持

它的默认值是-1, 代表不限制

 

1.3.TransactionStatus

TransactionStatus代表的是事务的当前状态;

 

1.4.总结以上3个API之间的关系

PlatformTransactionManager通过读取TransactionDefinition中定义事务信息参数来管理事务,

事务被管理之后会产生一些列的事务状态TransactionStatus;

 

七、Spring引入声明式事务

我们可以自己通过Spring的AOP去自己实现开发中的事务操作,但是在多人协作开发项目中,每个人使用不同的事务,会难以维护,且代码重用;

Spring提供了事务管理器,功能比较强大,无需重复造轮子,所以在日常开发中,我们一般通过配置Spring,实现1个项目公用的事务管理器,即采用声明式事务;

 

1.事务管理器配置思路

目标对象:service对象(已完成)

增强对象:事务控制功能,DataSourceTransactionManager(Spring已经提供)但是这个事务管理器要正常工作,需要我们传递参数:事务隔离级别 事务传播行为 事务超时时间 事务是否只读

配置切面:增强对象(开启 提交 回滚 关闭)中各个方法跟切点方法的执行顺序(顺序是固定的),我们仅仅需要指定增强对象和切点方法

 

2.配置事务管理器(XML)

2.1.使用默认配置参数

<?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: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/tx
                http://www.springframework.org/schema/tx/spring-tx.xsd
                http://www.springframework.org/schema/aop
                https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置注解扫描-->
    <context:component-scan base-package="com.zhanggen"></context:component-scan>
    <!--数据源-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://192.168.56.18:3306/dbForJava?characterEncoding=utf8"></property>
        <property name="username" value="zhanggen"></property>
        <property name="password" value="123.com"></property>
    </bean>
    <!--jdbcTemplate对象-->
    <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="druidDataSource"></constructor-arg>
    </bean>
    <!--事务管理器:这个id就写transactionManager-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
    <!--配置事务管理(transactionManager)的参数,所有参数都使用默认值-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <!--配置切点:com.zhanggen.service.impl包下的所有类的所有方法-->
        <aop:pointcut id="pt" expression="execution(* com.zhanggen.service.impl.*.*(..))"/>
        <!--
        配置切面:advisor:这是为声明式事务专门准备的1个切面
        advice-ref="带有参数的增强对象(事务管理器)”pointcut-ref="切点""
        -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>

</beans>
applicationContext.xml

2.2.指定事务管理器参数

name=“方法名”: 指的是事务参数作用方法的名称,支持模糊匹配,只要匹配成功1个,下一条就不再向下继续匹配

isolation="DEFAULT":事务隔离级别,DEFAULT表示根据数据库来定

propagation="REQUIRED":事务传播行为

timeout="-1":事务超时时长,-1永远不超时

read-only="false":事务是否只读

no-rollback-for:指定遇到什么异常时,无需回滚

rollback-for:指定遇到什么异常时,需回滚

<?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: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/tx
                http://www.springframework.org/schema/tx/spring-tx.xsd
                http://www.springframework.org/schema/aop
                https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置注解扫描-->
    <context:component-scan base-package="com.zhanggen"></context:component-scan>
    <!--数据源-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://192.168.56.18:3306/dbForJava?characterEncoding=utf8"></property>
        <property name="username" value="zhanggen"></property>
        <property name="password" value="123.com"></property>
    </bean>
    <!--jdbcTemplate对象-->
    <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="druidDataSource"></constructor-arg>
    </bean>
    <!--事务管理器:这个id就写transactionManager-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
    <!--配置事务管理(transactionManager)的参数,
        name=“方法名”:      指的是事务参数作用方法的名称,支持模糊匹配,只要匹配成功1个,下一条就不再向下继续匹配
        isolation="DEFAULT":  事务隔离级别,DEFAULT表示根据数据库来定
        propagation="REQUIRED":事务传播行为
        timeout="-1":事务超时时长,-1永远不超时
        read-only="false":事务是否只读
        no-rollback-for:指定遇到什么异常时,无需回滚
        rollback-for:指定遇到什么异常时,需回滚

    -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"
                       no-rollback-for=""/>
            <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"
                       no-rollback-for=""/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <!--配置切点:com.zhanggen.service.impl包下的所有类的所有方法-->
        <aop:pointcut id="pt" expression="execution(* com.zhanggen.service.impl.*.*(..))"/>
        <!--
        配置切面:advisor:这是为声明式事务专门准备的1个切面
        advice-ref="带有参数的增强对象(事务管理器)”pointcut-ref="切点""
        -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>

</beans>
applicationContext.xml

 

3.配置事务管理器(注解)

3.1.配置文件

<?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: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/tx
                http://www.springframework.org/schema/tx/spring-tx.xsd
                http://www.springframework.org/schema/aop
                https://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置注解扫描-->
    <context:component-scan base-package="com.zhanggen"></context:component-scan>
    <!--数据源-->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://192.168.56.18:3306/dbForJava?characterEncoding=utf8"></property>
        <property name="username" value="zhanggen"></property>
        <property name="password" value="123.com"></property>
    </bean>
    <!--jdbcTemplate对象-->
    <bean id="JdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <constructor-arg name="dataSource" ref="druidDataSource"></constructor-arg>
    </bean>
    <!--事务管理器:这个id就写transactionManager-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>
    <!--事务注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
applicationContext.xml

3.2.注解

@Transactional可以标注在类上,也可以标注在方法上,标注在方法上优先级高于类上

@Transactional //支持事务控制

4. 配置事务管理器(纯注解)

纯注解就是去xml文件;

package com.zhanggen.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration
@EnableTransactionManagement //开启事务管理
public class TranscationConfig {
// 注意方法名transactionManager固定
    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
TranscationConfig.java

 

posted on 2022-05-20 13:17  Martin8866  阅读(550)  评论(0编辑  收藏  举报