AOP, spring transaction management

Table of contents

Introduction to AOP

AOP entry case

AOP configuration management

AOP notification type

Execution efficiency of business layer interface

AOP notification to get data

Baidu network disk password data compatible processing

AOP transaction management


Introduction to AOP

What is AOP?

AOP(Aspect  Oriented  Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。
OOP(Object  Oriented  Programming)面向对象编程
AOP是在不改原有代码的前提下对其进行增强。

AOP role

作用:在不惊动原始设计的基础上为其进行功能增强
spring理念:无入侵式/无侵入式

AOP core concept

(1)前面一直在强调,Spring的AOP是对一个类的方法在不进行任何修改的前提下实现增强。
对于上面的案例中BookServiceImpl中有save , update , delete 和select 方法,
这些方法起了一个名字叫连接点

(2)在BookServiceImpl的四个方法中,update 和delete 只有打印没有计算万次执行消耗时间,
但是在运行的时候已经有该功能,那也就是说update 和delete 方法都已经被增强,
所以对于需要增强的方法起一个名字叫切入点

(3)执行BookServiceImpl的update和delete方法的时候都被添加了一个计算万次执行消耗时间的功能,
将这个功能抽取到一个方法中,换句话说就是存放共性功能的方法,起了个名字叫通知

(4)通知是要增强的内容,会有多个,切入点是需要被增强的方法,也会有多个,
那哪个切入点需要添加哪个通知,就需要提前将它们之间的关系描述清楚,
那么对于通知和切入点之间的关系描述,我们给起了个名字叫切面

(5)通知是一个方法,方法不能独立存在需要被写在一个类中,这个类我们也给起了个名字叫通知类
连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等
    在SpringAOP中,理解为方法的执行
切入点(Pointcut):匹配连接点的式子
    在SpringAOP中,一个切入点可以描述一个具体方法,也可也匹配多个方法
    一个具体的方法:如com.green.dao包下的BookDao接口中的无形参无返回值的save方法
    匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,
    所有带有一个参数的方法
通知(Advice):在切入点处执行的操作,也就是共性功能
    在SpringAOP中,功能最终以方法的形式呈现
通知类:定义通知的类
切面(Aspect):描述通知与切入点的对应关系。

 AOP entry case

需求分析
案例设定:测算接口执行效率
简化设定:在方法执行前输出当前系统时间。
思路分析
1.导入坐标(pom.xml)
2.制作连接点(原始操作,Dao接口与实现类) 
3.制作共性功能(通知类与通知)
4.定义切入点
5.绑定切入点与通知关系(切面)

Environmental preparation

pom.xml adds Spring dependency

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring_17_aop_demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>13</maven.compiler.source>
        <maven.compiler.target>13</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>

</project>
//添加BookDao和BookDaoImpl类
public interface BookDao {
    public void save();
    public void update();
    public void delete();
    public void select();
}

@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        //记录程序当前执行执行(开始时间)
        Long startTime = System.currentTimeMillis();
        //业务执行万次
        for (int i = 0; i < 10000; i++) {
            System.out.println("book dao save ...");
        }
        //记录程序当前执行时间(结束时间)
        Long endTime = System.currentTimeMillis();
        //计算时间差
        Long totalTime = endTime - startTime;
        //输出信息
        System.out.println("执行万次消耗时间:" + totalTime + "ms");
    }
    public void update() {
        System.out.println("book dao update ...");
    }
    public void delete() {
        System.out.println("book dao delete ...");
    }
    public void select() {
        System.out.println("book dao select ...");
    }
}
//创建Spring的配置类
@Configuration
@ComponentScan("com.green")
public class SpringConfig {
}
//编写App运行类
public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.save();
    }
}

AOP implementation steps

Define notification classes and notifications

 define pointcut

要增强的是update方法,该如何定义呢?
//通知类
public class MyAdvice {
    //切入点
    @Pointcut("execution(void com.green.dao.BookDao.update())") //连接点方法
    private void pt(){}
    
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

