栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring Boot 自定义注解,AOP 切面统一打印出入参请求日志

1. 问题提出
  • 在访问接口时,需要查看入参和出参信息,并且将这些访问信息存储到数据库中,实现接口问题排查

文章目录
  • 1. 问题提出
  • 2. 整体方案
    • 2.1 添加AOP Maven依赖
    • 2.2 自定义日志注解
    • 2.3 配置AOP切面
    • 2.4 添加切点的开关注解
  • 完整代码:
    • com.example.aspect.DemoApplication.java
    • com.example.aspect.controller.demoController.java
    • com.example.aspect.log.annotation.EnableAOPLog.java
    • com.example.aspect.log.annotation.LogAnnotation.java
    • com.example.aspect.log.aop.LogImportSelector.java
    • com.example.aspect.log.aop.WebLogAspect.java
    • com.example.aspect.log.params.SysLogParam.java
  • 相关问题
    • 问题1: com.example.aspect.log.aop.WebLogAspect.java中将logAnnotation放在输入参数中
    • 问题2:输入输出参数信息需要脱敏
    • 问题3: 输入参数需要存放在mongodb中

2. 整体方案
  • 自定义日志注解:在注解处添加切面,每次调用方法时,就会触发AOP
  • 配置AOP切面
  • 配置AOP开关

    这里采用自定义注解和AOP切面方式,实现入参和出参的日志信息打印与存储
2.1 添加AOP Maven依赖

在pom.xml文件中添加依赖,这里不用依赖也行,需要的时候加上:

 
2.2 自定义日志注解 
package com.example.aspect.log.annotation;
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)   // 在运行时使用该注解
@Target({ElementType.METHOD})        // 注解作用于方法上
@Documented							// 注解将包含在JavaDoc上
public @interface LogAnnotation {   // 注解名为LogAnnotation

    // 模块名称
    String module();

    // 是否从当前的上下文中获取获取 url、ip
    boolean request() default true;

    // 记录和打印请求参数
    boolean recordParam() default true;

    // 记录和打印响应参数
    boolean recordResponse() default true;

    // 记录到数据库
    boolean recordToDB() default false;
}
2.3 配置AOP切面

在配置AOP切面之前,我们需要了解下aspectj相关注解的作用

  • @Aspect: 声名该类为一个注解类
  • @Pointcut: 定义一个切点,后面跟随一个表达式,表达式可以定义为切某个注解,也可以切某个package下的方法

切点定义好后,就是围绕这个切点做文章:

  • @Before: 在切点之前,织入相关代码
  • @After: 在切点之前后,织入相关代码
  • @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
  • @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
  • @Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;

    正文:
    定义一个LogAnnotaion.java的切面类,声明一个切点:
// 以自定义@LogAnnotaion注解为切点,指向刚才注解的位置
@Pointcut("@annotation(com.example.aspect.log.annotation)")
public void webLog(){}

定义@Around环绕,用户何时执行切点:

@Around("webLog()")
public Object doAround(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation) throws Throwable {
	long startTime = System.currentTimeMillis();  
	Object result = joinPoint.proceed();  // 执行切点
	// 打印出参
	logger.info("Response Args  : {}", new Gson().toJson(result));
	// 执行耗时
	logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
	// 返回接口返参结果,这里必须要有返回
	return result;
}
2.4 添加切点的开关注解

log.aop.LogImportSelector.java

package com.example.aspect.log.aop;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class LogImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        return new String[]{WebLogAspect.class.getName()};
    }
}

ImportSelector接口是至spring中导入外部配置的核心接口,在SpringBoot的自动化配置和@EnableXXX(功能性注解)都有它的存在,关于SpringBoot的分析可以参考:深入理解SpringBoot的自动装配。

log.annotation.EnableAOPLog.java

package com.example.aspect.log.annotation;

import com.example.aspect.log.aop.LogImportSelector;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LogImportSelector.class)
public @interface EnableAOPLog {
}
完整代码:

com.example.aspect.DemoApplication.java
package com.example.aspect;

import com.example.aspect.log.annotation.EnableAOPLog;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@EnableAOPLog   // 打开日志切面开关
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}

com.example.aspect.controller.demoController.java
package com.example.aspect.controller;

import com.example.aspect.log.annotation.LogAnnotation;
import com.example.aspect.log.params.LoginParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping(value = "/v1")
public class demoController {

    @PostMapping(value = "/login")
    @LogAnnotation(module = "serviceA")  // 添加日志注解
    public String login(@RequestBody LoginParam loginParam){
        log.info("login方法开始执行");
        return loginParam.getUsername();
    }
}
com.example.aspect.log.annotation.EnableAOPLog.java
package com.example.aspect.log.annotation;

