【SpringBoot】AOP 自定义注解的使用详解

这篇具有很好参考价值的文章主要介绍了【SpringBoot】AOP 自定义注解的使用详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        Spring 中的切面 Aspect,这是 Spring 的一大优势。面向切面编程往往让我们的开发更加低耦合,也大大减少了代码量,同时呢让我们更专注于业务模块的开发,把那些与业务无关的东西提取出去,便于后期的维护和迭代。

一、什么是 AOP?

        AOP 的全称为 Aspect Oriented Programming,译为面向切面编程,是通过预编译方式和运行期动态代理实现核心业务逻辑之外的横切行为的统一维护的一种技术。AOP 是面向对象编程(OOP)的补充和扩展。 利用 AOP 可以对业务逻辑各部分进行隔离,从而达到降低模块之间的耦合度,并将那些影响多个类的公共行为封装到一个可重用模块,从而到达提高程序的复用性,同时提高了开发效率,提高了系统的可操作性和可维护性。 

        AOP 是 Spring 框架中的一个核心内容。在 Spring 中,AOP 代理可以用 JDK 动态代理或者 CGLIB 代理 CglibAopProxy 实现。Spring 中 AOP 代理由 Spring 的 IOC 容器负责生成和管理,其依赖关系也由 IOC 容器负责管理。

二、为什么要用 AOP?

        在实际的 Web 项目开发中,我们常常需要对各个层面实现日志记录,性能统计,安全控制,事务处理,异常处理等等功能。如果我们对每个层面的每个类都独立编写这部分代码,那久而久之代码将变得很难维护,所以我们把这些功能从业务逻辑代码中分离出来,聚合在一起维护,而且我们能灵活地选择何处需要使用这些代码。

三、AOP 的核心概念

名词 概念 理解
切面(Aspect) 切面类的定义,里面包含了切入点(Pointcut)和通知(Advice)的定义

首先要理解“切”字,需要把对象想象成一个立方体,每次实例化一个对象,对类定义中的成员变量赋值,就相当于对这个立方体进行了一个定义,定义完成之后,就等着被使用,等着被回收。

面向切面编程则是指,对于一个我们已经封装好的类,我们可以在编译期间或在运行期间,对其进行切割,把立方体切开,在原有的方法里面添加(织入)一些新的代码,对原有的方法代码进行一次增强处理。而那些增强部分的代码,就被称之为切面,如下面代码实例中的通用日志处理代码,常见的还有事务处理、权限认证等等。

切入点(PointCut) 对连接点进行拦截的定义 要对哪些类中的哪些方法进行增强,进行切割,指的是被增强的方法。既要切哪些东西。
连接点(JoinPoint) 被拦截到的点,如被拦截的方法、对类成员的访问以及异常处理程序块的执行等等,自身还能嵌套其他的 Joint Point。 知道了要切哪些方法后,剩下的就是什么时候切,在原方法的哪一个执行阶段加入增加代码,这个就是连接点。如方法调用前,方法调用后,发生异常时等等。
通知(Advice) 拦截到连接点之后所要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类。 通知被织入方法,该如何被增强。定义切面的具体实现。那么这里面就涉及到一个问题,空间(切哪里)和时间(什么时候切,在何时加入增加代码),空间我们已经知道了就是切入点中定义的方法,而什么时候切,则是连接点的概念。
目标对象(Target Object) 切入点选择的对象,也就是需要被通知的对象。由于 Spring AOP 通过代理模式实现,所以该对象永远是被代理对象。 被一个或多个切面所通知的对象,即为目标对象。即业务逻辑本身。
AOP 代理对象(AOP Proxy Object) Spring AOP 可以使用 JDK 动态代理或者 CGLIB 代理,前者基于接口,后者基于类。 AOP代理是AOP框架所生成的对象,该对象是目标对象的代理对象。代理对象能够在目标对象的基础上,在相应的连接点上调用通知。
织入(Weaving) 把切面应用到目标对象从而创建出AOP代理对象的过程。织入可以在编译期、类装载期、运行期进行,而 Spring 采用在运行期完成。 将切面切入到目标方法之中,使目标方法得到增强的过程被称之为织入。

四、相关注解