 Step 5: Make the cut sides

切面是用来描述通知和切入点之间的关系
绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置
说明:@Before翻译过来是之前,也就是说通知会在切入点方法执行之前执行
public class MyAdvice {
    //切入点
    @Pointcut("execution(void com.green.dao.BookDao.update())") //连接点方法
    private void pt(){}

    @Before("pt()") //在切入点前面执行
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

Step 6: Assign the notification class to the container and identify it as an aspect class

@Component
@Aspect
public class MyAdvice {
    //定义切入点
    @Pointcut("execution(void com.green.dao.BookDao.update())") //连接点方法
    private void pt(){}

    @Before("pt()") //在切入点前面执行
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

Step 7: Enable the annotation format AOP function

@Configuration
@ComponentScan("com.green")
@EnableAspectJAutoProxy//注解开发AOP
public class SpringConfig {
}

Step 8: Run the program

看到在执行update方法之前打印了系统时间戳,说明对原始方法进行了增强,AOP编程成功。

name

@EnableAspectJAutoProxy

@Aspect

@Pointcut

type

Configuration class annotation

class annotation

method annotation

Location

Above the configuration class definition

Above the aspect class definition

above the pointcut method definition

effect

Enable annotation format AOP function

Set the current class as an AOP aspect class

Set pointcut method

Attributes

value (default): pointcut expression

name

@Before

type

method annotation

Location

notification method definition above

effect

Set the binding relationship between the current advice method and the pointcut, the current advice method runs before the original pointcut method

AOP workflow _

由于AOP是基于Spring容器管理的bean做的增强,所以整个工作过程需要从Spring加载bean说起:

 Process 1: Spring container startup

容器启动就需要去加载bean,哪些类需要被加载呢?
需要被增强的类,如:BookServiceImpl
通知类,如:MyAdvice
注意此时bean对象还没有创建成功

Process 2: Read entry points in all aspect configurations

Process 3: Initialize beans

判定bean对应的类中的方法是否匹配到任意切入点
    注意第1步在容器启动的时候,bean对象还没有被创建成功。
    要被实例化bean对象的类中的方法和切入点进行匹配

匹配失败,创建原始对象,如UserDao
    匹配失败说明不需要增强,直接调用原始对象的方法即可。
匹配成功,创建原始对象(目标对象)的代理对象,如: BookDao
    匹配成功说明需要对其进行增强
    对哪个类做增强,这个类对应的对象就叫做目标对象
    因为要对目标对象进行功能增强,而采用的技术是动态代理,所以会为其创建一个代理对象
    最终运行的是代理对象的方法,在该方法中会对原始方法进行功能增强

Process 4: Get the bean execution method

获取的bean是原始对象时,调用方法并执行,完成操作
获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作

Verify that the container is a proxy object

如果目标对象中的方法会被增强,那么容器中将存入的是目标对象的代理对象
如果目标对象中的方法不被增强,那么容器中将存入的是目标对象本身。

Verification idea

1.要执行的方法,不被定义的切入点包含,即不要增强,打印当前类的getClass()方法
2.要执行的方法,被定义的切入点包含,即要增强,打印出当前类的getClass()方法
3.观察两次打印的结果

Step 1: Modify the App class to obtain the type of the class 

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        System.out.println(bookDao);
        System.out.println(bookDao.getClass());
    }
}

Step 2: Modify the MyAdvice class without enhancement

@Component
@Aspect
public class MyAdvice {
    //定义切入点
    @Pointcut("execution(void com.green.dao.BookDao.update1())") //连接点方法
    private void pt(){}

    @Before("pt()") //在切入点前面执行
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}
因为定义的切入点中,被修改成update1 ,所以BookDao中的update方法在执行的时候,就不会被增强
所以容器中的对象应该是目标对象本身。

Step 3: Run the program

 Step 4: Modify the MyAdvice class , enhance

因为定义的切入点中,被修改成update ,所以BookDao中的update方法在执行的时候,就会被增强
所以容器中的对象应该是目标对象的代理对象
@Component
@Aspect
public class MyAdvice {
    //定义切入点
    @Pointcut("execution(void com.green.dao.BookDao.update())") //连接点方法
    private void pt(){}

