IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> AOP+注解方式记录、打印请求信息 -> 正文阅读

[Java知识库]AOP+注解方式记录、打印请求信息

应对场景

在一些项目中,会有这样的一个场景,对某个接口所接受到的所有请求,进行入库记录,并记载该次请求的状态(成功与否),或业务中涉及任务时,也需要对该任务进行入库记录甚至跟踪。

最简单的实现方式,是在controler层接收到请求后,解析入参并入库保存请求(任务)信息,service层处理完逻辑后再解析返回并更新任务信息。

但这只是一次顺利的请求并成功返回的场景,如果接口返回失败,或在service层的任意地方发生异常、err,都让任务的数据库更新操作很难完成。当然,应对代码抛出异常的问题,结合全局参数异常捕获器(@RestControllerAdvice)也能有效解决任务更新问题,但代码总归是比较臃肿。

解决方案

使用AOP编程思想,结合反射、注解等知识,为接口搭建“门卫”,达到出入记录的目的。

详细逻辑是,在controller层接收到请求前,对请求进行解析并入库记录,在controller层接口返回后(包括成功,失败,异常)对请求进行解析并入库更新。

思路整理:

  • 在接口方法上加上自定义注解,以接口方法为接入点处理逻辑
  • @Before()在切点前解析并记录请求信息
  • @AfterReturning()在切点返回内容后解析并更新请求信息

具体实现

自定义注解Warehousing.java

import java.lang.annotation.*;

/**
 * 入库注解
 */
@Retention(RetentionPolicy.RUNTIME) //作用于运行时
@Target({ElementType.METHOD})       //作用于方法
@Documented                         //包含在JavaDoc 中
public @interface Warehousing {

    /**
     * 描述信息
     *
     * @return
     */
    String description() default "";
}

入库切片类WarehousingAspect.java,实现上注解的具体动作。

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 入库切片类
 * 解析web入参出参,对算路任务进行解析入库
 * @author Wyhao
 * @date 2022/1/28
 **/
@Component
@Aspect
@Slf4j
public class WarehousingAspect {

    @Autowired
    private WarehousingService warehousingService;

    /** 自定义 @Warehousing 注解为切入点 */
    @Pointcut("@annotation(com.asiainfo.upc.northward.aspect.Warehousing)")
    public void warehousing() {}

    /**
     * 环绕
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("warehousing()")
    public Object doAroud(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();

        /**
         * 执行切点
         * 执行切点后,会去依次调用
         * @Before -> 接口逻辑代码 ->
         * @After ->
         * @AfterReturning;
         */
        Object result = proceedingJoinPoint.proceed();

        return result;
    }

    /**
     * 在切入点之后切入
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before("warehousing()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {

        /**  记录打印http请求日志 可不加**/
        //获取请求信息
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        log.info("===============================request start===============================");
        log.info("URL           : {}",request.getRequestURL().toString());
        log.info("HTTP Method   : {}", request.getMethod());
        log.info("Class Method  : {},{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        log.info("IP            : {}", request.getRemoteAddr());
        Object[] args = joinPoint.getArgs();    //获取请求入参
        log.info("Request Args  : {}", JSONObject.toJSONString(args));

        
        /** 记录任务信息 **/
        User user = joinPoint.getArgs()[0];	//拿到入参实体类
        startInsert(user);	//进行入库操作

    }

    /**
     * 在切点返回内容后处理
     *
     * @param joinPoint
     * @param rvt		方法返回对象
     * @throws Throwable
     */
    @AfterReturning(value = "warehousing()",returning = "rvt")
    public void doAfterReturning(JoinPoint joinPoint, Object rvt) throws Throwable {

        /** 更新任务信息 **/
        User user = joinPoint.getArgs()[0];	//拿到入参实体类
        Result rvt = (Result)rvt;	//Result 该接口具体返回的类型
        endInsert(user, rvt);

        /**  记录打印http请求日志**/
        log.info("Response Args  : {}", JSONObject.toJSONString(rvt));
        log.info("===============================request end===============================");
    }
    
    /**
     * 请求记录入库
     * @param user	需要入库的实体类
    **/
    private void startInsert(User user) {
        //具体入库过程不再展开,使用mybatis根据具体情况执行即可
    }
    
    /**
     * 请求返回内容入库
     * @param user	需要入库的实体类
     * @param user	需要入库的实体类
    **/
    private void endInsert(User user,Result rvt) {
        //具体入库更新过程不再展开,使用mybatis根据具体情况执行即可
    }
}

入参类User.java

import lombok.Data;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@Data
public class User {
    private String string;
}

响应类Result.class

import lombok.Data;

@Data
public class Result<T> implements Serializable {
    private String msg = "success";
    private int code = 200;
    private T data;
    
    public Result() {
    }

    public Result(T data) {
        this.data = data;
    }

    public Result(Throwable e) {
        this.msg = e.toString();
        this.code = 500;
    }
}

注解的使用示例Controller.java

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*
import javax.validation.Valid;

/**
 * @author Wyhao
 * @date 2021/9/18
 **/
@Slf4j
@RestController
public class Controller {
    @PostMapping("/updataUser")
    @Warehousing(description = "任务入库")	//description 为预留字段,不做处理也可
    public Result<String> updataUser(User user) {
		//具体接口逻辑
    }
}

总结

  1. 这种模式下的请求入库记录、日志打印等操作,代码简洁,逻辑简单,且具备很强的可迁移性。
  2. 这样的设计模式,还为接口的其他处理预留了很多可操作空间。
  3. 本文为了能更直观、简洁的说明过程,入库逻辑写死了入参类型与出参类型,注解@Warehousing()使用范围有局限性,有兴趣或有需求的兄弟,可以对入库方法startInsert()等进行重写,使用反射技术、策略模式、模板方法等,实现@Warehousing()的通用性,对所有接口的入参出参进行记录并入库。(这块儿逻辑甚至可以考虑写在网关里,但一版项目不会有记录入库所有http请求的业务需求)。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-02-26 11:16:56  更:2022-02-26 11:19:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:30:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码