注解 说明
@Aspect 将一个 java 类定义为切面类
@Pointcut 定义一个切入点,定义需要拦截的东西,即上下文中所关注的某件事情的入口,切入点定义了事件触发时机。可以是一个规则表达式(execution() 表达式,annotation() 表达式),比如下例中某个 package 下的所有函数,也可以是一个注解等
@Before 在切入点开始处切入内容
@After      在切入点结尾处切入内容
@AfterReturning 在切入点 return 内容之后处理逻辑
@Around 在切入点前后切入内容,并自己控制何时执行切入点自身的内容
@AfterThrowing 用来处理当切入内容部分抛出异常之后的处理逻辑
@Order(100) AOP 切面执行顺序,@Before 数值越小越先执行,@After 和 @AfterReturning 数值越大越先执行

其中 @Before、@After、@AfterReturning、@Around、@AfterThrowing 都属于通(Advice)。 

五、添加 AOP Maven 依赖

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

六、第一个实例

接下来我们先看一个例子,利用AOP+Swagger注解实现日志记录功能,所有接口请求完成时,打印日志记录。

具体实现如下:

1、创建一个 AOP 切面类,只要在类上加个 @Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里实现 advice:

@Aspect
@Slf4j
@Component
@Order(1)
public class WebLogAspect {

    private ThreadLocal<Long> startTime = new Thre

    // 定义一个切点:所有接口中被@ApiOperation 注解修饰的方法会织入advice
    @Pointcut("@annotation(operation)")
    public void logPointcut(ApiOperation operation
    }

    // Before 表示 before 将在目标方法执行前执行
    @Before(value = "logPointcut(operation)", argN
    public void before(JoinPoint joinPoint, ApiOperation operation) {
        startTime.set(System.currentTimeMillis());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String[] moduleName = getModuleName(joinPoint, operation);
        log.info("[{}]-[{}]-start {}", moduleName[0], moduleName[1], request.getRequestURL().toString());
        log.info("参数 : {}", Arrays.toString(joinPoint.getArgs()));
    }

    @AfterReturning(returning = "ret", pointcut = "logPointcut(operation) ", argNames = "joinPoint,ret,operation")
    public void after(JoinPoint joinPoint, Object ret, ApiOperation operation) {
        // 处理完请求,返回内容
        String[] moduleName = getModuleName(joinPoint, operation);
        String retMesage = JSON.toJSONString(ret);
        log.info("[{}]-[{}]-end [{}ms] 响应:{}", moduleName[0], moduleName[1], (System.currentTimeMillis() - startTime.get()), retMesage);
        startTime.remove();
    }

    private String[] getModuleName(JoinPoint joinPoint, ApiOperation operation) {
        Class<?> clazz = joinPoint.getTarget().getClass();
        Api api = clazz.getAnnotation(Api.class);
        String pInfo = Optional.ofNullable(api).map(Api::value).orElse(clazz.getName());
        return new String[]{pInfo, operation.value()};
    }
}

2、创建一个接口类,在方法上加上 swagger 的 @ApiOperation 的注解

    @PostMapping("/getSupplierOrderDetailInfo")
    @ApiOperation(value = "售卖渠道订单查询", notes = "售卖渠道订单查询")
    public ApiResultResponse<SupplierOrderDetailResponse> getSupplierOrderDetailInfo(@RequestBody @Valid SupplierOrderDetailRequest request) {
        SupplierOrderDetailResponse response = appVipOrderService.getSupplierOrderDetailInfo(request);
        return ApiResponseUtils.buildSuccessMsg(response);
    }

3、项目启动后,请求该接口时,日志打印如下:

INFO  n.b.z.c.l.WebLogAspect - [售卖渠道信息相关]-[售卖渠道资金池余额查询接口]-start http://localhost:8080/openapi/supplier/getSupplier
INFO  n.b.z.c.l.WebLogAspect - 参数 : [SupplierAssetsPoolRequest(supplierId=8)]
INFO  n.b.z.c.l.WebLogAspect - [售卖渠道信息相关]-[售卖渠道资金池余额查询接口]-end [136ms] 响应:{"apiResult":{"balance":2910},"retCode":"000000","retMsg":"成功","retState":"SUCCESS"}

七、第二个实例

下面将问题复杂化一些,该例的场景是:

1、自定义一个注解 SupplierCheck 

2、创建一个切面类,切点设置为校验所有标注 SupplierCheck 的方法,截取到接口的参数,进行简单的 ip 白名单校验

3、将 SupplierCheck 标注在接口类上面的方法上

具体的实现步骤:

1、让我们来自定义一个注解,注解名为 SupplierCheck,如下所示

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface SupplierCheck {
    /**
     * 校验白名单
     * 标识该方法是否需要校验白名单(默认校验),白话文就是说是否需要执行该校验白名单方法
     */
    boolean checkIpWhite() default true;
}

1、@Target 注解

用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。可以指定多个位置(@Target({ElementType.METHOD, ElementType.FIELD})),语法如下:

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方) 