    @Before("pt()") //在切入点前面执行
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

Step 5: Run the program

 AOP core concept

目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

AOP configuration management

AOP pointcut expression

对于AOP中切入点表达式,总共会学习三个内容,分别是 语法格式 、 通配符和书写技巧 

grammatical format

切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述方式

描述方式一:执行com.green.dao包下的BookDao接口中的无参数update方法

 

描述方式二:执行com.green.dao.impl包下的BookDaoImpl类中的无参数update方法

 

因为调用接口方法的时候最终运行的还是其实现类的方法,所以上面两种描述方式都是可以的。
对于切入点表达式的语法为:
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)  异常名)
  execution(public User com.green.service.UserService.findById(int))

execution:动作关键字,描述切入点的行为动作,例如execution表示执行到指定切入点
public:访问修饰符,还可以是public,private等,可以省略
User:返回值,写返回值类型
com.green.service:包名,多级包使用点连接
UserService:类/接口名称
findById:方法名
int:参数,直接写参数的类型,多个类型用逗号隔开异常名:方法定义中抛出指定异常,可以省略

 wildcard

* :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.green.*.UserService.find*(*))
匹配com.green包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

.. :多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

+ :专用于匹配子类类型
execution(* *..*Service+.*(..))
*Service+,表示所有以Service结尾的接口的子类。

 

 

 writing skills

All codes are developed in accordance with standard specifications, otherwise the following techniques will be invalid

The description entry point usually describes the interface , not the implementation class . If it is described to the implementation class, there will be tight coupling
Access control modifiers are used for interface development. Public descriptions are used ( access control modifier descriptions can be omitted )
. The return value type is for Add, delete, and modify classes use precise types to speed up matching. For query classes, use * wildcards to quickly describe
the package name . Try not to use .. matching , the efficiency is too low, and * is often used for single package description matching, or precise matching

The name of the interface name / class name is matched with the module . For example, UserService is written as *Service , and the interface name of the business layer is bound.

The method name is written with verbs for precise matching , and nouns are used for matching. For example, getById is written as getBy , selectAll is written as selectAll

Parameter rules are more complicated, flexible adjustments based on business methods
usually do not use exceptions as matching rules

 AOP notification type

type introduction

前置通知
后置通知
环绕通知(重点)
返回后通知(了解)
抛出异常后通知(了解)

(1)前置通知,追加功能到方法执行前,类似于在代码1或者代码2添加内容
(2)后置通知,追加功能到方法执行后,不管方法执行的过程中有没有抛出异常都会执行,类似于在代码5添加内容
(3)返回后通知,追加功能到方法执行后,只有方法正常执行结束后才进行,类似于在代码3添加内容,
如果方法执行抛出异常,返回后通知将不会被添加
(4)抛出异常后通知,追加功能到方法抛出异常后,只有方法执行出异常才进行,类似于在代码4添加内容,
只有方法抛出异常后才会被添加
(5)环绕通知,环绕通知功能比较强大,它可以追加功能到方法执行的前后

 Environmental preparation

pom.xml adds Spring dependency

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>

Add BookDao and BookDaoImpl classes

 Create a Spring configuration class

@Configuration
@ComponentScan("com.green")
@EnableAspectJAutoProxy//注解开发AOP
public class SpringConfig {
}

Create a notification class

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.green.dao.BookDao.update())")
    private void pt(){}

    @Pointcut("execution(int com.green.dao.BookDao.select())")
    private void pt2(){}

    //切入点之前
//    @Before("pt()")
    public void before(){
        System.out.println("before advice...");
    }

    //切入点之后
//    @After("pt()")
    public void after(){
        System.out.println("after advice...");
    }

    //环绕
//    @Around("pt()")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice...");
        //表示对原始操作的调用
        pjp.proceed();
        System.out.println("around after advice...");
    }

//    @Around("pt2()")
    public Object aroundSelect(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before advice...");
        //表示对原始操作的调用
        Object result = pjp.proceed();
        System.out.println("around after advice...");
        //需要将返回值扔回去
        return result;
    }

