Spring使用(三)

7 篇文章 0 订阅

1. AOP简介

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

2. AOP入门案例

  • 定义接口BookDao和实现类BookDaoImpl
@Repository
public class BookDaoImpl implements BookDao {

    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 ...");
    }
}
  • 定义切面类MyAdvice
package com.lan.aop
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect // 设置当前类为AOP切面类
public class MyAdvice {
}
  • 在切面类中定义切入点,表示要切入的方法
package com.lan.aop
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAdvice {
    // 切入点表示要切入的方法,要切入的方法叫连接点
    @Pointcut("execution(void com.lan.dao.BookDao.delete())")
    private void pt(){} // 该方法名随意
}
  • 在切面类中定义通知和切面,切面是用来描述通知和切入点之间的关系
package com.lan.aop
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAdvice {
    // 切入点表示要切入的方法,要切入的方法叫连接点
    @Pointcut("execution(void com.lan.dao.BookDao.delete())")
    private void pt(){} // 该方法名随意

    @Before("pt()") // 定义切面,@Before表示通知在连接点执行前执行
    public void before(){ // 定义通知
        System.out.println(System.currentTimeMillis());
    }


    @After("pt()") // 定义切面,@After表示通知在连接点执行后执行
    public void after() {
        System.out.println("after execution");
    }

    @Around("pt()") // 定义切面,@Around表示连接点在通知的中间某一步执行
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("------------------------------");
        Long startTime = System.currentTimeMillis();
        for (int i = 0 ; i<10 ; i++) {
            //调用原始操作
            pjp.proceed();
        }
        Long endTime = System.currentTimeMillis();
        Long totalTime = endTime-startTime;
        System.out.println("执行10次消耗时间:" + totalTime + "ms");
        return null;
    }
}
  • 开启AOP功能
package com.lan.config;

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

@Configuration
@ComponentScan("com.lan")
@EnableAspectJAutoProxy // 开启AOP功能
public class SpringConfig {
}
  • 运行程序
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
// 此处的参数必须是接口类,不能是实现类BookDaoImpl,因为JDK Proxy代理的只能是接口
// JDK Proxy可以参考 https://blog.csdn.net/werewolf2017/article/details/124950536
BookDao bookDao = ctx.getBean(BookDao.class);
bookDao.delete();

3. AOP注解讲解

注解作用
@EnableAspectJAutoProxy开启注解格式AOP功能
@Aspect设置当前类为切面类
@Pointcut在切面类中设置切入点方法,value为切入点表达式
@Before设置前置通知方法
@Around设置环绕通知方法
@After设置后置通知方法
@AfterReturning设置返回后通知方法
@AfterThrowing设置抛出异常后通知方法

4. AOP实现原理

AOP实际上是通过代理模式实现的,匹配到切入点的bean,spring会为其生成代理对象,其class是Proxy;没匹配到切入点的bean,其class是对应接口的实现类。输出bean的class对象可以验证:

ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
BookDao bookDao = ctx.getBean(BookDao.class);
System.out.println(bookDao); // 无论是否匹配到切入点,对象都是BookDaoImpl
// 匹配到切入点的bean,其class是Proxy;没匹配到切入点的bean,则其class是BookDao对应的实现类
System.out.println(bookDao.getClass());

5. AOP切入点表达式

表达式格式:

动作关键字 ( 访问修饰符 返回值 包名 . / 接口名 . 方法名 ( 参数 ) 异常名)

以这个为例子

execution(public User com.lan.service.UserService.findById(int))

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

5.1 通配符

可以使用通配符描述切入点,简化切入点描述

  • * 单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution (public * com.lan.*.UserService.find*(*))
匹配 com.lan 包下的任意包中的 UserService 类或接口中所有 find 开头的带有一个参数的方法
  • ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

execution (public User com..UserService.findById(..))
匹配 com 包下的任意包中的 UserService 类或接口中所有名称为 findById 的方法
  • + :专用于匹配子类类型
execution(* *..*Service+.*(..))
这个使用率较低,描述子类的,咱们做 JavaEE 开发,继承机会就一次,使用都很慎重,所以很少
用它。 *Service+ ,表示所有以 Service 结尾的接口的子类。

6. 通知中获取参数

