AOP 熟悉的陌生人

前序

AOP,Spring中另一个重要的概念,中文可以叫做 面向切面编程。它是为 解耦 而生的。

AOP 的基本思想是 将 横切 关注点(如事务管理、安全检查等那些 复杂的代码)从 业务逻辑 中分离出来,这样的方式减少了开发中重复的代码,让代码更加简洁,降低了开发人员的工作量,同时对于后期的维护带来一定的便利。
简单的说 面向切面 就是对 业务逻辑 的 各个部分 进行隔离,减少 业务逻辑 各部分之间的耦合度,提高程序可重用性。

举例而言,一个西瓜整体就是一个 应用程序, 里面混合着 日志、定时任务、安全、支付、工具函数、加密工具、业务代码等各个模块的代码。因为都散布在西瓜的各个位置,交相呼应,耦合度很高,所以维护起来很难。如果我们将西瓜 切成多片, 这一片仅仅是 日志代码模块,那一片仅仅是 定时任务代码模块,以此类推,这样的模块的转台 耦合度 就很低了,维护起来就很方便了。

  • 切面
    是一组关注点的集合,通常包括多个 切点 和 增强 。
  • 切点
    是程序中的特定位置,如方法调用、异常处理等。
  • 增强
    是对切点执行的操作,如在方法调用前、后或抛出异常时执行某个操作。
    AOP 支持四种类型的增强:前置增强、后置增强、环绕增强和异常增强。
    前置增强:是在目标方法 执行前 执行的操作,
    后置增强:是在目标方法 执行后 执行的操作,
    环绕增强:是在目标方法 执行前后都 执行的操作,
    异常增强:是在目标方法 抛出异常时 执行的操作。

常用注解:
在这里插入图片描述

在这里插入图片描述

通过 @Aspect 注解 在 类上创建 切面,切面 中可以 包含 多个 切点,通过 @Pointcut 注解 将 切点 给到方法上。
切点 可以 链接 多个 连接点, 这里 切点 的其中一个 链接点 为 上面的 自定义注解。
所以 凡是 被 上述自定义注解 标注修饰 的 方法 也都会 间接地成为 连接点。
切点 可有拥有 4类 增强,所以 切点 函数在执行时 会先后调用 设置的增强。

以例说明

  • 切面获取 外部信息 的一些方法:
    获取目标方法(连接点)的参数:JoinPoint类下的getArgs()方法,或ProceedingJoinPoint类下的getArgs()方法
    获取自定义注解中的参数:自定义注解可以定义多个参数、必填参数、默认参数等
    通过抛出异常来传递业务处理情况,切面通过捕获异常来记录异常信息
    也可以根据 方法参数的名称 去校验是否是你想要的参数 String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

定义枚举类

public enum TransType {
    // 转账交易类型
    TRANSFER,
    // 查询交易类型
    QUERYING;
}

自定义注解类

注解设置有 必填的交易类型 和 选填的交易说明

import org.example.enums.TransType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 日志记录注解:作用于方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RecordLog {
    // 必填的交易类型
    TransType transType() ;
    // 选填的交易说明
    String description() default "";
}

切面类

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.annotations.RecordLog;
import org.example.enums.TransType;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class RecordLogAspect {

    // 指定自定义注解为切入点
    @Around("@annotation(org.example.annotations.RecordLog)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint){
        // 1.获取目标方法(连接点)的参数信息
        Object[] args = proceedingJoinPoint.getArgs();
        // 获取特定类型的参数:根据具体情况而定
        for (Object arg : args) {
            if (arg instanceof String) {
                System.out.println("日志记录--执行前:方法请求参数信息记录: " + arg);
            }
        }
        // 2.获取自定义注解中的参数
        MethodSignature methodSignature = (MethodSignature)proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        RecordLog annotation = method.getAnnotation(RecordLog.class);
        // 交易类型
        TransType transType = annotation.transType();
        // 交易描述信息
        String description = annotation.description();
        try {
            System.out.println("日志记录--执行前:注解参数信息记录:"+transType+"|"+description+"|");
            Object proceed = proceedingJoinPoint.proceed();
            System.out.println("日志记录--执行后:"+proceed.toString());
            // 只要没异常,那就是执行成功
            System.out.println("日志记录--执行成功:200");
            return proceed;
        } catch (Throwable e) {
//            e.printStackTrace();
            // 3.捕获异常来记录异常信息
            String errorMessage = e.getMessage();
            System.out.println("日志记录--执行异常: "+errorMessage);
            throw new Exception("日志记录--执行异常: ").initCause(e);
        } finally {
            System.out.println("日志记录--执行完成");
        };
    }
}