//    @AfterReturning("pt2()")
    public void afterReturning(){
        System.out.println("afterReturning advice...");
    }

    @AfterThrowing("pt2()")
    public void afterThrowing(){
        System.out.println("afterThrowing advice...");
    }
}

Write App running class

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        int num = bookDao.select();
        //int i = 1 / 0;
        System.out.println(num);
    }
}

name

@After

type

method annotation

Location

notification method definition above

effect

Set the binding relationship between the current advice method and the pointcut, the current advice method runs after the original pointcut method

name

@AfterReturning

type

method annotation

Location

notification method definition above

effect

Set the binding relationship between the current notification method and the entry point. The current notification method is executed after the original entry point method is executed normally.

name

@AfterThrowing

type

method annotation

Location

notification method definition above

effect

Set the binding relationship between the current notification method and the entry point. The current notification method is executed after the original entry point method throws an exception.

name

@Around

type

method annotation

Location

notification method definition above

effect

Set the binding relationship between the current advice method and the pointcut, the current advice method runs before and after the original pointcut method

 Surround notification considerations

1.  环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,
进而实现原始方法调用前后同时添加通知
2.  通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
3.  对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,最好设定为Object类型
4.  原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
5.  由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理Throwable异常

Execution efficiency of business layer interface

demand analysis

需求:任意业务层接口执行均可显示其执行效率(执行时长)
具体实现的思路:
(1)  开始执行方法之前记录一个时间
(2)  执行方法
(3)  执行完方法之后记录一个时间
(4)  用后一个时间减去前一个时间的差值,就是我们需要的结果。

pom.xml adds Spring dependency

    <dependencies>
        <!--spring依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <!--数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--spring-jdbc整合-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</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.2.10.RELEASE</version>
        </dependency>

        <!--解析切入点表达式依赖-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
    </dependencies>

添加AccountService、AccountServiceImpl、AccountDao与Account类
public interface AccountService {

    void save(Account account);//添加

    void delete(Integer id);//删除

    void update(Account account);//修改

    List<Account> findAll();//查询所有

    Account findById(Integer id);//根据id查询数据
}

@Service
public class AccountServiceImpl implements AccountService {

    //自动装配
    @Autowired
    private AccountDao accountDao;

    @Override
    public void save(Account account) {
        accountDao.save(account);
    }

    @Override
    public void delete(Integer id) {
        accountDao.delete(id);
    }

    @Override
    public void update(Account account) {
        accountDao.update(account);
    }

    @Override
    public List<Account> findAll() {
        return accountDao.findAll();
    }

    @Override
    public Account findById(Integer id) {
        return accountDao.findById(id);
    }
}

public interface AccountDao {

    //添加数据
    @Insert("insert into tbl_account(name,money)values(#{name},#{money})")
    void save(Account account);

    //删除数据
    @Delete("delete from tbl_account where id = #{id} ")
    void delete(Integer id);

    //根据id修改数据
    @Update("update tbl_account set name = #{name} , money = #{money} where id = #{id} ")
    void update(Account account);

    //查询所有数据
    @Select("select * from tbl_account")
    List<Account> findAll();

    //根据id查询数据
    @Select("select * from tbl_account where id = #{id} ")
    Account findById(Integer id);
}

public class Account implements Serializable {

    private Integer id;
    private String name;
    private Double money;
//setter..getter..toString方法省略
}

Provide a jdbc .properties under resources

 Create related configuration classes

Spring配置类:SpringConfig 
@Configuration
@ComponentScan("com.green") //包扫描
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class}) //导入配置类
@EnableAspectJAutoProxy
public class SpringConfig {
}

//JdbcConfig配置类

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}

//mybatis配置类
public class MybatisConfig {
    //定义bean,SqlSessionFactoryBean,用于产生SqlSessionFactory对象
    @Bean
    public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        //设置模型类的别名扫描
        ssfb.setTypeAliasesPackage("com.green.domain");
        //设置数据源
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    //定义bean,返回MapperScannerConfigurer对象
    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.green.dao");
        return msc;
    }
}