JoinPoint :适用于前置、后置、返回后、抛出异常后通知
@Before("pt()")
public void before(JoinPoint jp) {
    Object[] args = jp.getArgs();
    System.out.println(Arrays.toString(args));
    System.out.println("before advice ..." );
}
ProceedingJoinPoint:适用于环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) {
    Object[] args = pjp.getArgs();
    System.out.println(Arrays.toString(args));
    args[0] = 666;
    Object ret = null;
    try {
        ret = pjp.proceed(args);
    } catch (Throwable t) {
        t.printStackTrace();
    }
    return ret;
}

设置返回后通知获取原始方法的返回值,要求returning属性值必须与方法形参名相同

@AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(JoinPoint jp,String ret) {
     System.out.println("afterReturning advice ..."+ret);
}

设置抛出异常后通知获取原始方法运行时抛出的异常对象,要求throwing属性值必须与方法形参名相同

@AfterThrowing(value = "pt()",throwing = "t")
public void afterThrowing(Throwable t) {
    System.out.println("afterThrowing advice ..."+t);
}

7. Spring事务管理

Spring的事务是基于AOP实现事务管理,为了实现事务管理,提供了一个平台事务管理器PlatformTransactionManager,该接口包含了commit和rollback方法,一般使用jdbc的事务。

7.1 开启注解式事务驱动

使用@EnableTransactionManagement注解开启注解式事务驱动

@Configuration
@ComponentScan("com.lan")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

7.2 创建平台事务管理器PlatformTransactionManager

//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
    DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
    transactionManager.setDataSource(dataSource);
    return transactionManager;
}

7.3 使用@Transactional注解开启事务

在AccountService接口类加@Transactional注解表示该方法内的jdbc操作处于同一个事务中,注解也可以在实现类上加

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

7.3.1 @Transactional的属性

  • readOnly:true只读事务,false读写事务,增删改要设为false,查询设为true。
  • timeout:设置超时时间单位秒,在多长时间之内事务没有提交成功就自动回滚,-1表示不设置超 时时间。
  • rollbackFor:当出现指定异常进行事务回滚。需要注意的是,默认不配置rollbackFor的情况下,只会对Error和RuntimeException类型(包括其子类)的异常进行回滚,其它类型的异常不会回滚
  • noRollbackFor:当出现指定异常不进行事务回滚
  • rollbackForClassName等同于rollbackFor,只不过属性为异常的类全名字符串
  • noRollbackForClassName等同于noRollbackFor,只不过属性为异常的类全名字符串
  • isolation 设置事务的隔离级别

7.3.2 事务的传播

场景:在转账操作中,不管成功或失败,都记录操作日志到数据库中。代码如下

@Transactional(rollbackFor=Exception.class)
public void transfer(String out,String in ,Double money) {
    try {
        // 转账的数据库操作
    } finally {
        // 记录日志的数据库操作
        log();
    }
}

public void log() {
}

以上代码中,转账发生异常时失败时,记录日志的数据库操作也会被回滚,若将日志记录的操作放在一个新的事务中,则可以避免,这就涉及到事务的传播。修改后的代码如下

@Transactional(rollbackFor=Exception.class)
public void transfer(String out,String in ,Double money) {
    try {
        // 转账的数据库操作
    } finally {
        // 记录日志的数据库操作
        log();
    }
}

@Transactional(propagation = Propagation.REQUIRES_NEW) // 表示需要一个新的事务
public void log() {
}

@Transaction的propagation属性可以指定事务的传播方式,事务的传播方式有六种,分别是REQUIRED(默认)、REQUIRED_NEW、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER、NESTED

以上面的transfer调用log为例,讲解两个方法的事务传播的影响:

log的传播属性transfer事务log事务结果
REQUIRED开启事务T加入事务T
无事务新建事务
REQUIRED_NEW开启事务T新建事务
无事务新建事务
SUPPORTS开启事务T加入事务T
无事务无事务
NOT_SUPPORTED开启事务T无事务
无事务无事务
MANDATORY开启事务T加入事务T
无事务ERROR
NEVER开启事务TERROR
无事务无事务
NESTED设置savePoint,一旦事务回滚,事务将回滚到savePoint处,交由客户响应提交或回滚

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值