第9章 AOP

AOP

§ AOP基础概念

AOP作用

AOP(Aspect Oriented Programming):面向切面编程,一种编程范式,指导开发者如何组织程序结构

作用:在不惊动原始设计的基础上为其进行功能增强

在不惊动一下代码情况下,让updatedelete也和save一样执行一万次

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

AOP核心概念

将上述代码中save中的非业务代码抽取

  • 连接点:程序执行过程中的任意位置,粒度为执行方法抛出异常设置变量等等
    • 原始方法saveupdatedelete
  • 切入点:匹配连接点的式子
    • 可以只描述一个方法,也可以是多个方法
    • 需要添加额外功能的方法如update
  • 通知:抽出来的共性功能method,写在通知类中
  • 切面:将通知和切入点绑定,两者的对应关系
public void method() {
  	Long startTime = System.currentTimeMillis();
    // 业务执行一万次
    for (int i = 0; i < 10000; i++) {
        // 业务代码
    }
    // 记录程序结束是按
    Long endTime = System.currentTimeMillis();
    // 计算时间差
    Long totalTime = endTime - startTime;
    // 输出信息
    System.out.println("执行消耗时间: " + totalTime+ "ms");
}

§ AOP入门案例

导入AOP坐标

<dependency>
  <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
  <version>1.9.6</version>
</dependency>

连接点方法

原始操作,Dao接口与实现类

public interface BookDao {
    void save();

    void update();
}
@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save");
        
    }

    public void update() {
        System.out.println("book dao update");
    }
    
}

共性功能

通知类与通知

  1. 新建包aop,在里面新建类MyAdvice

  2. 使用@Component指定其被Ioc管理

  3. 使用@Aspect指定他为切面

  4. 在配置类中添加注解@EnableAspectJAutoProxy告诉Spring该系统使用注解定义切面

// 通知类
@Component
@Aspect
public class MyAdvice {

    // 切入点
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt() {}

    // 切面
    @Before("pt()")
    // 通知
    public void method() {
        System.out.println(System.currentTimeMillis());
    }
}

定义切入点

切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法体无实际逻辑如void pt()

使用@Pointcut("execution(切入点)")注解指定切入点

绑定切入点与通知关系(切面)

使用注解@Before绑定切入点与通知

§ AOP工作流程

Y
N
spring容器启动
读取所有切面配置中的切入点
初始化bean, 判断bean中方法是否匹配到切入点
创建bean的代理对像
创建bean对象
获取代理对象, 根据代理对象模式调用原方法
获取bean, 调用原方法
  1. spring容器启动

  2. 读取所有切面配置中的切入点(即在切面使用了的,下例中的ptx切点不会被读取)

    public class MyAdvice {
        @Pointcut("execution(void com.itheima.dao.BookDao.update())")
        private void ptx() {}
        @Pointcut("execution(void com.itheima.dao.BookDao.update())")
        private void pt() {}
    
        @Before("pt()")
        public void method() {
            System.out.println(System.currentTimeMillis());
        }
    }
    
  3. 初始化bean,判定bean对应的类中的方法是否匹配到任意切入点

    • 匹配失败,创建初始对象
    • 匹配成功,创建原始对象(目标对象)的代理对象
  4. 获取bean的执行方法

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

§ AOP切入点表达式

语法格式

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

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

execution(void com.itheima.dao.BookDao.update())

描述方式二:执行com.itheima.dao.Impl包下的BookDaoImpl接口的无参数update方法

execution(void com.itheima.dao.Impl.BookDaoImpl.update())

上述两种描述方式效果一样

标准格式execution(public void com.itheima.dao.BookDao.update())

  • 动作关键词:描述切入点的动作行为,如execution表示执行到指定切入点
  • 访问修饰符:publicprivate等,可以省略
  • 返回值
  • 包名
  • 类||接口名
  • 方法名
  • 参数
  • 异常名:可以省略

通配符

  • *:单个任意符号,可以独立出现,也可以作为前缀或后缀的匹配符出现

    execution(public * com.itheima.*.UserService.find* (*))

    匹配com.itheima包下任意包中的UserService类或接口中所有find开头带有一个参数的方法

  • ..:多个让连续的任意符号,可以独立出现,常用于简化包名或参数的书写

    execution(public User com..UserService.findById(..))

    匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法(参数0或多个)

  • +:专用于匹配子类类型

    execution(* *..*Service+.*(..))

    匹配所有包下service的实现类的所有方法

书写技巧

§ AOP通知类型

通知类型有以下五类

  • 前置通知

  • 后置通知

  • 环绕通知(重点)

    • 通知方法第一个参数必须定义一个固定类型ProceedingJoinPoint的参数
    • 如果没有定义,原始操作不会执行,可以用来隔离原始操作,如权限校验
    • 该参数进行原始调用,需要抛出异常,防止原始调用的异常无法获取
    • 有返回值最后需要扔出返回值
  • 返回后通知

    • 在后置通知前运行

    • 切入方法抛异常没有返回,不会运行

  • 抛出异常后通知

    • 在后置通知前运行
@Repository
public class BookDaoImpl implements BookDao {
    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save");

    }

    public void update() {
        System.out.println("book dao update");
    }

    @Override
    public int select() {
        return 100;
    }
}
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt() {}
  
  	@Pointcut("execution(int com.itheima.dao.BookDao.select())")
    private void pt2() {}

  	// 前置
    @Before("pt()")
    public void before() {
        System.out.println("before");
    }

  	// 后置
    @After("pt()")
    public void after() {
        System.out.println("after");
    }

  	// 环绕
    @Around("pt()")
    public void around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before");
        pjp.proceed();
        System.out.println("around after");
    }
  
  	// 原始方法有返回值情况
    @Around("pt2()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("around before");
        Integer res =  pjp.proceed();
        System.out.println("around after");
      	return res + 100;
    }
  
  	@AfterReturning("pt2()")
    public void afterReturning() {
        System.out.println("afterReturning");
    }

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

§ AOP获取数据

public interface BookDao {
    String findName(int id);
}
@Repository
public class BookDaoImpl implements BookDao {
    @Override
    public String findName(int id) {
        System.out.println("id" + id);
        return "ahh";
    }
}

获取切入点方法的参数

  • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
  • ProceedJointPoint:适用于环绕通知
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(String com.itheima.dao.BookDao.findName(int))")
    private void pt3(){}

    @Before("pt3()")
    public void before2(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
    }
    
    @Around("pt3()")
    public Object around3(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        // proceed可以传入参数,即可以修改参数来执行
        args[0] = 666
        Object ret = pjp.proceed(args);
        return ret;
    }
}

获取切入点方法返回值

  • 返回后通知
    • 使用注解中的returning属性
    • 有JoinPoint时,JoinPoint必须在第一位参数
  • 环绕通知
@AfterReturning(value = "pt3()", returning = "ret")
public void afterReturning3(Object ret) {
    System.out.println(ret);
}

获取切入点方法运行异常信息

  • 抛出异常后通知
    • 同上,使用注解中throwing属性
  • 环绕通知
    • try-catch
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值