Write a test Spring to integrate Junit

 function development

Create an AOP notification class

该类要被Spring管理,需要添加@Component
要标识该类是一个AOP的切面类,需要添加@Aspect
配置切入点表达式,需要添加一个方法,并添加@Pointcut
@Component
@Aspect
public class ProjectAdvice {

    //匹配业务层的所有接口
    @Pointcut("execution(* com.green.service.*Service.*(..))")
    private void servicePt() {}
    
    @Around("ProjectAdvice.servicePt()")
    public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
        //一次执行的签名时间
        Signature signature = pjp.getSignature();
        String className = signature.getDeclaringTypeName();//类型名
        String methodName = signature.getName();//方法名
        long start = System.currentTimeMillis();

        for (int i = 0; i < 10000; i++) {
            pjp.proceed();
        }

        long end = System.currentTimeMillis();
        System.out.println("万次执行: " + className + "." + methodName + "===>" + (end - start) + "ms");
    }
}

AOP notification to get data

获取参数
获取返回值 
获取异常

获取切入点方法的参数,所有的通知类型都可以获取参数
    JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    ProceedingJoinPoint:适用于环绕通知
获取切入点方法返回值
    返回后通知
    环绕通知
获取切入点方法运行异常信息
    抛出异常后通知
    环绕通知

Environmental preparation

pom.xml adds Spring dependency

添加BookDao和BookDaoImpl类
public interface BookDao {
    String findName(int id,String password);
}

@Repository
public class BookDaoImpl implements BookDao {
    @Override
    public String findName(int id,String password) {
        System.out.println("id:" + id);
        return "ok";
    }
}
创建Spring的配置类
@Configuration
@ComponentScan("com.green")
@EnableAspectJAutoProxy
public class SpringConfig {
}
编写App运行类
public class App {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = context.getBean(BookDao.class);
        String name = bookDao.findName(100,"root");
//        if (true)throw new NullPointerException();//异常测试
        System.out.println(name);
    }
}
编写通知类
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.green.dao.BookDao.findName(..))")
    private void pt() {
    }

    //    @Before("pt()")
    public void before(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice ...");
    }

    //    @After("pt()")
    public void after(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("after advice ...");
    }

    //    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;

        Object ret = pjp.proceed(args);
        return ret;
    }

    //    @AfterReturning(value = "pt()", returning = "ret")
    public void afterReturning(Object ret) {
        System.out.println("afterReturning advice ..." + ret);
    }
    
    @AfterThrowing(value = "pt()", throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("afterThrowing advice ..." + t);
    }
}
使用JoinPoint的方式获取参数适用于前置 、 后置 、 返回后 、 抛出异常后 通知

get return value

 

 get exception

Baidu network disk password data compatible processing

需求分析
需求:  对百度网盘分享链接输入密码时尾部多输入的空格做兼容处理。
①:在业务方法执行之前对所有的输入参数进行格式处理—— trim()  
②:使用处理后的参数调用原始方法——环绕通知中存在对原始方法的调用

Environmental preparation

pom.xml adds Spring dependency

<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
</dependencies>
添加ResourcesService,ResourcesServiceImpl,ResourcesDao和ResourcesDaoImpl类
public interface ResourcesService {
    public boolean openURL(String url ,String password);
}

@Service
public class ResourcesServiceImpl implements ResourcesService {
    @Autowired
    private ResourcesDao resourcesDao;
    public boolean openURL(String url, String password) {
        return resourcesDao.readResources(url,password);
    }
}

public interface ResourcesDao {
    boolean readResources(String url, String password);
}

@Repository
public class ResourcesDaoImpl implements ResourcesDao {
    public boolean readResources(String url, String password) {
        System.out.println(password.length());
        //模拟校验
        return password.equals("root");
    }
}

