Spring Boot使用AOP实现拦截器

2023-11-18

Spring Boot 专栏:https://blog.csdn.net/dkbnull/category_9278145.html

Spring Cloud 专栏:https://blog.csdn.net/dkbnull/category_9287932.html

GitHub:GitHub - dkbnull/SpringBootDemo: SpringBootDemo

Gitee:SpringBootDemo: SpringBootDemo

当我们使用Spring Boot发布后台接口时,如果多个接口存在相同的操作,比如对参数进行非空校验或验签。这些操作都是在具体业务代码之前,如果我们每个接口都进行独立编码,即使我们将验签操作封装成方法,也会有大量重复代码。这时我们可以使用AOP实现拦截器,对参数进行非空校验或验签。

1、引包

首先引包。

<dependency>
   <groupId>org.aspectj</groupId>
   <artifactId>aspectjweaver</artifactId>
   <version>1.9.1</version>
</dependency>

aspectjweaver用于引入AOP的相关的注解,如@Aspect、@Pointcut

2、自定义异常类

新建GlobalException异常类,用于捕捉全局异常。

public class GlobalException extends Exception {

    private static final long serialVersionUID = -238091758285157331L;

    private String code;
    private String message;

    public GlobalException() {
        super();
    }

    public GlobalException(String message) {
        super(message);
        this.message = message;
    }

    public GlobalException(String code, String message) {
        super(code + ": " + message);
        this.code = code;
        this.message = message;
    }

    public GlobalException(String message, Throwable throwable) {
        super(message, throwable);
        this.message = message;
    }

    public GlobalException(Throwable throwable) {
        super(throwable);
    }

    public String getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return code + ": " + message;
    }
}

3、全局异常处理器

新建全局异常处理器,用户处理捕捉到的异常。

import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public JSONObject exceptionHandler(HttpServletRequest request, Exception e) {
        return ReturnMessage.createReturnMessage("4000", e.getMessage());
    }
}

4、自定义AOP

自定义AOP,对接口入参进行非空校验和验签;并对接口返回参数进行签名。

import cn.wbnull.springbootdemo.boot.GlobalException;
import cn.wbnull.springbootdemo.constant.DemoConstants;
import cn.wbnull.springbootdemo.util.JSONUtils;
import cn.wbnull.springbootdemo.util.StringUtils;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class SignAop {

    /**
     * 声明一个切入点,范围为controller包下所有的类
     * 注:作为切入点签名的方法必须返回void类型
     */
    @Pointcut("execution(public * cn.wbnull.springbootdemo.controller.*.*(..))")
    private void signAop() {

    }

    /**
     * 前置通知:在某连接点之前执行的通知,但这个通知不能阻止连接点之前的执行流程(除非它抛出一个异常)
     *
     * @param joinPoint
     * @throws Exception
     */
    @Before("signAop()")
    public void doBefore(JoinPoint joinPoint) throws Exception {
        Object[] objects = joinPoint.getArgs();
        String sign = objects[0].toString();
        String timestamp = objects[1].toString();
        String data = objects[2].toString();

        if (StringUtils.isEmpty(sign) || StringUtils.isEmpty(timestamp) ||
                StringUtils.isEmpty(data)) {
            throw new GlobalException("sign or timestamp or data is null");
        }

        String md5String = "data=" + data + "&key=1234567890&timestamp=" + timestamp;
        String signNow = DigestUtils.md5Hex(md5String);

        if (!sign.equalsIgnoreCase(signNow)) {
            throw new GlobalException("sign is error");
        }
    }

    /**
     * 后置通知:在某连接点正常完成后执行的通知,通常在一个匹配的方法返回的时候执行
     *
     * @param joinPoint
     * @param params
     * @return
     */
    @AfterReturning(value = "signAop()", returning = "params")
    public JSONObject doAfterReturning(JoinPoint joinPoint, JSONObject params) {
        String data = JSONUtils.getJSONString(params, DemoConstants.DATA);
        long timestamp = System.currentTimeMillis() / 1000;

        String md5String = "data=" + data + "&key=1234567890&timestamp=" + timestamp;
        String sign = DigestUtils.md5Hex(md5String);

        params.put(DemoConstants.TIMESTAMP, timestamp);
        params.put(DemoConstants.SIGN, sign);

        return params;
    }
}

