AOP与动态代理

AOP与动态代理

  • 1)AOP
    • 【1】核心概念
    • 【2】代码例子
    • 【3】上述代码AOP执行流程讲解
    • 【4】通知类型
    • 【5】@PointCut
    • 【6】通知的执行顺序
    • 【7】切入点表达式
      • 定义
      • 作用
      • 常见形式:
    • 【8】连接点
  • 2)AOP案例--记录操作日志
    • 步骤
  • 3)动态代理
  • 4)对比

1)AOP


【1】核心概念

  • 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
  • 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
  • 切入点: PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用

【2】代码例子

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * 编写AOP面向切面/方向编程,为了解决一些类或者方法需要共同处理的事务
 * 比如:以下是计算某包下所有方法的运行时间
 */
@Aspect//表示该类是AOP类
@Component//把该类交给IOC容器管理
@Slf4j
public class TimeAspect {

    @Around("execution(* com.itheima.service.*.*(..))")//切入点表达式,表示要作用到的哪些方法
    public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        //1、记录开始时间
        long begin=System.currentTimeMillis();

        //2、调用原始方法运行
        Object result= proceedingJoinPoint.proceed();

        //3、记录结束时间
        long end=System.currentTimeMillis();
        log.info(proceedingJoinPoint.getSignature()+"方法执行耗时:{}ms",end-begin);//得到原始方法的签名:proceedingJoinPoint.getSignature()

        //4、返回运行原始方法的返回值
        return result;
    }
}

【3】上述代码AOP执行流程讲解

  • 当调用目标类中的方法执行时
  • 会去执行AOP类中的代码
  • AOP类中的代码就包括原目标的方法,只不过在该方法的前后可以进行逻辑代码的编写,以实现某个目标。

内部解析:当调用目标类中的方法执行时,会去执行AOP类中的代码,这个时候会生成一个动态代理对象,这个动态代理对象通俗来讲,就是在目标类方法的基础上进行功能的扩充。以减少代码的复用性。方便大范围修改,统一管理。

【4】通知类型

  • @Around:环绕通知:此注解标注的通知方法在目标方法前、后都被执行

  • @Before:前置通知:此注解标注的通知方法在目标方法前被执行

  • @After :后置通知:此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行-

  • @AfterReturning :返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行

  • @AfterThrowing :异常后通知,此注解标注的通知方法发生异常后执行

简单代码演示:

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

@Aspect
@Component
public class MyAspect {

    @Around("execution(* com.example.MyService.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("环绕通知 - 方法执行前");
        Object result;
        try {
            result = joinPoint.proceed();
            System.out.println("环绕通知 - 方法执行后");
        } catch (Exception e) {
            System.out.println("环绕通知 - 方法抛出异常");
            throw e;
        }
        return result;
    }

    @Before("execution(* com.example.MyService.*(..))")
    public void beforeAdvice() {
        System.out.println("前置通知 - 方法执行前");
    }

    @After("execution(* com.example.MyService.*(..))")
    public void afterAdvice() {
        System.out.println("后置通知 - 方法执行后");
    }

    @AfterReturning("execution(* com.example.MyService.*(..))")
    public void afterReturningAdvice() {
        System.out.println("返回后通知 - 方法正常返回");
    }

    @AfterThrowing(pointcut = "execution(* com.example.MyService.*(..))", throwing = "ex")
    //pointcut 属性用于指定哪些方法会触发异常后通知,而 throwing 属性用于定义接收异常对象的参数名。
    public void afterThrowingAdvice(Exception ex) {
        System.out.println("异常后通知 - 方法抛出异常:" + ex.getMessage());
    }
}

注意事项:

  • @Around环绕通知需要自己调用 ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
  • @Around环绕通知方法的返回值,必须指定为object,来接收原始方法的返回值。

【5】@PointCut

AOP与动态代理_第1张图片

【6】通知的执行顺序

1.不同切面类中,默认按照切面类的类名字母排序

  • 目标方法前的通知方法:字母排名靠前的先执行
  • 目标方法后的通知方法:字母排名靠前的后执行

2.用 @Order(数字) 加在切面类上来控制顺序

  • 目标方法前的通知方法:数字小的先执行
  • 目标方法后的通知方法:数字小的后执行

【7】切入点表达式

定义