业务相关类

public class TransInfoBean {
    private String transStatusCode;
    private String transResultInfo;
    private String account;
    private BigDecimal transAmt;

    public String getTransStatusCode() {
        return transStatusCode;
    }

    public void setTransStatusCode(String transStatusCode) {
        this.transStatusCode = transStatusCode;
    }

    public String getTransResultInfo() {
        return transResultInfo;
    }

    public void setTransResultInfo(String transResultInfo) {
        this.transResultInfo = transResultInfo;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public BigDecimal getTransAmt() {
        return transAmt;
    }

    public void setTransAmt(BigDecimal transAmt) {
        this.transAmt = transAmt;
    }
}

使用注解

import org.example.annotations.RecordLog;
import org.example.enums.TransType;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;

@Component
public class RecordLogDemo {

    @RecordLog(transType = TransType.QUERYING,description = "查询账户剩余多少金额")
    public BigDecimal queryRecordLog(String account) throws Exception {
        System.out.println("--执行当前方法:"+Thread.currentThread().getStackTrace()[1].getMethodName());

        try{
            // 执行查询操作:这里只是模拟
            TransInfoBean transInfoBean = this.queryAccountAmt(account);
            BigDecimal accountAmt = transInfoBean.getTransAmt();
            System.out.println("--查询到的账户余额为:"+accountAmt);
            return accountAmt;
        }catch (Exception e){
            throw new Exception("查询账户余额异常:"+e.getMessage());
        }
    }

    /**
     * 调用查询交易
     * @param account
     * @return TransInfoBean
     */
    private TransInfoBean queryAccountAmt(String account) throws Exception {
        TransInfoBean transInfoBean = new TransInfoBean();
        transInfoBean.setAccount(account);
        try{
            // 调用查询交易
//            int n = 1/0;
            transInfoBean.setTransAmt(new BigDecimal("1.25"));
            //交易成功:模拟交易接口返回来的状态
            transInfoBean.setTransStatusCode("200");
            transInfoBean.setTransResultInfo("成功");
        }catch (Exception e){
            //交易成功:模拟交易接口返回来的状态
            transInfoBean.setTransStatusCode("500");
            transInfoBean.setTransResultInfo("失败");
            throw new Exception(transInfoBean.getTransStatusCode()+"|"+transInfoBean.getTransResultInfo());
        }
        return transInfoBean;
    }
}

测试

@SpringBootTest
class SpringDemoAOPApplicationTests {

    @Autowired
    private RecordLogDemo recordLogDemo;

    @Test
    void contextLoads() throws Exception {
        System.out.println("Test...");
        recordLogDemo.queryRecordLog("123567890");
    }

}
  • 成功的情况:
    在这里插入图片描述
  • 异常的情况:
    在这里插入图片描述

常见AOP使用场景

事务管理

Spring AOP 提供了 @Transactional 注解来简化事务管理,底层是通过 AOP 实现的。通过声明式事务管理,可以根据方法的执行情况自动提交或回滚事务。

性能监控

AOP 可以用来监控方法的执行性能(如执行时间、频率等),特别适合用于系统的性能分析和优化。
比如: 计算方法执行时间,并记录日志。 监控方法的调用频率。

安全控制

AOP 适用于实现方法级别的安全控制。例如,你可以在方法调用之前检查用户的权限,决定是否允许访问。
比如:校验用户是否具有某个操作的权限。使用注解 @Secured 或自定义注解,基于角色或权限进行安全验证。

缓存管理

AOP 可以用于方法结果的缓存。对于一些耗时较长的方法,可以使用 AOP 来在第一次调用时执行计算,后续的调用则直接从缓存中获取结果,从而提高性能。
比如:使用 AOP 实现方法结果缓存,避免重复计算。

异常处理

AOP 可以统一处理方法中的异常,比如记录日志、发送警报或执行其他处理。可以通过 @AfterThrowing 或 @Around 注解来实现异常的捕获和处理。
比如:统一捕获异常并记录日志或发送通知。

自定义验证

AOP 可以用于方法参数的验证,尤其在输入数据的校验上。在方法调用之前进行参数验证,避免无效数据的传入。
比如:校验方法参数是否为空或符合特定规则,比如密码格式校验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值