5、控制器

新建登录接口控制器LoginController

import com.alibaba.fastjson.JSONObject;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Scope("prototype")
public class LoginController extends BaseController {

    @PostMapping(value = "/login")
    public JSONObject login(
            @RequestParam(value = "sign") String sign,
            @RequestParam(value = "timestamp") String timestamp,
            @RequestParam(value = "data") String data
    ) throws Exception {
        return baseService.login(data);
    }
}

6、服务类

新建登录接口服务类LoginService

import cn.wbnull.springbootdemo.util.JSONUtils;
import cn.wbnull.springbootdemo.util.LoggerUtils;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Service;

@Service
public class LoginService {

    public JSONObject login(Object data) throws Exception {
        JSONObject responseParams = new JSONObject();

        JSONObject requestParams = JSONObject.parseObject(data.toString());
        if (JSONUtils.getJSONString(requestParams, "username").equalsIgnoreCase(
                JSONUtils.getJSONString(requestParams, "password"))) {
            responseParams.put("code", "1000");
            responseParams.put("message", "SUCCESS");
        } else {
            responseParams.put("code", "2000");
            responseParams.put("message", "FAIL");
        }

        return responseParams;
    }
}

7、测试

(1)、当传入参数缺少时,接口自动提示(未进入AOP):

(2)、当传入参数正常,但存在某参数为空时,AOP校验参数是否为空:

(3)、当传入参数正常且都不为空时,AOP执行@Before注释的方法进行验签操作:

(4)、若签名校验通过,则执行具体业务代码,并在接口返回时进入到@AfterReturning注释的方法进行签名返回:

8、日志记录

增加日志记录,更方便直观的查看代码执行顺序。

(1)、引包

<dependency>
   <groupId>log4j</groupId>
   <artifactId>log4j</artifactId>
   <version>1.2.17</version>
</dependency>

(2)、配置日志记录格式log4j.properties

log4j.rootLogger=debug,Console,logInfo,logError,logDebug
log4j.category.org.springframework=debug,Console,logInfo,logError,logDebug
#输出到控制台
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.Target=System.out
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%C.%M(%F\:%L)] || %m%n
#输出到文件
log4j.appender.logInfo=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logInfo.File=${pwd}/SpringBootDemoLogs/SpringBootDemoLog_
log4j.appender.logInfo.Append=true
log4j.appender.logInfo.DatePattern=yyyy-MM-dd'.log'
log4j.appender.logInfo.Threshold=ALL
log4j.appender.logInfo.layout=org.apache.log4j.PatternLayout
log4j.appender.logInfo.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%C.%M(%F\:%L)] || %m%n
#错误日志单独记录
log4j.appender.logError=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logError.File=${pwd}/SpringBootDemoLogs/SpringBootDemoErrorLog_
log4j.appender.logError.Append=true
log4j.appender.logError.DatePattern=yyyy-MM-dd'.log'
log4j.appender.logError.Threshold=ERROR
log4j.appender.logError.layout=org.apache.log4j.PatternLayout
log4j.appender.logError.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%C.%M(%F\:%L)] || %m%n
#Debug日志单独记录
log4j.appender.logDebug=org.apache.log4j.DailyRollingFileAppender
log4j.appender.logDebug.File=${pwd}/SpringBootDemoLogs/SpringBootDemoDebugLog_
log4j.appender.logDebug.Append=true
log4j.appender.logDebug.DatePattern=yyyy-MM-dd'.log'
log4j.appender.logDebug.Threshold=DEBUG
log4j.appender.logDebug.layout=org.apache.log4j.PatternLayout
log4j.appender.logDebug.filter.infoFilter=org.apache.log4j.varia.LevelRangeFilter
log4j.appender.logDebug.filter.infoFilter.LevelMin=DEBUG
log4j.appender.logDebug.filter.infoFilter.LevelMax=DEBUG
log4j.appender.logDebug.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%C.%M(%F\:%L)] || %m%n

