SpringBoot AOP使用

2023-11-08

AOP即面向切面编程,其存在的目的就是为了解耦,通过AOP的实现,可以让业务逻辑只关心业务本身,而不用在意其他的事情,无需改动原有代码,实现无侵入增加部分能力。在系统日志处理、系统事务处理、系统安全验证、系统数据验证等多个场景中都有可能使用到。
在关于AOP的描述中,有如下几个比较重要的概念:
通知:Advice,给目标方法添加额外操作步骤,即拦截到连接点之后要执行的方法
连接点:JoinPoint,可以添加额外步骤的地方,基本上每个方法前后都可以是连接点
切面:Aspect,横切面对象,即通知和切点的结合,是封装了通知和切入点用来横向插入的类,也可理解为要植入的新的功能的类
切点:PointCut,筛选部分连接点作为切点,程序执行过程中某个特点的点,也就是实际需要添加额外步骤的地方
目标对象:Target,需要额外添加操作步骤的对象(业务逻辑)
代理对象:Proxy,负责调用切面中的方法为目标对象植入新的功能
织入:Weaving,把切面加入程序代码的过程,切面在指定的连接点被织入到目标对象中

举一个简单的栗子来说

public class TestService {

    void funcA() {};

    void funcB() {}
}

TestService中的所有方法都是连接点(JoinPoint),其中某个方法亦可称之为切点,如funcA方法就可以成为一个切点(PointCut)。需要在funcA方法前后执行方法就是通知(Advice)。funcA方法就是目标对象(Target),把切面中想要增加的代码加入到funcA方法的前后就是织入(Weaving)。

AOP常用通知注解

@PointCut:定义切入点
@Before:在目标方法开始执行前执行
@After:在目标方法执行后执行,无论目标方法成功与否,但是不能访问目标方法的返回结果
@AfterReturning:在目标方法有返回值正常返回后执行
@AfterThrowing:在目标方法抛出异常时执行
@Around:环绕通知,可以获取到目标方法到入参和返回值,在方法执行前和执行后都会执行

使用方法

引入依赖

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

定义切面

@Aspect
@Component
public class MyLogAspect { }

使用@Aspect和@Component便可简单的定义一个切面

定义切入点

切入点定义有如下三种方式

beanId方式
@Pointcut("bean(appController)")
public void pointCut() {}
execution方式
@Pointcut("execution(public * com.example.demo.Service..*.*(..))")
public void pointCut() {}

execution中是AspectJ表达式,其大概的含义为
• 第一个* 代表任意的返回值
• com.example.demo.Service表示横切的包名
• 包后面… 表示当前包及其子包
• 第二个* 表示类名,代表所有类
• .*(…) 表示任何方法,括号代表参数 … 表示任意参数

注解annonation方式
@Pointcut("@annotation(com.example.demo.meta.myAnnotation)")
public void pointCut() {}

使用注解的形式时,可自定义注解来实现切入点的定义,个人还是比较喜欢用注解的方式来实现的。

注解使用

注解在使用切入点时,可直接传入定义该切入点的方法名即可

@Before、@After

@Before定义在具体的业务逻辑执行前执行的通知,@After定义在具体的业务逻辑执行后执行的通知,看到网上有文章指出可以通过@Before来实现业务逻辑执行方法输入参数的修改,但我试了下并不可行,不知道是哪里写的不对,还是咋的。
需要特别指出的是@After无法获取到业务逻辑处理的返回值。
同时,@Before和@After注解的方法都需要携带一个JoinPoint类型的参数。

// 目标方法执行前前的方法
@Before("myPointCut()")
public void before(JoinPoint point) {
    System.out.println("目标方法执行之前...");
}

// 目标方法执行后的方法
@After("myPointCut()")
public void after(JoinPoint point) {
    System.out.println("目标方法执行之后...");
}
@AfterReturning

根据名字就可以很清楚的知道,该注解可以获取到业务逻辑处理后的返回值,因此可以根据需要,对返回值进行修改。
此外,@AfterReturning注解在使用时,需严格根据要求传入value和returning两个变量,其中value为定义的切入点,returning 标识目标业务逻辑的返回值自定义变量名必须和通知方法的形参一样。

@AfterReturning(value = "myPointCut()",returning = "result")
public void afterReturn(JoinPoint point, Object result) {
    System.out.println("实际方法返回后执行...");
    processOutPutObj(result);
}
@AfterThrowing