:切入点表达式:描述切入点方法的一种表达式

作用

:主要用来决定项目中的哪些方法需要加入通知

常见形式:

  1. execution(.....):根据方法的签名来匹配

AOP与动态代理_第2张图片
AOP与动态代理_第3张图片

execution(* com.*.service.*.update*(*))

  • 第一个*表示通配任何返回值
  • 第二个*表示通配第二级所有的包
  • 第三个*表示通配service包下的所有接口
  • 第四个*表示通配方法名前缀包含update的所有方法
  • 第五个*表示通配任意类型的一个参数

注意事项
根据业务需要,可以使用 且 (&&)、或()、非(!) 来组合比较复杂的切入点表达式。

书写建议

  • 所有业务方法名在命名时尽量规范,方便切入点表达式快速匹配。如: 查询类方法都是 find 开头,更新类方法都是 update开头
  • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性。
  • 在满足业务需要的前提下,尽量缩小切入点的匹配范围。如:包名匹配尽量不使用 …,使用“匹配单个包。
  1. @annotation(....) :根据注解匹配
  • 第一步:定义注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义注解
 */
@Retention(RetentionPolicy.RUNTIME)// 注解,指定该注解在运行时可被反射读取和使用;
@Target(ElementType.METHOD) //注解,指定该注解可以应用在方法上。

public @interface MyLog {
    // 可以在这里定义注解的属性,用于进一步配置日志记录的行为
    // 例如:记录级别、日志消息模板等
}

  • 第二部:定义一个AOP类
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

/**
 * 自定义切面,实现AOP功能
 */
@Aspect // 表示该类是AOP类
@Component // 把该类交给IOC容器管理
@Slf4j // 引入日志注解,方便打印日志
public class MyAspect {

    /**
     * 定义切点,该切点用于匹配被 "@全类名" 注解标记的方法
     */
    @Pointcut("@annotation(自定义注解的全类名)")
    private void pt() {
        // 此处为空方法体,仅作为一个切点的定义
    }

    /**
     * 前置通知,拦截使用指定注解标记的方法,并在方法执行前进行处理
     */
    @Before("pt()")
    public void before() {
        log.info("MyAspect......before");
    }
}

  • 第三步:在service接口的对应的实现类加上自定义的@MyLog注解
    例如
    @MyLog
    @Override
    public List<Dept> list() {
        List<Dept> deptList = deptMapper.list();
        return deptList;
    }

    @MyLog
    @Override
    public void delete(Integer id) {
        //1. 删除部门
        deptMapper.delete(id);
    }

【8】连接点

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等

  • 对于 @Around 通知,获取连接点信息只能使用 ProceedingJoinPoint

AOP与动态代理_第4张图片

  • 对于其他四种通知,获取连接点信息只能使用 JinPoint ,它是 ProceedingJoinPoint 的父类型
    AOP与动态代理_第5张图片

2)AOP案例–记录操作日志

步骤

准备:

  • 在案例工程中引入AOP的起步依赖
  • 准备好的数据库表结构,并引入对应的实体类

编码:

  • 自定义注解 @Log
  • 定义切面类,完成记录操作日志的逻辑
  • 定义插入日志信息的mapper接口
  • 需要作用的方法加上自定义注解@Log

3)动态代理

动态代理是一种实现代理模式的技术,在运行时生成代理对象,可以在代理对象的方法调用前后插入额外的逻辑。通过使用动态代理,可以对原始对象进行增强,而无需修改原始对象的代码。


4)对比

AOP和动态代理都可以用于实现类似的功能,但它们之间存在一些差异:

  • 关注点的分离:AOP通过将横切关注点与核心业务逻辑分离,提供了更好的代码模块化和可重用性。而动态代理主要用于在方法调用前后添加额外逻辑。

  • 代码修改:AOP不需要修改原始对象的代码,它通过将切面织入到应用程序中来实现功能增强。而动态代理需要使用代理类包装原始对象并添加额外的逻辑。

  • 支持的目标类型:AOP可以用于任何对象(包括类、接口、甚至其他切面),并且可以对整个类或类中的特定方法进行增强。而动态代理通常用于接口实现类,通过生成实现了目标接口的代理对象来实现代理功能。

你可能感兴趣的:(JAVAWeb,spring,boot,java,maven,后端)