类型 描述
ElementType.TYPE 可以用于类、接口和枚举类型
ElementType.FIELD 可以用于字段(包括枚举常量)
ElementType.METHOD 可以用于方法(controller上面的接口里它也是方法)
ElementType.PARAMETER 可以用于方法的参数
ElementType.CONSTRUCTOR 可以用于构造函数
ElementType.LOCAL_VARIABLE 可以用于局部变量
ElementType.ANNOTATION_TYPE 可以用于注解类型
ElementType.PACKAGE 可以用于包
ElementType.TYPE_PARAMETER 可以用于类型参数声明(Java 8新增)
ElementType.TYPE_USE 可以用于使用类型的任何语句中(Java 8新增)

2、@Rerention 注解

该注解指定了被修饰的注解的生命周期,语法如下:

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效) 

类型 描述
RetentionPolicy.SOURCE 在源文件中有效(即源文件保留)
RetentionPolicy.CLASS 在class文件中有效(即class保留),不会被加载到JVM中
RetentionPolicy.RUNTIME 在运行时有效(即运行时保留),会被加载到JVM中

3、@Documented

        @Documented 注解表示被它修饰的注解将被 javadoc 工具提取成文档。

4、Inherited

        @Inherited 注解表示被它修饰的注解具有继承性,即如果一个类声明了被 @Inherited 修饰的注解,那么它的子类也将具有这个注解。

2、创建一个 AOP 切面类

只要在类上加个 @Aspect 注解即可。@Aspect 注解用来描述一个切面类,定义切面类的时候需要打上这个注解。@Component 注解将该类交给 Spring 来管理。在这个类里面实现第一步白名单校验逻辑:

@Aspect
@Order(3)
@Slf4j
@Component
public class SupplierAspect {

    @Around("@annotation(supplierCheck)")
    public Object around(ProceedingJoinPoint pjp, SupplierCheck supplierCheck) throws Throwable {
        try {
            Object obj = pjp.getArgs()[0];
            // 业务逻辑
            doCheck(obj, supplierCheck);
            return pjp.proceed(pjp.getArgs());
        } catch (BizException e) {
            log.warn(e.getLogMsg());
            return buildErrorMsg(e.getApiCode(), e.getApiMsg());
        } catch (Exception e) {
            log.error("SupplierAspect 调用失败 ", e);
            return buildErrorMsg(ResponseCode.FAILED.code, ResponseCode.FAILED.desc);
        }

    }

    private void doCheck(Object obj, SupplierCheck,supplierCheck) throws BizException {
        AppAuthRequest authRequest = JSON.parseObject(JSON.toJSONString(obj), AppAuthRequest.class);
        log.info("authRequest = {}", authRequest);
        //白名单校验
        if (supplierCheck.checkIpWhite()) {
            // 白名单业务逻辑
        }
    }

3、创建接口类,并在目标方法上标注自定义注解 SupplierCheck :

    @PostMapping("/getSupplierOrderDetailInfo")
    @ApiOperation(value = "售卖渠道订单查询", notes = "售卖渠道订单查询")
    @SupplierCheck
    public ApiResultResponse<SupplierOrderDetailResponse> getSupplierOrderDetailInfo(@RequestBody @Valid SupplierOrderDetailRequest request) {
        SupplierOrderDetailResponse response = appVipOrderService.getSupplierOrderDetailInfo(request);
        return ApiResponseUtils.buildSuccessMsg(response);
    }

3、有人会问,上面一个接口设置了多个切面类进行了校验怎么办?这些切面的执行顺序如何管理?

很简单,一个自定义的 AOP 注解可以对应多个切面类,这些切面类执行顺序由 @Order 注解管理,该注解后的数字越小,所在切面类越先执行

比如上面接口中 @ApiOperation 增强的注解中(第一个实例介绍的)的 @Order(1),那么这个注解先执行,@SupplierCheck 白名单校验的注解的 @Order(3) 后面执行。文章来源地址https://www.toymoban.com/news/detail-745195.html

到了这里,关于【SpringBoot】AOP 自定义注解的使用详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring Boot入门(23):基于AOP实现自定义注解拦截接口日志并保存入库 | 超级详细,建议收藏