其主要能力和@AfterReturning差不多,区别在于该注解是在目标业务逻辑抛出异常时执行的通知,并且可以获得目标业务逻辑所抛出的具体的异常信息,其在使用时也需要在注解中传入value和throwing的值。

@AfterThrowing(value = "myPointCut()", throwing = "ex")
public void afterThrowing(JoinPoint point, Exception ex) {
    System.out.println("实际方法抛出异常...");
    System.out.println("异常信息:" + ex.getMessage());
}
@Around

由于@Around贯穿于业务方法执行之前和之后,因此在实际使用中,仅使用@Around便可实现我们大多数想要附加的内容,比如,根据用户输入的参数是否含有敏感词来过滤,或者根据返回结果是否加密来决定是否返回等。通过@Around可同时对输入参数和返回值进行修改。
需要注意的是@Around注解下的方法需要带有ProceedingJoinPoint类型的参数,通过该参数来决定是否可以执行目标业务逻辑,并且其必须要有返回值,返回值即为具体目标业务逻辑的返回值。

@Around("myPointCut()") // 环绕通知,在方法执行前和执行后都会执行
public Object around(ProceedingJoinPoint point) {
    System.out.println("切点【环绕通知】开始执行...");
    Object result = null;
    long beginTime = System.currentTimeMillis();
    try {
        // 处理输入参数
        Object[] args = point.getArgs();
        processInputObj(args);
        // 执行方法
        // 可以决定是否执行目标方法
        result = point.proceed(args);
        // 处理返回值
        processOutPutObj(result);
    } catch (Throwable e) {
        e.printStackTrace();
    }
    long time = System.currentTimeMillis() - beginTime;
    // 具体的额外操作方法
    processing(point, time);
    System.out.println("切点【环绕通知】执行结束...");
    return result;
}

切面的全部代码:

package com.example.demo.aop;

import com.alibaba.fastjson.JSONObject;
import com.example.demo.entity.SysLog;
import com.example.demo.entity.User;
import com.example.demo.meta.MyLog;
import com.example.demo.util.HttpContextUtils;
import com.example.demo.util.IPUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * Description:
 *
 * @date:2021/11/14 12:54
 * @author: lyf
 */
// 定义切面
@Aspect
@Component
public class MyLogAspect {

//    @Pointcut("bean(appController)")
//    public void pointCut() {}
//
//    @Pointcut("execution(public * com.example.demo.Service..*.*(..))")
//    public void pointCut() {}
//
//    @Pointcut("@annotation(com.example.demo.meta.myAnnotation)")
//    public void pointCut() {}

    // 定义切点  切点的定义有多种方式
    // 这边使用的是注解的方式
    @Pointcut("@annotation(com.example.demo.meta.MyLog)")
    public void myPointCut() {}

    // 目标方法执行前前的方法
//    @Before("myPointCut()")
    public void before(JoinPoint point) {
        System.out.println("目标方法执行之前...");
    }

    // 目标方法执行后的方法
//    @After("myPointCut()")
    public void after(JoinPoint point) {
        System.out.println("目标方法执行之后...");
    }

//    @AfterReturning(value = "myPointCut()",returning = "result")
    public void afterReturn(JoinPoint point, Object result) {
        System.out.println("实际方法返回后执行...");
        processOutPutObj(result);
    }

//    @AfterThrowing(value = "myPointCut()", throwing = "ex")
    public void afterThrowing(JoinPoint point, Exception ex) {
        System.out.println("实际方法抛出异常...");
        System.out.println("异常信息:" + ex.getMessage());
    }