创建Spring的配置类
@Configuration
@ComponentScan("com.green")
@EnableAspectJAutoProxy
public class SpringConfig {
}
运行类
public class AppCase {
    public static void main(String[] args) {
        ApplicationContext cxt = new AnnotationConfigApplicationContext(SpringConfig.class);
        ResourcesService resourcesService = cxt.getBean(ResourcesService.class);
        boolean flag = resourcesService.openURL("http://pan.baidu.com/haha", "root");
        System.out.println(flag);
    }
}

通知类
@Component
@Aspect
public class DataAdvice {
    @Pointcut("execution(boolean com.green.service.*Service.*(*,*))")
    private void servicePt(){}

    @Around("DataAdvice.servicePt()")
    public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        for (int i = 0; i < args.length; i++) {
            //判断参数是不是字符串
            if(args[i].getClass().equals(String.class)){
                args[i] = args[i].toString().trim();
            }
        }
        Object ret = pjp.proceed(args);
        return ret;
    }
}

AOP transaction management

Introduction to Spring Transactions

事务作用:在数据层保障一系列的数据库操作同成功同失败
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
举个简单的例子,
转账业务会有两次数据层的调用,一次是加钱一次是减钱
把事务放在数据层,加钱和减钱就有两个事务
没办法保证加钱和减钱同时成功或者同时失败
这个时候就需要将事务放在业务层进行处理。
Spring为了管理事务,提供了一个平台事务管理器PlatformTransactionManager

PlatformTransactionManager只是一个接口,Spring还为其提供了一个具体的实现:

 

 Transfer Case - Demand Analysis

需求:  实现任意两个账户间转账操作
需求微缩:  A账户减钱,B账户加钱
实现步骤:
①:数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)
②:业务层提供转账操作(transfer),调用减钱与加钱的操作
③:提供2个账号和操作金额执行转账操作
④:基于Spring整合MyBatis环境搭建上述操作

Transfer case - environment construction

Step 1: Prepare database tables

 Step 2: Create a project and import the jar package

    <dependencies>
        <!--spring依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>
        <!--数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <!--spring-jdbc整合-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.10.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</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.2.10.RELEASE</version>
        </dependency>
    </dependencies>

Step 3: Create a model class based on

 Step 4: Create Dao interface

 Step 5: Create Service interface and implementation class

 Step 6: Add jdbc.properties file

 Step 7: Create JdbcConfig configuration class

 Step 8: Create a MybatisConfig configuration class

 

 Step 9: Create a SpringConfig configuration class

 Step 10: Write the test class

 affairs management

上述环境,运行单元测试类,会执行转账操作, Tom 的账户会减少100,Jerry 的账户会加100。
这是正常情况下的运行结果,但是如果在转账的过程中出现了异常,如:
@Service
public class AccountServiceImpl implements AccountService {

    //自动装配
    @Autowired
    private AccountDao accountDao;

    @Override
    public void transfer(String out, String in, Double money) {
        accountDao.inMoney(in, money);
//        int i = 1 / 0;
        accountDao.outMoney(out, money);
    }
}

①:程序正常执行时,账户金额A减B加,没有问题
②:程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败

 Step 1: Add annotations to methods that need to be managed by transactions

@Service
public class AccountServiceImpl implements AccountService {

    //自动装配
    @Autowired
    private AccountDao accountDao;
    @Transactional
    public void transfer(String out, String in, Double money) {
        accountDao.inMoney(in, money);
//        int i = 1 / 0;
        accountDao.outMoney(out, money);
    }
}
注意:
写在接口类上,该接口的所有实现类的所有方法都会有事务
写在接口方法上,该接口的所有实现类的该方法都会有事务
写在实现类上,该类中的所有方法都会有事务
写在实现类方法上,该方法上有事务
建议写在实现类或实现类的方法上

Step 2: Configure the transaction manager in the JdbcConfig class

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }

    //事务管路
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

Step 3 : Turn on transaction annotations

@Configuration
@ComponentScan("com.green") //包扫描
//@PropertySource:加载类路径jdbc.properties文件
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class}) //导入配置类
@EnableTransactionManagement    //事务管理
public class SpringConfig {
}

name

@EnableTransactionManagement

type