            在上两期中,我们着重介绍了如何集成使用 Logback 与 log4j2 日志框架的使用,今天我们讲解的主题依旧跟日志有关,不过不是使用何种开源框架,而是自己动手造。         Spring的核心之一AOP;AOP翻译过来叫面向切面编程, 核心就是这个切面. 切面表示从业务逻辑中

    2024年02月11日
    浏览(47)
  • 【Spring】使用自定义注解方式实现AOP鉴权

    AOP,是一种面向切面编程,可以通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。 在软件开发中,鉴权(Authentication)是一项非常重要的安全措施,用于验证用户身份和权限。在应用程序中,我们通常会使用AOP(Aspect-Oriented Programming)来实现鉴权功能

    2024年02月11日
    浏览(46)
  • springboot3使用自定义注解+AOP+redis优雅实现防重复提交

      ⛰️个人主页:     蒾酒 🔥 系列专栏 :《spring boot实战》 🌊 山高路远,行路漫漫,终有归途 目录 写在前面 实现思路 实现步骤 1.定义防重复提交注解 2.编写一个切面去发现该注解然后执行防重复提交逻辑 3.测试 依赖条件 1.接口上标记防重复提交注解 2.接口测试 写在最

    2024年04月11日
    浏览(36)
  • springboot aop 自定义注解形式

    2024年01月25日
    浏览(36)
  • spring自定义注解+aop+@BindingParam

    2.1 声明切面注解  2.1.1切面对应枚举  2.2 声明绑定参数注解 4.1 ThreadLocalUtil  4.2  自定义异常

    2024年02月14日
    浏览(38)
  • Springboot 中使用 Redisson+AOP+自定义注解 实现访问限流与黑名单拦截

    🏷️ 个人主页 :牵着猫散步的鼠鼠  🏷️ 系列专栏 :Java全栈-专栏 🏷️ 个人学习笔记,若有缺误,欢迎评论区指正   前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站AI学习网站。 目录 前言 1.导入Redisson 引入依

    2024年02月21日
    浏览(52)
  • SpringBoot + 自定义注解 + AOP 打造通用开关

    前言 最近在工作中迁移代码的时候发现了以前自己写的一个通用开关实现,发现挺不错,特地拿出来分享给大家。 为了有良好的演示效果,我特地重新建了一个项目,把核心代码提炼出来加上了更多注释说明,希望xdm喜欢。 案例 1、项目结构 2、引入依赖 3、yml配置 连接Re

    2024年01月23日
    浏览(40)
  • SpringBoot+自定义注解+AOP高级玩法打造通用开关

    1.项目结构 2.引入依赖 3.yml配置 4.自定义注解 5.定义常量 6.AOP核心实现 7.使用注解 8.工具类 9.测试接口 10.Redis中把开关加上 11.启动服务 将redis中开关置为1 欢迎大家积极留言交流学习心得,点赞的人最美丽!

    2024年02月07日
    浏览(34)
  • springboot自定义注解+aop+redis实现延时双删

    redis作为用的非常多的缓存数据库,在多线程场景下,可能会出现数据库与redis数据不一致的现象 数据不一致的现象:https://blog.csdn.net/m0_73700925/article/details/133447466 这里采用aop+redis来解决这个方法: 删除缓存 更新数据库 延时一定时间,比如500ms 删除缓存 这里之所以要延时一

    2024年01月17日
    浏览(41)
  • spring-自定义AOP面向切面注解--统一切面处理-登陆信息采集

    2023华为OD统一考试(A+B卷)题库清单-带答案(持续更新)or2023年华为OD真题机考题库大全-带答案(持续更新) 1. 先写一个登陆记录注解(//记录:XXX时间,XXX姓名,XX系统,登录成功) 2. 写一个切面对注解进行处理(业务逻辑处理,记录登陆的信息) 3.写一个登录的控制类,

    2024年02月13日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包