背景

开发项目的时候,对出入参可以通过idea 的debug模式实现。但是项目一旦发布到线上如果发现有数据存在问题,那么究竟是哪一个环节出现的问题呢。有些情况就会不好分析。或者在系统间互相调用的时候,自己作为被调用方,如何证明调用方的参数正确与否呢。当然是通过日志实现,可以通过如果log日志输出实现。但是更建议写成一个通用的,要么放到基础组件中,也可以放到各自微服务中选择是否启用。

分析

思考一番个人觉得可以采用三种方式实现

  1. 拦截器
  2. 过滤器
  3. aop切面(个人更推荐使用)
    拦截器和过滤器实现方式差不多,因此我就只说其中一个和aop切面实现。

filter实现

首先在项目启动的时候注入过滤器bean

@Configuration
public class FilterConfig {@Beanpublic FilterRegistrationBean timeFilter() {FilterRegistrationBean bean = new FilterRegistrationBean();bean.setFilter(new TimeFilter());bean.addUrlPatterns("/remote/*");return bean;}
}

过滤实现bean

@Component
public class TimeFilter implements Filter {private Logger log = LoggerFactory.getLogger(TimeFilter.class);@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpServletRequest = (HttpServletRequest) request;ServletRequest requestWrapper = null;if(HttpMethod.GET.toString().equalsIgnoreCase(httpServletRequest.getMethod())){log.info("入参参数--get-----" + JSONObject.toJSONString(httpServletRequest.getQueryString()));}else if(HttpMethod.POST.toString().equalsIgnoreCase(httpServletRequest.getMethod())){requestWrapper = new BodyReaderHttpServletRequestWrapper((HttpServletRequest) request);byte[] bodyBytes = StreamUtils.copyToByteArray(requestWrapper.getInputStream());String body = new String(bodyBytes, httpServletRequest.getCharacterEncoding());log.info("入参参数--post-----" + body);}long start = System.currentTimeMillis();if (null == requestWrapper) {chain.doFilter(request, response);} else {chain.doFilter(requestWrapper, response);}log.info(request.getRemoteAddr() + ":" + request.getLocalPort() + httpServletRequest.getRequestURI() + " 耗时 : " + (System.currentTimeMillis() - start) + "ms");}@Overridepublic void destroy() {}
}

如果用过滤器实现,目前我只想到的在请求中通过流的方式把请求方式拿取出来。但是使用流有一个重要的问题:

流只能读取一次, 流只能读取一次, 流只能读取一次

重要的事情说三遍

因此如果硬要使用, 那必须得处理一下request stream。

public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper {private final byte[] body;public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException {super(request);String bodyString = HttpHelper.getBodyString(request);body = bodyString.getBytes(Charset.forName("UTF-8"));}@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream bais = new ByteArrayInputStream(body);return new ServletInputStream() {@Overridepublic int read() throws IOException {return bais.read();}@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener readListener) {}};}
}
public class HttpHelper {public static String getBodyString(HttpServletRequest request) throws IOException {StringBuilder sb = new StringBuilder();InputStream inputStream = null;BufferedReader reader = null;try {inputStream = request.getInputStream();reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8")));char[] bodyCharBuffer = new char[1024];int len = 0;while ((len = reader.read(bodyCharBuffer)) != -1) {sb.append(new String(bodyCharBuffer, 0, len));}} catch (IOException e) {e.printStackTrace();} finally {if (inputStream != null) {try {inputStream.close();} catch (IOException e) {e.printStackTrace();}}if (reader != null) {try {reader.close();} catch (IOException e) {e.printStackTrace();}}}return sb.toString();}
}

总结filter实现

这样就可以通过流获取请求参数并且request中还有流。这种实现方法有一个弊端:先把流转成字符串,又把字符串转回流,这样的目的就为了输出请求参数。在我看来有点好钢没有用在刀刃上的感觉。毕竟我们做这个也只是一个辅助性的util,当线上并发大的时候这样来回转换流会拉低不少系统的并发。

切面实现(个人比较推荐)

aop是spring很重要的一个属性,因此在这里可以使用一下。直接上干货有两种实现方式二选一,两种方式大家按需选择

方式一

pom引用

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
@Aspect
@Component
@Order(1)
@Slf4j
public class AspectConfig {@Pointcut("execution(* com.xxx.xxx.xxx.controller..*.*(..))")private void webLog(){}@Before(value="webLog()")public void before(JoinPoint joinPoint){ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();log.info("请求日志的打印");log.info("请求地址:{}", Optional.ofNullable(request.getRequestURI().toString()).orElse(null));log.info("请求方式:{}",request.getMethod());log.info("请求类方法:{}",joinPoint.getSignature());log.info("请求类方法参数:{}", JSONObject.toJSONString(JSONObject.toJSONString(filterArgs(joinPoint.getArgs()))));}private List<Object> filterArgs(Object[] objects) {return Arrays.stream(objects).filter(obj -> !(obj instanceof MultipartFile)&& !(obj instanceof HttpServletResponse)&& !(obj instanceof HttpServletRequest)).collect(Collectors.toList());}
}

此种方式在请求之前拦截,但是没法实现整个请求所消耗的时间。在拦截配置中配置需要拦截的包,这种方式适合只拦截参数并且不对请求时长数据做输出的。

方式二

@Aspect
@Component
@Order(1)
@Slf4j
public class AspectConfig {@Around("@within(org.springframework.web.bind.annotation.RestController)" +"||@within(org.springframework.stereotype.Controller)")public Object after(ProceedingJoinPoint joinPoint) throws Throwable{ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = requestAttributes.getRequest();log.info("请求日志的打印");log.info("请求地址:{}", Optional.ofNullable(request.getRequestURI().toString()).orElse(null));log.info("请求方式:{}",request.getMethod());log.info("请求类方法:{}",joinPoint.getSignature());log.info("请求类方法参数:{}", JSONObject.toJSONString(filterArgs(joinPoint.getArgs())));long start = System.currentTimeMillis();Object result = joinPoint.proceed(joinPoint.getArgs());long end = System.currentTimeMillis();log.info("执行耗时:{}", end - start);return result;}private List<Object> filterArgs(Object[] objects) {return Arrays.stream(objects).filter(obj -> !(obj instanceof MultipartFile)&& !(obj instanceof HttpServletResponse)&& !(obj instanceof HttpServletRequest)).collect(Collectors.toList());}
}

这种方式通过对注解切面实现,通用性很强,适合做成项目级别,每个微服务引用基础包后自动启用。这里注意一个点public Object after方法必须要有返回值,不然请求的返回值是null。Object result = joinPoint.proceed(joinPoint.getArgs());这个就是调用请求result即为调用请求后的response。

以上就是我的三种实现在项目中出入参监控的实现方式,如有问题,希望大家评论区留言多多指教!

springboot出入参参数日志打印方案以及实现相关推荐