Configuration class annotation

Location

Above the configuration class definition

effect

Set to enable annotation transaction support in the current Spring environment

name

@Transactional

type

Interface Annotation Class Annotation Method Annotation

Location

Above the Business Layer Interface Above the Business Layer Implementation Class Above the Business Method

effect

Add transactions for the current business layer method (if set above the class or interface, all will add transactions)

 Spring transaction roles

事务管理员 和 事务协调员 
事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法

1. 未开启Spring事务之前:

AccountDao的outMoney因为是修改操作,会开启一个事务T1 
AccountDao的inMoney因为是修改操作,会开启一个事务T2
AccountService的transfer没有事务
    运行过程中如果没有抛出异常,则T1和T2都正常提交,数据正确
    如果在两个方法中间抛出异常,T1因为执行成功提交事务,T2因为抛异常不会被执行就会导致数据出现错误

2.  开启Spring的事务管理后

transfer上添加了@Transactional注解,在该方法上就会有一个事务T 
AccountDao的outMoney方法的事务T1加入到transfer的事务T中
AccountDao的inMoney方法的事务T2加入到transfer的事务T中
这样就保证他们在同一个事务中,当业务层中出现异常,整个事务就会回滚,保证数据的准确性。
注意:
目前的事务管理是基于DataSourceTransactionManager和SqlSessionFactoryBean使用的是同一个数据源

 Spring事务属性

事务配置

 上面这些属性都可以在@Transactional 注解的参数上进行设置。

出现这个问题的原因是,Spring的事务只会对Error异常 和RuntimeException异常及其子类进行事务回滚
其他的异常类型是不会回滚的,对应IOException不符合上述条件所以不回滚
此时就可以使用rollbackFor属性来设置出现IOException异常不回滚
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    @Transactional(rollbackFor = {IOException.class})
    public void transfer(String out, String in, Double money) throws IOException {
        accountDao.outMoney(out, money);
//int i = 1/0; 
        if (true) {
            throw new IOException(); 
        }
        accountDao.inMoney(in, money);
    }
}
rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串
noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串
isolation设置事务的隔离级别
DEFAULT  :默认隔离级别,  会采用数据库的隔离级别
READ_UNCOMMITTED  :  读未提交
READ_COMMITTED  :  读已提交
REPEATABLE_READ  :  重复读取
SERIALIZABLE:  串行化

 转账业务追加日志案例

需求分析
在前面的转案例的基础上添加新的需求,完成转账后记录日志。
需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
需求微缩:A账户减钱,B账户加钱,数据库记录日志

①:基于转账操作案例添加日志模块,实现数据库中记录日志
②:业务层转账操作(transfer),调用减钱、加钱与记录日志功能
无论转账操作是否成功,均进行转账操作的日志留痕

步骤1:创建日志表

 步骤2:添加LogDao接口

 步骤3:添加LogService接口与实现类

 步骤4:在转账的业务中添加记录日志

public interface AccountService {
    /**
     * 转账操作
     *
     * @param out   传出方
     *  @param  in  转入方
     * @param money 金额
     */
//配置当前接口方法具有事务
    public void transfer(String out, String in, Double money) throws IOException;
} 

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao; 
    @Autowired
    private LogService logService; 

    @Transactional
    public void transfer(String out, String in, Double money) {
        try {
            accountDao.outMoney(out, money);
            accountDao.inMoney(in, money);
        } finally {
            logService.log(out, in, money); }
    }
}

步骤5:行程序

当程序正常运行,tbl_account表中转账成功,tbl_log表中日志记录成功
当转账业务之间出现异常(int i =1/0),转账失败,tbl_account成功回滚,但是tbl_log表未添加数据
这个结果和我们想要的不一样,什么原因?该如何解决?
失败原因:日志的记录与转账操作隶属同一个事务,同成功同失败最终效果:无论转账操作是否成功,日志必须保留

事务传播行为

 1.修改logService改变事务的传播行为

 2.事务传播行为的可选值

Guess you like

Origin blog.csdn.net/weixin_52270382/article/details/130564051