AOP、spring事务管理

news2024/11/23 2:41:28

目录

AOP简介

AOP入门案例

AOP配置管理

AOP通知类型

业务层接口执行效率

AOP通知获取数据

百度网盘密码数据兼容处理

AOP事务管理


AOP简介

什么是AOP?

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

AOP作用

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

AOP核心概念

(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入门案例

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

环境准备

pom.xml添加Spring依赖

<?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实现步骤

定义通知类和通知

 定义切入点

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

 步骤5:作切面

切面是用来描述通知和切入点之间的关系
绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置
说明:@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());
    }
}

步骤6:将通知类配给容器并标识其为切面类

@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());
    }
}

步骤7:开启注解格式AOP功能

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

步骤8:行程序

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

名称

@EnableAspectJAutoProxy

@Aspect

@Pointcut

类型

配置类注解

类注解

方法注解

位置

配置类定义上方

切面类定义上方

切入点方法定义上方

作用

开启注解格式AOP功能

设置当前类为AOP切面类

设置切入点方法

属性

value(默认):切入点表达式

名称

@Before

类型

方法注解

位置

知方法定义上方

作用

设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行

AOP工作流程

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

 流程1:Spring容器启动

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

流程2:读取所有切面配置中的切入点

流程3:初始化bean

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

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

流程4:获取bean执行方法

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

验证容器中是否为代理对象

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

验证思路

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

步骤1:修改App,取类的类型 

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());
    }
}

步骤2:修改MyAdvice类,不增强

@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方法在执行的时候,就不会被增强
所以容器中的对象应该是目标对象本身。

步骤3:行程序

 步骤4:修改MyAdvice,增强

因为定义的切入点中,被修改成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());
    }
}

步骤5:行程序

 AOP核心概念

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

AOP配置管理

AOP切入点表达式

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

语法格式

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

描述方式一:执行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:参数,直接写参数的类型,多个类型用逗号隔开异常名:方法定义中抛出指定异常,可以省略

 通配符

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

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

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

 

 

 书写技巧

所有代码按照标准规范开发,否则以下技巧全部失效

描述切入点通常描述接口,而不描述实现类,如果描述到实现类,就出现紧耦合了
访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述
返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配

接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名

方法名书写以动词进行精准匹配,名词采用匹配,例如getById书写成getBy,selectAll书写成selectAll

参数规则较为复杂,根据业务方法灵活调整
通常不使用异常作为匹配规则

 AOP通知类型

类型介绍

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

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

 环境准备

pom.xml添加Spring依赖

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

添加BookDaoBookDaoImpl

 创建Spring的配置类

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

创建通知类

@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...");
    }
}

编写App运行类

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);
    }
}

名称

@After

类型

方法注解

位置

知方法定义上方

作用

设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行

@AfterReturning

类型

方法注解

位置

知方法定义上方

作用

设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法正常执行完毕后执行

名称

@AfterThrowing

类型

方法注解

位置

通知方法定义上方

作用

设置当前通知方法与切入点之间绑定关系,当前通知方法在原始切入点方法运行抛出异常执行

名称

@Around

类型

方法注解

位置

知方法定义上方

作用

设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行

 环绕通知注意事项

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

业务层接口执行效率

需求分析

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

pom.xml添加Spring依赖

    <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方法省略
}

resources下提供一个jdbc.properties

 创建相关配置类

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;
    }
}

编写Spring整合Junit的测试类

 功能开发

创建AOP通知类

该类要被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通知获取数据

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

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

环境准备

pom.xml添加Spring依赖

添加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的方式获取参数适用于前置 、 后置 、 返回后 、 抛出异常后 通知

获取返回值

 

 获取异常

百度网盘密码数据兼容处理

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

环境准备

pom.xml添加Spring依赖

<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事务管理

Spring事务简介

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

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

 

 转账案例-需求分析

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

转账案例-环境搭建

步骤1:备数据库表

 步骤2:创建项目导入jar

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

步骤3:据表创建模型类

 步骤4:创建Dao接口

 步骤5:创建Service接口和实现类

 步骤6:添加jdbc.properties文件

 步骤7:创建JdbcConfig配置类

 步骤8:创建MybatisConfig配置类

 

 步骤9:创建SpringConfig配置类

 步骤10:编写测试类

 事务管理

