SpringBoot AOP简记

AOP

理解AOP

面向切面编程(AOP)补充了面向对象编程(OOP) 提供了另一种思考程序结构的方式。模块化的关键单元 在OOP中是类,而在AOP中,模块化的单位是切面。它允许开发人员在不修改原始代码的情况下,通过在方法执行前、后或异常抛出时执行某些操作来改变程序的行为。

Aspect:切面是一个横切关注点的模块化,它封装了通知和切入点,这些通知和切入点应用于目标对象以修改其行为。简单来说,切面是一个可重用的类,其中包含通知(在应用程序执行期间的特定点运行的代码)和切入点(定义哪些方法应该被拦截并应用通知的表达式)。在Aspect类中,您需要定义一个或多个通知方法和一个切入点表达式。通知方法将在满足切入点表达式时被执行。

Join Point:程序执行期间的一个点,例如方法执行、异常抛出等。

Pointcut(切点):描述哪些Join Point如何被拦截应用与切面,通过表达式定义。可以基于方法名、参数、注解等条件进行匹配。

Advice:它定义了在Join Point执行时要执行的操作。Advice可以在Join Point之前、之后或之前和之后执行,以提供不同的行为。Spring Boot AOP中有以下五种Advice类型:

  • Before Advice:在Join Point之前执行的Advice。

  • After Returning Advice:在Join Point成功返回后执行的Advice。

  • After Throwing Advice:在Join Point抛出异常后执行的Advice。

  • After Advice:在Join Point之后执行的Advice,无论成功与否。

  • Around Advice:在Join Point之前和之后执行的Advice,它可以控制是否调用Join Point以及如何处理它的结果。

Springboot中如何去使用AOP

1、类加上@Aspect注解,定义为一个切面类,加上@Component注入道IOC容器

2、使用@Pointcut注解定义切点

  • 通过方法名定义切点

例:这个切点匹配了 com.example.aop.controller 包下的每一个控制器方法

@Pointcut("execution(* com.example.aopdemo.controller.*.*(..))")
public void firstPoint() {}
  • 通过注解定义切点

例:这个切点定义匹配了所有使用 @EnumChange 注解的方法

@Pointcut("@annotation(com.example.aopdemo.annotation.EnumChange)") 
public void enumChangePoint() {}
  • 使用正则表达式定义切点

例:这个切点定义匹配了 com.example.demo.service 包下的所有方法,方法参数为Long类型且参数名为id

@Pointcut("execution(* com.example.demo.service.*.*(Long)) && args(id)") 
public void serviceLayerExecutionWithId(Long id) {}
  • 使用逻辑运算符定义切点

例:这个切点定义匹配了 com.example.demo.service 包下的所有方法,但不包括UserService类中的方法

@Pointcut("execution(* com.example.demo.service.*.*(..)) && !execution(* com.example.demo.service.UserService.*(..))") 
public void serviceLayerExecutionWithoutUserService() {}

3、定义Advice方法

@Before:使用@Before注解来定义Before Advice,它会在切点方法执行之前执行。如下面例子可以在切点方法执行之前,我们就成功地修改了它的参数。(需要注意的是,修改参数的行为可能会对应用程序的正确性产生影响,因此需要谨慎使用。)

@Component
@Aspect
public class PageAspect {

    @Pointcut("execution(public * com.example.demo_knife4jv2.controller.*.*(Integer,Integer)) && args(pageSize,pageNum)")
    public void checkPagePointCut(Integer pageSize, Integer pageNum) {}

    @Before("checkPagePointCut(pageSize,pageNum)")
    public void beforePiontCut(JoinPoint joinPoint, Integer pageSize, Integer pageNum) {
        if (pageSize < 0) {
            pageSize = 1;
        }
        if (pageSize > 10000) {
            pageSize = 10000;
        }
    }
}

@AfterReturning:在Join Point成功返回后执行的Advice。可以通过Join Point参数和Advice方法参数来访问返回值

@Component
@Aspect
public class ResultAspect {

    @Pointcut("execution(* com.example.demo_knife4jv2.controller.*.*(..))")
    public void checkResultPointCut() {}

    @AfterReturning(value = "checkResultPointCut()", returning = "result")
    public Object afterPiontCut(JoinPoint joinPoint, Object result) {
        // 如果result为空,返回特定的返回结构 Result.success()是自定义的方法
        if (result == null) {
            result = (Object) Result.success("操作成功");
        }
        return result;
    }
}

@AfterThrowing:After Throwing Advice在Join Point抛出异常后执行的Advice。可以通过Join Point参数和Advice方法参数来访问异常信息

@Component
@Aspect
@Slf4j
public class ExceptionLogAspect {

    @Pointcut("execution(* com.example.demo_knife4jv2.controller.*.*(..))")
    public void exceptionLogPointCut() {}

    @AfterThrowing(value = "exceptionLogPointCut()", throwing = "exception")
    public void afterThrowingPiontCut(JoinPoint joinPoint, Exception exception) {
        log.error("exception is happen the message is " + exception.getMessage());
    }
}

@After:After Advice在Join Point之后执行的Advice,无论成功与否。可以通过Join Point参数来访问方法信息

@Component
@Aspect
@Slf4j
public class LogAspect {

    @Pointcut("execution(* com.example.demo_knife4jv2.controller.*.*(..))")
    public void finalLogPointCut() {}

    @After("finalLogPointCut()")
    public void afterPiontCut(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        log.info("Method " + methodName + " executed finally!");
    }
}

@Around:Around Advice 用于在方法执行前后执行操作。它允许您通过修改方法参数或返回值来控制方法执行流程。Around Advice 的使用场景包括性能监控、日志记录、事务管理等。

如下例子是一个通过Around修改返回值,转换枚举值的方法。

@Component
@Aspect
@Slf4j
public class EnumAspect {

    @Pointcut("@annotation(com.example.demo_knife4jv2.annotation.EnumChange)")
    public void EnumAspect() {}

    @Around("EnumAspect()")
    public Object around(ProceedingJoinPoint jp) throws Throwable {
        Result result = (Result) jp.proceed();
        log.info(result.toString());
        Class resultClass = result.getData().getClass();
        Field[] fields = resultClass.getDeclaredFields();
        JSONObject resultJson = (JSONObject) JSON.toJSON(result.getData());
        for (Field field : fields) {
            if (field.isAnnotationPresent(EnumMessage.class)) {
                Enum[] enums = (Enum[]) field.getType().getEnumConstants();
                Enum enumOptional = Arrays.stream(enums).map(s -> {
                    if (s.name().equals(resultJson.getString(field.getName()))) {
                        return s;
                    }
                    return null;
                }).findFirst().get();
                IEnum iEnum = (IEnum) enumOptional;
                String message = iEnum.getMessage();
                resultJson.put(field.getName() + "_message", message);
            }
        }
        result.setData(resultJson);
        return result;
    }

}

4、添加@Order注解设置Aspect类顺序,在一个项目中有多个Aspect类时,可能会因为顺序问题导致与实际效果不符,这时可以在Aspect类上添加一个@Order注解来设置Aspect类的顺序,数值越小的Aspect越先执行。如果没有指定@Order注解,则Aspect的执行顺序将由Spring决定

参考资料:https://docs.spring.io/spring-framework/reference/core/aop.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值