AOP和注解

本文介绍了AOP(面向切面编程)的概念,如何通过AspectJ框架实现切面类,以及各种通知类型(如前置、后置、环绕)的应用,以简化日志记录、权限检查等任务,遵循DRY和开闭原则。

聊聊: 在开发中经常会碰到 日志记录、权限检查、事务控制 等问题,如果全都手动进行编码,代码量将会非常夸张,不符合 DRY 原则,也不遵循开闭原则,或者通过封装等方式简化代码,依旧不遵循开闭原则,也为后期维护增加压力。这个时候就可以使用 AOP 的思想了。

概念

AOP 是一种面向切面编程的思想,大概可以理解为,把与业务逻辑无关的操作分离出来。举个例子:把一块面包切成几段,每段之间加了一些好吃夹层,增加了口感又不影响原有的面包分量。

常用AOP框架 AspectJ

//定义切面类
@Aspect
@Component
public class AopTest {

    /**
     * Pointcut 支持多种表达式来定义切点:
     * <p>
     * 1. 使用 @annotation: 当方法上添加了指定注解时,切点匹配。
     *    <br>示例: {@code @annotation(com.example.annotation.TestAnno)}
     * <p>
     * 2. 使用 execution: 根据方法签名匹配特定的方法。
     *    <br>注意:第一个星号(*)后需要跟随一个空格。
     *    <br>示例: {@code execution(* com.example.service.impl.*ServiceImpl.*(..))}
     * <p>
     * 3. 使用 @within: 当类级别的注解被应用时生效。适用于注解应用于整个类的情况。
     *    <br>示例: {@code @within(com.example.annotation.TestAnno)}
     * <p>
     * 每种方式都有其适用场景,根据实际需求选择合适的方式来定义切点。
     */
    @Pointcut("@annotation(xx.xx.annotation.TestAnno)")
    public void txPointcut() {}

    /**
     * 前置通知
     * @param joinPoint 用来访问当前连接点的静态部分,如方法签名和参数等,但不提供对目标方法执行的控制能力。
     */
    @Before("txPointcut()")
    public void beforeAdvice(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }

    /**
     * 返回之后通知
     * @param joinPoint 用来访问当前连接点的静态部分,如方法签名和参数等,但不提供对目标方法执行的控制能力。
     * @param result 返回值绑定自定义参数名
     */
    @AfterReturning(pointcut = "txPointcut()", returning = "result")
    public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
        System.out.println("Method: " + joinPoint.getSignature().getName() + " returned with: " + result);
    }

    /**
     * 抛出(异常)后执⾏通知
     * @param joinPoint 用来访问当前连接点的静态部分,如方法签名和参数等,但不提供对目标方法执行的控制能力。
     * @param ex 发生异常时绑定自定义参数名
     */
    @AfterThrowing(pointcut = "txPointcut()", throwing = "ex")
    public void afterThrowingAdvice(JoinPoint joinPoint, Throwable ex) {
        System.out.println("Method: " + joinPoint.getSignature().getName() + " threw exception: " + ex);
    }

    /**
     * 后置通知
     * @param joinPoint 用来访问当前连接点的静态部分,如方法签名和参数等,但不提供对目标方法执行的控制能力。
     */
    @After("txPointcut()")
    public void afterAdvice(JoinPoint joinPoint) {
        System.out.println("After method: " + joinPoint.getSignature().getName());
    }

    /**
     * 围绕通知
     * @param joinPoint 继承了 JoinPoint 的所有功能,且提供控制目标方法的执行(即通过调用 proceed() 方法来执行目标方法)。
     * @return 目标方法处理后结果
     * @throws Throwable 目标方法抛出的异常
     */
    @Around("txPointcut()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        // 方法执行前的操作
        System.out.println("Before method: " + joinPoint.getSignature().getName());

        // 业务逻辑...

        Object result = joinPoint.proceed();

        // 方法执行后的操作
        System.out.println("After method: " + joinPoint.getSignature().getName());

        return result;
    }
}

定义注解

// 语法格式
元注解 
修饰符 @interface 注解名 {
    // 成员(属性)
    返回值类型 方法名() default 默认值
}
1.元注解
  1. @Target: 表示注解可以贴在哪些位置(类,方法上,构造器上等等)

  2. @Retention: 用于描述注解的生命周期

  3. @Documented: 使用 @Documented 标注的标签会保存到 API文档中.

  4. @Inherited: @Inherited标注的标签可以被子类所继承.

2.示例代码
@Target(ElementType.TYPE)
@Retention(RetentionPolicy=RUNTIME)
@Inherited
@interface A{ }

@A
class Merchandise{ }
3.返回值类型
只能是八种基本数据类型、String 类型、 Class 类型、 enum 类型、 Annotation 类型、以上所有
类型的数组
4.拓展
 - getAnnotation( Class clz ):通过类的Class对象调用该方法,参数使用注解的class,可以拿到类中关于注解的相关信息 
示例代码:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Content {
    public String value() default "hello world";
}
import com.wwx.web.util.Content;
import lombok.Data;

@Data
@Content
//商户
public class Merchandise {

    //编号
    private Integer goodsId;
    //名称
    private String name;
    //数量
    private Integer number;
}
@GetMapping("/anno")
public String test(){
    Merchandise merchandise = new Merchandise();
    Class<? extends Merchandise> merchandiseClass = merchandise.getClass();
    Content content = merchandiseClass.getAnnotation(Content.class);
    return content.value();
}

注意

1.AOP失效情况

1.Spring Boot 的自动配置将自动启用 AspectJ 自动代理,无需手动设置 @EnableAspectJAutoProxy 。使用 Spring 上下文注入 Bean,AOP 生效

2.当使用自定义 @Configuration 方式来创建 bean 时,AOP不生效,需要手动添加 @EnableAspectJAutoProxy 类注解

@Configuration
@EnableAspectJAutoProxy
public class AopConfig {
 //...
}

3.默认情况下,Spring Boot 的自动配置将 Spring AOP 配置为使用 CGLib 代理。若要改用 JDK 代理,请将 spring.aop.proxy-target-class 设置为 false 。

参考

SpringBoot/Spring AOP默认动态代理方式_springboot动态代理-优快云博客

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值