(3)、Logger工具类LoggerUtils

import org.apache.log4j.Logger;

public class LoggerUtils {

    private static Logger logger;

    public static Logger getLogger() {
        if (logger == null) {
            logger = Logger.getLogger("SpringBootDemoLogs");
        }
        return logger;
    }
}

(4)、SignAop增加日志记录

@Before("signAop()")
public void doBefore(JoinPoint joinPoint) throws Exception {
    //code

    String strLog = "[" + Thread.currentThread().getId() + "]" + "[请求方法] " + joinPoint.getSignature().getName() + " ||";
    LoggerUtils.getLogger().info(strLog + "[请求参数] sign=" + sign + ",timestamp=" + timestamp + ",data=" + data);

    //code
}

@AfterReturning(value = "signAop()", returning = "params")
public JSONObject doAfterReturning(JoinPoint joinPoint, JSONObject params) {
    //code

    String strLog = "[" + Thread.currentThread().getId() + "]" + "[请求方法] " + joinPoint.getSignature().getName() + " ||";
    LoggerUtils.getLogger().info(strLog + "[返回参数] " + params);

    return params;
}

(5)、Postman请求,日志记录如下

[INFO][2018-09-25 23:59:46,483][cn.wbnull.springbootdemo.boot.aop.SignAop.doBefore(SignAop.java:50)] || [21][请求方法] login ||[请求参数] sign=fafb23d6e7daae823ca3d4a15a2ab4bd,timestamp=1537888124,data={"username":"123","password":"123"}
[INFO][2018-09-25 23:59:46,499][cn.wbnull.springbootdemo.controller.LoginController.login(LoginController.java:26)] || [21] LoginController
[INFO][2018-09-25 23:59:46,604][cn.wbnull.springbootdemo.service.LoginService.login(LoginService.java:30)] || [21] LoginService
[INFO][2018-09-25 23:59:46,615][cn.wbnull.springbootdemo.boot.aop.SignAop.doAfterReturning(SignAop.java:84)] || [21][请求方法] login ||[返回参数] {"code":"1000","sign":"18954bd1a668e0d6fc98e4e15386352f","message":"SUCCESS","timestamp":1537891186}

GitHub:GitHub - dkbnull/SpringBootDemo: SpringBootDemo

微信:Spring Boot入门:使用AOP实现拦截器

微博:Sina Visitor System

知乎:Spring Boot使用AOP实现拦截器 - 知乎

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

Spring Boot使用AOP实现拦截器 的相关文章