    @Around("myPointCut()") // 环绕通知,在方法执行前和执行后都会执行
    public Object around(ProceedingJoinPoint point) {
        System.out.println("切点【环绕通知】开始执行...");
        Object result = null;
        long beginTime = System.currentTimeMillis();
        try {
            // 处理输入参数
            Object[] args = point.getArgs();
            processInputObj(args);
            // 执行方法
            // 可以决定是否执行目标方法
            result = point.proceed(args);
            long time = System.currentTimeMillis() - beginTime;
            // 具体的额外操作方法
            processing(point, time);
            // 处理返回值
            processOutPutObj(result);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        System.out.println("切点【环绕通知】执行结束...");
        return result;
    }


    /**
     * 处理输入参数
     * @param args
     */
    private Object[] processInputObj(Object[] args) {
        for (int i = 0; i < args.length; i++) {
            System.out.println("arg原来的值为:" + args[i]);
            args[i] = "被aop修改的name";
        }
        return args;
    }

    /**
     * 处理返回对象
     * @param obj
     */
    private void processOutPutObj(Object obj) {
        System.out.println("OBJ 原本为:" + obj.toString());
        if (obj instanceof User) {
            User user = (User) obj;
            user.setDesc("哈哈,我被aop改了");
        }
    }


    private SysLog processing(ProceedingJoinPoint joinPoint, long time) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SysLog sysLog = new SysLog();
        MyLog logAnnotation = method.getAnnotation(MyLog.class); // 获取方法上的注解
        if (logAnnotation != null) {
            // 注解上的描述
            sysLog.setOperation(logAnnotation.value());
        }
        // 请求的方法名
        String className = joinPoint.getTarget().getClass().getName(); // target
        String methodName = signature.getName();
        sysLog.setMethod(className + "." + methodName + "()");
        // 请求的方法参数
        Object[] args = joinPoint.getArgs();
        // 请求的方法参数名称
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames != null) {
            String params = "";
            for (int i = 0; i < args.length; i++) {
                params += "  " + paramNames[i] + ": " + args[i];
            }
            sysLog.setParams(params);
        }
        // 获取request
        HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        // 设置ip地址
        sysLog.setIp(IPUtils.getIpAddr(request));
        // 模拟一个用户
        sysLog.setUserName("test");
        sysLog.setTime((int) time);
        sysLog.setCreatTime(new Date());
        System.out.println(JSONObject.toJSONString(sysLog));
        return sysLog;
        // 这边可以实现存入数据库的操作
    }
}

参考文章:
[1] https://www.jianshu.com/p/4d22ea402d14
[2] https://www.jianshu.com/p/a7c150458e2c
[3] https://blog.csdn.net/puhaiyang/article/details/78146620

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot AOP使用 的相关文章

  • 如何默认将 Maven 插件附加到阶段?

    我有一个 Maven 插件应该在编译阶段运行 所以在项目中consumes我的插件 我必须做这样的事情
  • 在 java 类和 android 活动之间传输时音频不清晰

    我有一个android活动 它连接到一个java类并以套接字的形式向它发送数据包 该类接收声音数据包并将它们扔到 PC 扬声器 该代码运行良好 但在 PC 扬声器中播放声音时会出现持续的抖动 中断 安卓活动 public class Sen
  • 在 HTTPResponse Android 中跟踪重定向

    我需要遵循 HTTPost 给我的重定向 当我发出 HTTP post 并尝试读取响应时 我得到重定向页面 html 我怎样才能解决这个问题 代码 public void parseDoc final HttpParams params n
  • Spring Data JPA 应用排序、分页以及 where 子句

    我目前正在使用 Spring JPA 并利用此处所述的排序和分页 如何通过Spring data JPA通过排序和可分页查询数据 https stackoverflow com questions 10527124 how to query
  • Mockito when().thenReturn 不必要地调用该方法

    我正在研究继承的代码 我编写了一个应该捕获 NullPointerException 的测试 因为它试图从 null 对象调用方法 Test expected NullPointerException class public void c
  • Spring @RequestMapping 带有可选参数

    我的控制器在请求映射中存在可选参数的问题 请查看下面的控制器 GetMapping produces MediaType APPLICATION JSON VALUE public ResponseEntity
  • 斯坦福 NLP - 处理文件列表时 OpenIE 内存不足

    我正在尝试使用斯坦福 CoreNLP 中的 OpenIE 工具从多个文件中提取信息 当多个文件 而不是一个 传递到输入时 它会给出内存不足错误 All files have been queued awaiting termination
  • 如何在PreferenceActivity中添加工具栏

    我已经使用首选项创建了应用程序设置 但我注意到 我的 PreferenceActivity 中没有工具栏 如何将工具栏添加到我的 PreferenceActivity 中 My code 我的 pref xml
  • 十进制到八进制的转换[重复]

    这个问题在这里已经有答案了 可能的重复 十进制转换错误 https stackoverflow com questions 13142977 decimal conversion error 我正在为一个类编写一个程序 并且在计算如何将八进
  • Java按日期升序对列表对象进行排序[重复]

    这个问题在这里已经有答案了 我想按一个参数对对象列表进行排序 其日期格式为 YYYY MM DD HH mm 按升序排列 我找不到正确的解决方案 在 python 中使用 lambda 很容易对其进行排序 但在 Java 中我遇到了问题 f
  • JRE 系统库 [WebSphere v6.1 JRE](未绑定)

    将项目导入 Eclipse 后 我的构建路径中出现以下错误 JRE System Library WebSphere v6 1 JRE unbound 谁知道怎么修它 右键单击项目 特性 gt Java 构建路径 gt 图书馆 gt JRE
  • 使用Caliper时如何指定命令行?

    我发现 Google 的微型基准测试项目 Caliper 非常有趣 但文档仍然 除了一些示例 完全不存在 我有两种不同的情况 需要影响 JVM Caliper 启动的命令行 我需要设置一些固定 最好在几个固定值之间交替 D 参数 我需要指定
  • getResourceAsStream() 可以找到 jar 文件之外的文件吗?

    我正在开发一个应用程序 该应用程序使用一个加载配置文件的库 InputStream in getClass getResourceAsStream resource 然后我的应用程序打包在一个 jar文件 如果resource是在里面 ja
  • 仅将 char[] 的一部分复制到 String 中

    我有一个数组 char ch 我的问题如下 如何将 ch 2 到 ch 7 的值合并到字符串中 我想在不循环 char 数组的情况下实现这一点 有什么建议么 感谢您花时间回答我的问题 Use new String value offset
  • 无法捆绑适用于 Mac 的 Java 应用程序 1.8

    我正在尝试将我的 Java 应用程序导出到 Mac 该应用程序基于编译器合规级别 1 7 我尝试了不同的方法来捆绑应用程序 1 日食 我可以用来在 Eclipse 上导出的最新 JVM 版本是 1 6 2 马文 看来Maven上也存在同样的
  • 如何从指定日期获取上周五的日期? [复制]

    这个问题在这里已经有答案了 如何找出上一个 上一个 星期五 或指定日期的任何其他日期的日期 public getDateOnDay Date date String dayName 我不会给出答案 先自己尝试一下 但是 也许这些提示可以帮助
  • 如何在桌面浏览器上使用 webdriver 移动网络

    我正在使用 selenium webdriver 进行 AUT 被测应用程序 的功能测试自动化 AUT 是响应式网络 我几乎完成了桌面浏览器的不同测试用例 现在 相同的测试用例也适用于移动浏览器 因为可以从移动浏览器访问 AUT 由于它是响
  • Firebase 添加新节点

    如何将这些节点放入用户节点中 并创建另一个节点来存储帖子 我的数据库参考 databaseReference child user getUid setValue userInformations 您需要使用以下代码 databaseRef
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • JGit 检查分支是否已签出

    我正在使用 JGit 开发一个项目 我设法删除了一个分支 但我还想检查该分支是否已签出 我发现了一个变量CheckoutCommand但它是私有的 private boolean isCheckoutIndex return startCo