  1. aop springboot 传入参数_java相关:springboot配置aop切面日志打印过程解析

    java相关:springboot配置aop切面日志打印过程解析 发布于 2020-3-31| 复制链接 摘记: 这篇文章主要介绍了springboot配置aop切面日志打印过程解析,文中通过示例代码 ...

  2. Springboot mybatis 配置sql日志打印

    1.方式一 ######################################################## ###配置打印sql ########################## ...

  3. java项目统一打印入参出参等日志

    java项目统一打印入参出参等日志 1.背景 2.设计思路 3.核心代码 3.1 自定义注解 3.2 实现BeanFactoryPostProcessor接口 3.3 实现MethodIntercep ...

  4. SpringBoot日期时间全局出入参格式化-3:全局Timestamp出入参

    SpringBoot时间出入参格式化-3:全局Timestamp出入参 上一篇中使用的是全局使用字符串处理时间参数.本文提供第三种处理方式:使用全局时间戳方式处理入参时间,如入参:1657096088 ...

  5. springboot 自定义日期出入参

    springboot 版本2.0.4,针对全局的日期出入参做一个详细的配置介绍,局部日期转换可采用@JsonFormat注解实现,本文不再赘述. 1.自定义日期入参 对于表单参数或get请求url后面 ...

  6. springboot日志打印

    springboot日志打印 很多项目在生产上严禁使用System.out输出,性能太低,原因是System.out输出会导致线程等待(同步),而使用Logger输出线程不等待日志的输出(异步),而继 ...

  7. 运用aop做日志,实现请求方法的入参、返回结果日志统一打印,避免日志打印格式杂乱,同时减少重复代码

    文章目录 一.自定义注解 二.切面类 三.应用 一.自定义注解 自定义切面注解@PrintlnLog 用来输出日志,注解权限 @Target({ElementType.METHOD}) 限制只在方法上 ...

  8. SpringBoot+Mybatis+Swagger2环境搭建+logback-spring日志打印及入库

    本文简介 本文将基于Spring官方提供的快速启动项目模板集成Mybatis.Swagger2框架,并讲解mybatis generator一键生成代码插件.logback.一键生成文档以及多环境的配 ...

  9. springboot filter and interceptor实战之mdc日志打印

    1.1  mdc日志打印全局控制 1.1.1    logback配置 <property name="log.pattern" value="%d{yyyy-MM ...

最新文章

  1. FTP服务器端程序分类
  2. [BZOJ 2438] [中山市选2011]杀人游戏 Tarjan缩点
  3. allegro怎么设置孔的属性_两种在Allegro中增加过孔的方法
  4. 礼赞 Wordpress,蝉知可直接使用 Wordpress 模板
  5. Eclipse里不同的project,右键选择属性property facet里看到的list 内容是否相同
  6. 热榜!基于jsp+mysql的JSP在线水果销售商城系统设计实现【建议收藏】
  7. oir 用image j打开的插件_Windows 上使用 VSCode Remote 插件进行远程开发
  8. Spring中的InitializingBean接口
  9. git本地给远程仓库创建分支
  10. 内置函数(内嵌函数或内联函数)
  11. 无头浏览器介绍和对比
  12. Chromium OS autotest
  13. (CVPR-2021)具有深度通用线性嵌入的跨视角步态识别
  14. 卡巴斯基破解版 KISV8.0.0.432 Beta 江南混混汉化特别版
  15. Pr 2019版安装教程
  16. 大专适合学习php么_学习php有没有学历要求
  17. 米家app扫描不到石头机器人_12月米家剁手清单,第二款冬天必备!
  18. 八条佛曰 66句震撼人心的禅语
  19. Fabric链码常用API文档
  20. maven使用TestNG

热门文章

  1. 会声会影试用版到期了怎么办_会声会影2018试用版如何正确安装、卸载?
  2. 苹果几是双卡双待_苹果史上首款实体双卡双待小屏iPhone诞生,值得入手吗?
  3. Python基础(五)---python3中的内置函数
  4. 关于redis多个哨兵sentinel在阿里云的坑 sdown sentinel或者failover-abort-not-elected
  5. Android中的常见时区
  6. 数据分析模型:漏斗分析
  7. 计算机学院的加油口号,各学院校运会加油口号
  8. Angular: ‘ng’ is not recognized as an internal or external command, operable program or batch file
  9. EDM电子邮件营销策划常用创意
  10. markdown 转 html c,STATA中的Markdown转换命令markstat