import com.example.aspect.log.aop.LogImportSelector;
import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(LogImportSelector.class)
public @interface EnableAOPLog {
}
com.example.aspect.log.annotation.LogAnnotation.java
package com.example.aspect.log.annotation;
import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface LogAnnotation {

    // 模块
    String module();

    // 是否从当前的上下文中获取获取 url、ip
    boolean request() default true;

    // 记录和打印请求参数
    boolean recordParam() default true;

    // 记录和打印响应参数
    boolean recordResponse() default true;

    // 记录到数据库
    boolean recordToDB() default false;
}
com.example.aspect.log.aop.LogImportSelector.java
package com.example.aspect.log.aop;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class LogImportSelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {

        return new String[]{WebLogAspect.class.getName()};
    }
}
com.example.aspect.log.aop.WebLogAspect.java
package com.example.aspect.log.aop;

import com.example.aspect.log.annotation.LogAnnotation;
import com.example.aspect.log.params.SysLogParam;
import com.example.aspect.utils.ParemetersDesensitive;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;



@Slf4j
@Aspect
public class WebLogAspect {

    @Around("@annotation(logAnnotation)")
    public Object doAround(ProceedingJoinPoint joinPoint, LogAnnotation logAnnotation) throws Throwable {

        SysLogParam sysLogParam = new SysLogParam();
        HttpServletRequest httpServletRequest = ((ServletRequestAttributes) RequestContextHolder
                .currentRequestAttributes()).getRequest();

        // 记录进入时间
        long startTime = System.currentTimeMillis();
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sysLogParam.setLogTime(dateFormat.format(new Date(startTime)));

        // 记录归属模块/服务
        sysLogParam.setModule(logAnnotation.module());

        // todo:记录登录用户

        // 记录客户端相关信息
        if (logAnnotation.request() && httpServletRequest != null) {
            sysLogParam.setMethod(httpServletRequest.getMethod());
            sysLogParam.setIp(null);
            sysLogParam.setUrl(httpServletRequest.getRequestURI());
        }

        // 记录请求参数
        if (logAnnotation.recordParam()) {
            ParemetersDesensitive.recordRequestParam(joinPoint, sysLogParam);
        } else {
            sysLogParam.setParams("[-]");
        }
        log.info("开始请求:{} {}, req:{} ", sysLogParam.getMethod(), sysLogParam.getUrl(), sysLogParam.getParams());

        Object result = null;
        try {
            // 执行方法
            result = joinPoint.proceed();
            sysLogParam.setFlag(Boolean.TRUE);
        } catch (Exception e) {
            sysLogParam.setFlag(Boolean.FALSE);
            log.error("请求报错{}", e.getMessage());
            throw e;
        } finally {
            try {
                if (logAnnotation.recordResponse()) {
                    log.info("结束请求:{} {}, time:{}, req:{}, resp:{}", sysLogParam.getMethod(), sysLogParam.getUrl(), (System.currentTimeMillis() - startTime), sysLogParam.getParams(), ParemetersDesensitive.resultStr(result));
                } else {
                    log.info("结束请求:{} {}, time:{}, req:{}", sysLogParam.getMethod(), sysLogParam.getUrl(), (System.currentTimeMillis() - startTime), sysLogParam.getParams());
                }
                if (logAnnotation.recordToDB()) {
                    recordToDb(sysLogParam);
                }
                log.trace("退出日志切面");
            } catch (Exception e) {
                log.warn("记录日志时,出现异常");
            }
        }
        return result;
    }

    // 记录执行参数到数据库
    private void recordToDb(SysLogParam sysLog) {
//        if (logService == null) {
//            return;
//        }
//        CompletableFuture.runAsync(() -> {
//            try {
//                log.trace("日志落库开始:{}", sysLog);
//                logService.save(sysLog);
//                log.trace("开始落库结束:{}", sysLog);
//            } catch (Exception e) {
//                log.error("落库失败:{}", e.getMessage());
//            }
//        }, taskExecutor);
    }

}

com.example.aspect.log.params.SysLogParam.java
package com.example.aspect.log.params;

import lombok.Data;

import java.io.Serializable;

@Data
public class SysLogParam implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private String id;
    
    private String ip;
    
    private String loginName;
    
    private String userId;
    
    private String userName;
    
    private String module;
    
    private String method;
    
    private String url;
    
    private String params;
    
    private Boolean flag;
    
    private String logTime;
    
    private String durationTime;
}
相关问题 问题1: com.example.aspect.log.aop.WebLogAspect.java中将logAnnotation放在输入参数中 问题2:输入输出参数信息需要脱敏 问题3: 输入参数需要存放在mongodb中
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/952646.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号