随机推荐

  • [已解决] wget命令出现Unable to establish SSL connection.错误

    文章目录 解决 从linux上下载mysql 包时 wget https downloads mysql com archives get p 23 file mysql 5 6 46 linux glibc2 12 x86 64 tar
  • qt 中 file generation failure: unable to create the directory

    原因 不能将qt的项目工程安装到qt软件的安装目录中 如 opt 的目录中 解决 应该使用当前系统的普通用户的目录中建立项目目录 如 home chenfan QT myprojects
  • 数据分析概述和理论基础

    2018 03 26 Python开发者交流平台 什么是数据分析 数据分析 是指用适当的统计分析方法对收集来的大量数据进行分析 提取有用信息和形成结论 而对数据加以详细研究和概括总结的过程 为什么会有数据分析 随着计算机技术 互联网技术 数
  • Linux---用户的权限

    专栏 Linux 个人主页 HaiFan 本章为大家带来用户的权限的讲解 用户的权限 Linux权限的概念 权限的三类对象 权限的三种类型 权限设置chmod chown chgrp 更改权限chmod chown chgrp umask
  • Atcoder Beginner Contest 164 D

    题意 给定一个只包含数字字符且不包含 0 0 0字符的字符串 S S S 问有子串组成的十位数是 2019 2019
  • QListView的使用

    一 介绍 QListView可以用来以列表的形式展示数据 在Qt中使用model View结构来管理数据与视图的关系 model负责数据的存取 数据的交互则通过delegate来实现 二 model QT提供了一些现成的models用于处理
  • drools规则引擎初探

    1 drools是什么 Drools是为Java量身定制的基于Charles Forgy的RETE算法的规则引擎的实现 具有了OO接口的RETE 使得商业规则有了更自然的表达 Rule是什么呢 一条规则是对商业知识的编码 一条规则有 att
  • Java中Set的使用

    在Java中使用Set 可以方便地将需要的类型以集合类型保存在一个变量中 主要应用在显示列表 Set是一个不包含重复元素的 collection 更确切地讲 set 不包含满足 e1 equals e2 的元素对 e1 和 e2 并且最多包
  • 如何修改桌面的存放路径?(将桌面放到D盘或E盘)

    方法1 使用超级兔子 依次选择 魔法设置 系统 系统文件夹 桌面 就可以自定义桌面项的路径了 方法2 首先要在 开始 运行 内输入 regedit 打开注册表编辑器 然后要在 文件 下拉菜单中 的 导出 功能备份好注册表 以防万一 接着在左
  • 学习笔记——Java入门第二季

    1 1 介绍类与对象 类和对象的关系 时间万物皆对象 对象是具体的事物 是类的具体事例 类是抽象的概念 是对象的模板 new关键字是创建实例对象最重要的标志 Dog duoduo new Dog Dog lucky new Dog 这样就创
  • RabbitMQ--扩展--11--均衡负载

    RabbitMQ 扩展 11 均衡负载 1 场景 面对大量业务访问 高并发请求 可以使用高性能的服务器来提升RabbitMQ服务的负载能力 当单机容量达到极限时 可以采取集群的策略来对负载能力做进一步的提升 但这里还存在一个负载不均衡的问题
  • consul作为配置中心

    Conusl可以作为注册中心 也可以作为配置中心 作为配置中心 使用起来也很方便 大多数都是配置 Consul的安装这里不说了 主要是说明java后端怎么使用 所以本次采用windows版本的consul 引入和配置 2个文件 pom配置
  • ajax error弹框,完美解决:layui弹出层无法关闭/ajax请求成功,但是不进入success,也不进入error...

    我在使用一个叫做H ui admin的后台模板 基于layui 做一套管理系统 业务 点击按钮 出现弹出层 ajax通过接口向后台发送数据 成功后 关闭弹出层 前面都没有什么问题 就是到了关闭弹出层 真的快把我逼疯了 1 首先 我认为是la
  • IFrame和Ajax比较

    说到比较 可能我是需要把这连个东西都给大家介绍一下的 但是介于大家都已经有了很多的理解 我就简单的说了 Ajax 是指一种创建交互式网页应用的网页开发技术 主要是利用XmlHttpRequest对象 该对象在 Internet Explor
  • 网络基础知识

    网络基础概念 网络 两个设备之间 通过介质连接起来就形成网络 上网 就是连接到英特网 要想使pc机之间关联起来 要设置地址 1 IP地址 子网掩码 网关 2 MAC地址 物理地址 window键 R会跳出运行窗口 输入ncpa cpl可以调
  • UGUI ScrollRect使用(实现滑动效果)

    在工作中需要实现滑动效果 网上一番资料查询之后了解到ScrollRect组件 记录一下使用过程中遇到的问题与解决办法 先来看一下ScrollRect的属性 其中Content和Viewport非常重要 能否实现滑动效果取决于这两个属性 至于
  • GCC AVR(Atmel Studio+ AVR Studio)如何在程序存储器(flash)空间存放字符串常量和使用字符串常量

    目录 一 program memory和data memory 二 如何将字符串变量定义到program memory flash 三 如何读取字符串变量 四 PSTR 五 使用strcpy 函数将字符串常量拷贝字符串缓冲区会占用大量的da
  • 报错curl: (7) Failed to connect to 127.0.0.1 port xxxx: Connection refused

    pyenv install xxx 报错curl 7 Failed to connect to 127 0 0 1 port xxxx Connection refused的解决方法 问题重现截图 在查看下面的原因和使用解决方法之前 确保自
  • .NET EF Core 使用 DBFrist 连接sql server

    目录 1 创建项目并导导入包 2 使用命令行生成实体类和上下行类 3 修改上下文类 从配置文件 appsettings json 读取数据库连接字符串 4 编辑Program cs文件 5 测试连接是否成功 1 创建一个控制器 2 修改控制
  • SpringBoot AOP使用

    AOP即面向切面编程 其存在的目的就是为了解耦 通过AOP的实现 可以让业务逻辑只关心业务本身 而不用在意其他的事情 无需改动原有代码 实现无侵入增加部分能力 在系统日志处理 系统事务处理 系统安全验证 系统数据验证等多个场景中都有可能使用