上述环境,运行单元测试类,会执行转账操作, 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加,没有问题
②:程序出现异常后,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败

 步骤1:在需要被事务管理的方法上添加注解

@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);
    }
}
注意:
写在接口类上,该接口的所有实现类的所有方法都会有事务
写在接口方法上,该接口的所有实现类的该方法都会有事务
写在实现类上,该类中的所有方法都会有事务
写在实现类方法上,该方法上有事务
建议写在实现类或实现类的方法上

步骤2: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;
    }

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

步骤3:开启事务注解

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

名称

@EnableTransactionManagement

类型

配置类注解

位置

配置类定义上方

作用

设置当前Spring环境中开启注解式事务支持

名称

@Transactional

类型

接口注解 类注解 方法注解

位置

业务层接口上方 业务层实现类上方 业务方法上方

作用

为当前业务层方法添加事务(如果设置在类或接口上方则类或接口中所有方法均添加事务)

 Spring事务角色

事务管理员 和 事务协调员 
事务管理员:发起事务方,在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.事务传播行为的可选值

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/507525.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML <area> 标签

实例 带有可点击区域的图像映射: <img src="planets.jpg" border="0" usemap="#planetmap" alt="Planets" /><map name="planetmap" id="planetmap"><area shape="circle" coords=&q…

AHUT周赛2

1.A - Mahmoud and Ehab and the MEX Problem - A - Codeforces 核心在于x之前的数肯定是有的&#xff0c;x是没有的 所以从0开始一直到x&#xff0c;如果哪个数没有就加上哪个数(操作数1)&#xff0c;如果有x就删去x(操作数1) AC代码&#xff1a; #include<iostream>…

【UML】

文章目录 1.uml图2.类图3.类合类之间关系&#xff1a;泛化关系4.类合类之间关系&#xff1a;实现关系5.类合类之间关系&#xff1a;关联关系6. 聚合关系 组合关系 依赖关系6.1聚合关系6.2 组合关系6.3 依赖关系 7.用例图8.时序图9.状态图10.活动图 1.uml图 2.类图 新建类图 新…

【超全解决方法】关于anaconda navigator启动时一直卡在 loading applications 的问题

问题描述 点开 anaconda navigator 一直处于以下页面&#xff0c;且过了很长时间都加载不出来。 &#xff08;至于为什么会出现这样的问题&#xff0c;原因是我更新了anaconda navigator 之后就出现一大堆问题&#xff0c;最终点开anaconda navigator却一直开在下面的页面&am…