随机推荐

  • 【蓝桥杯试题】试题 算法训练 印章

    试题 算法训练 印章 资源限制 时间限制 1 0s 内存限制 256 0MB 问题描述 共有n种图案的印章 每种图案的出现概率相同 小A买了m张印章 求小A集齐n种印章的概率 输入格式 一行两个正整数n和m 输出格式 一个实数P表示答案 保
  • 正则实现去除字符串前后空格

    前言 正则去掉字符串前后空格 1 去除左空格 str1是处理后的 let str1 str replace s g 2 去除右空格 str2是处理后的 let str2 str replace s g 3 去除左右空格 let str3 s
  • qt 多边形填充 生成语义分割的mask 灰度图

    qt多边形的绘制 填充与显示 直接在QWidget界面显示 重写paintEvent即可 QtTest QtTest QWidget parent QMainWindow parent ui setupUi this void QtTest
  • linux c 语言小结

    linux c 语言小结 gdb 使用 gdb是调试linux c语言代码的 所以要调试linux c语言 先要 gcc g 文件名 才能开始调试 gdb 命令 首先在命令行中输入 gdb 调试的基本代码 list 展示 s 进入函数内部
  • 如何理解原码,反码,补码转换

    首先 无论原码还是反码还是补码都是二进制形式 有效位数是7位 最后 最左边 一位是符号位 用以区别正负 数据在内存中是以 补码 的形式存放 一 区别 原码 最高位 0 是正数 最高位 1 是负数 反码 正数 与原码一致 负数 最高位不变 其
  • 为什么我的AJAX请求处理成功了,还报404错误

    为什么我的AJAX请求处理成功了 还报404错误 忘了添加 ResponseBody注解 参考博客 https blog csdn net jiaotuwoaini article details 53445182
  • 标注数字对应的文本_循环嵌套_全局暂元

    标注数字对应的文本 循环嵌套 全局暂元 1 清洗数据 cd path EPS data use temp ybmy nodest dta clear gen hy3 real substr string hy4 1 3 gen hy2 re
  • 安全客&FREEBUF 文章阅读量似乎有点问题

    安全客 FREEBUF 文章阅读量似乎有点问题 随手写个脚本测试一下 其中freebuf那篇文章是很老的文章了 居然阅读量还是嗖嗖的涨 下次是不是可以写个折线图分析一下 看看每次加的是不是有规律的 scripts cat freebufcu
  • OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000b4680000, 297795584, 0)

    在测试环境中 出现如下报错 经排查发现是物理机内存不足 此时对应服务进程已不在 OpenJDK 64 Bit Server VM warning INFO os commit memory 0x00000000b4680000 297795
  • TIM的一些配置参数

    1 使能TIM时钟 RCC APB1PeriphClockCmd RCC APB1Periph TIM ENABLE 2 基础设置 TIM TimeBaseStructure TIM Period 计数值 TIM TimeBaseStruc
  • osg fbo(四),将颜色缓冲区图片中的牛通过shader变绿

    osg fbo 三 中 把整个屏幕变绿了 因为是把shader添加到了颜色缓冲区图片上了 如果只想把牛变绿 就需要把shader添加到原始场景根中 即 osg ref ptr
  • Linux笔记:终端复用与管理工具screen和tmux

    文章目录 目的 screen 快速入门 更多介绍 tmux 快速入门 窗口与窗格 更多介绍 总结 目的 通过终端使用Linux时比较纠结的是一个终端通常同一时间只能做一件事 虽然可以将任务放入后台 但是对于有输出或交互的任务放入后台并不是一
  • Linux 等待队列

    1 Linux内核中等待队列简介 Linux 内核等待队列可以用于许多用途 如中断 进程同步 以及定时 等待队列实现了在事件上的条件等待 希望等待特定事件的进程把自己放进合适的队列 并且放弃控制权限 因此等待队列表示一组睡眠的进程 当某一事
  • 基于Sql-Labs靶场的SQL注入-11~16关

    目录 Less 11 基于POST表单提交方式的字符型注入 爆破数据库名 爆破表名 爆破列名 爆破字段值 Less 12 基于POST表单提交方式的字符型注入 Less 13 基于POST表单提交方式的报错注入 爆破数据库名 爆破表名 爆破
  • HDLBits — Verilog Practice(每日一题)

    HDLBits Verilog Practice 每日一题 一 Getting Started 1 Getting Started 一 Getting Started 1 Getting Started 问题描述 Build a circu
  • C/C++面试笔试知识点总结

    C C 面试笔试知识点总结 1 const关键字的作用 变量 参数 返回值 2 什么是死锁 3 造成死锁的4个必要条件 4 如何避免死锁 5 static关键字作用 6 c c 中内存可以划分为几个部分 7 new 和 malloc的区别
  • DC-2靶机渗透测试

    1 信息收集 使用arp scan l扫描 扫描靶机开放端口 直接访问192 168 188 165发现无法访问被重定向到了 http dc 2 修改hosts文件 hosts文件是linux系统中负责ip地址与域名快速解析的文件 Linu
  • 关于实体类中多层嵌套集合,用mybatis的collection标签可以快速实现

    一 首先分析需求在有三个实体类的情况下 好比user类 Student YearClass类 结构如下 最外层类 public class user private String name private String age privat
  • png文件格式详解【转】

    5 2 2 PNG图像文件存储结构 1 PNG文件存储结构的格式可以在http www w3 org TR REC png htm上找到定义 BMP文件总体上由两部分组成 分别是PNG文件标志和数据块 chunks 如表5 8所示 其中数据
  • Spring Boot使用AOP实现拦截器

    Spring Boot 专栏 https blog csdn net dkbnull category 9278145 html Spring Cloud 专栏 https blog csdn net dkbnull category 92