【sop】基于灵敏度分析的有源配电网智能软开关优化配置(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【类和对象(中)】六大默认成员函数

文章目录 前言一、&#x1f33a;构造函数&#xff08;重点&#x1f33a;&#xff09;1.构造函数的特性 二、&#x1f33a;析构函数&#xff08;重点&#x1f33a;&#xff09;1.析构函数的特性 三、&#x1f33a;拷贝构造函数 &#xff08;重点&#x1f33a;&#xff09;1.拷贝…

Powerlink协议在嵌入式linux上的移植和测试(电脑和linux板通信实验)

使用最新的openPOWERLINK 2.7.2源码&#xff0c;业余时间搞定了Powerlink协议在嵌入式linux上的移植和测试&#xff0c;并进行了下电脑和linux开发板之间的通信实验。添加了一个节点配置&#xff0c;跑通了源码中提供的主站和从站的两个demo。这里总结下移植过程分享给有需要的…

4。计算机组成原理(4)CPU

嵌入式软件开发&#xff0c;非科班专业必须掌握的基本计算机知识 核心知识点&#xff1a;数据表示和运算、存储系统、指令系统、总线系统、中央处理器、输入输出系统 CPU&#xff08;中央处理器&#xff09;是计算机的核心部件&#xff0c;它主要负责执行计算机指令&#xff0c…

万字超详细的Java图书管理系统

&#x1f495;”生命中的每个人都是一个故事&#xff0c;而每个故事都值得被讲述“&#x1f495; &#x1f386;作者&#xff1a;不能再留遗憾了&#x1f386; &#x1f43c;专栏&#xff1a;Java学习&#x1f43c; &#x1f697;该文章主要内容&#xff1a;用Java实现简单的图…

三、PEMFC基础之组件间热传导

三、PEMFC基础之组件间热传导 一、理论基础二、编程实践 一、理论基础 热传导主要基于傅里叶热传导定律。在燃料电池中&#xff0c;除了各组件内部的热传导外&#xff0c;还有冷却流体与双极板的对流换热。公式略。 燃料电池内部稳态导热&#xff1a; d 2 T d x 2 q i n t k…

iMazing2软件最新版本功能技术参数详细介绍

这里有iMazing 的产品概述、功能特性、技术参数等详细介绍&#xff0c; 可以帮助您快速入门&#xff0c;了解iMazing的功能。不管是 iPhone、iPad 或 iPod Touch 设备&#xff0c;只要使用 USB 电缆将设备连接到计算机&#xff0c;就可以处理不同类型的数据。 自动备份 iMazi…

【Linux】基础IO——文件描述符

目录 什么是文件描述符标准输入、输出、错误的返回值类型FILE*的理解进程中文件描述符的分配规则重定向的原理重定向的实际使用方法dup2 如何理解缓冲区 什么是文件描述符 在基础IO的上一篇博客里有提到过&#xff0c;系统调用open与close的返回值问题&#xff1a; 成功返回文…

PyQGIS中一次性加载多个shp文件

目录 遍历添加多个图层 打印图层列表清单 打开QGIS Desktop 3.22.16&#xff0c;点击菜单栏 【设置】——>【Python控制台】 在Python控制台中点击【显示编辑器】按钮&#xff0c;打开Python编辑器 点击Python编辑器的第一个按钮 【打开脚本文件】&#xff0c;选择加载遍历…

2023年继续使用WordPress的6个最重要原因

为什么要使用 WordPress&#xff1f;我的网站不够好吗&#xff1f;为什么我需要从另一个平台切换到 WordPress&#xff1f; 在本文中&#xff0c;我们将分享您应该使用 WordPress 的最重要原因。我们还将涵盖您可以使用 WordPress 创建的所有不同类型的网站&#xff0c;并展示…

c高级(常用命令及软件安装与下载)

初始工作路径不在家目录下&#xff0c;在不切换路径的情况下&#xff0c;在家目录下创建一个subdir目录&#xff0c;在subdir这个目录下&#xff0c;创建subdir1和subdir2&#xff0c;并且把/etc/passwd拷贝到subdir1中&#xff0c;把/etc/group文件拷贝到subdir2中&#xff0c…

开源趣事~ 记给 OpenHarmony 提 PR 的那些事

大家好哇&#xff0c;许久不见&#xff0c;也感谢大家这么久一直以来的关注&#xff0c;也感谢在短视频盛行的今天&#xff0c;你们还能静下心来坚守文字的阵地。 说到这次的主题&#xff0c;参加鸿蒙项目的开源&#xff0c;也是小编第一次拥抱开源&#xff0c;就像是别人有困…

vue脚手架+elementUI,实现登录用户时的Loading...窗口

文章目录 App.vuevuex全局变量登陆成功Login组件使用AboutMe组件中关闭 登录失败情况login组件中关闭 改为aop思想的请求拦截器 App.vue 为了全局通用控制此标签&#xff0c;所以我建议把他放到App.vue文件中 <!--全局加载ing&#xff0c;保证不会在转换组件时被销毁-->…

Day965.从持续集成到持续部署 -遗留系统现代化实战

从持续集成到持续部署 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于从持续集成到持续部署的内容。 只有做好任务分解和小步提交&#xff0c;才能放心大胆地 PUSH 代码&#xff0c;触发持续构建&#xff1b; 只有通过质量门禁&#xff0c;才能得到一个有信心的制…

【Spring MVC】Spring MVC的执行流程以及运行原理

文章目录 一、 什么是MVC&#xff1f;二、什么是SpringMVC&#xff1f;三、SpringMVC中的核心组件四、SpringMVC的执行流程五、关于DispatcherServlet的配置说明六、关于SpringMVC的配置文件以及常用部分注解解释七、参考资料 一、 什么是MVC&#xff1f; MVC 是 Model、View …

软件工程的基础

软件危机软件工程软件工程是将系统化的&#xff0c;严格约束的&#xff0c;可量化的方法应用于软件的开发&#xff0c;运行和维护&#xff0c;将工程应用于软件。 软件工程的三个要素&#xff1a;方法&#xff0c;工具&#xff0c;过程软件的生命周期&#xff0c;是指从从软…