美文网首页
SpringBoot定制记录请求的返回值

SpringBoot定制记录请求的返回值

作者: 西5d | 来源:发表于2020-03-26 18:20 被阅读0次

    背景和要求

    项目中要求记录部分业务请求的返回值,需要全部记录,且可以通过具体的请求字段,能够查询到,用来做问题查询和处理。项目本身接入了一套ELK体系,能够查询相关的请求日志,但是没有记录返回值,且这个实现是全局的,如果直接扩展,记录返回,可能导致日志内容超大而无法处理,况且目前没有记录返回值的日志本身已经足够大量了。所以,要求能够定制地在某些接口请求中记录返回值,项目是springboot,首先想到的是使用注解,在对应接口上添加注解,再设置拦截器,从而方便获取对应的返回值。

    实现

    原理介绍

    添加新的注解,注解在需要记录返回值的接口方法上,然后通过切面,获取相应的返回值,处理数据后,写入现有的ELK日志中,最终可以通过kibana来查询。其中有些点值得详细说下。

    首先,这种日志统计,可以单独放到独立的线程池中去处理,避免增加业务请求的耗时,如下的代码中有个单独的线程池;其次请求中除了统一的模板参数,对于自己定义的参数,也应该记录,如@RequestParam标记的参数,这样一个日志条目就包括了完整的请求参数,完整的返回值,得到了所有需要的东西。目前的项目中,header使用不多,所以暂时不需要记录。

    代码详解

    注解类,建议只在方法上添加注解:

    import java.lang.annotation.*;
    
    @Inherited
    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RecordResp {
        
    }
    

    切面类

    @Slf4j
    @Aspect
    @Component
    public class RecordRespInterceptor {
    
        @Resource(name = "respRecorderExecutor")
        private ExecutorService respRecorderExecutor;
    
        @Resource
        private RespRecorder respRecorder;
    
        @Pointcut("@annotation(com.xxxx.xxxx.aop.RecordResp)")
        private void pointcut() {
        }
    
        @Around(value = "pointcut()")
        public Object after(ProceedingJoinPoint joinPoint) throws Throwable {
            Object object = joinPoint.proceed();
            HttpServletRequest request = getRequest();
            HttpServletResponse response = getResponse();
            respRecorderExecutor.submit(() -> respRecord(object, joinPoint, request, response));
            //object是方法返回值
            return object;
        }
    
        private void respRecord(Object object, ProceedingJoinPoint joinPoint, HttpServletRequest request, HttpServletResponse response) {
            try {
                Parameter[] parameters = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameters();
                Object[] args = joinPoint.getArgs();
                Map<String, Object> paramMap = new HashMap<>();
                for (int i = 0; i < parameters.length; i++) {
                    if (args[i] instanceof HttpServletRequest) {
                        continue;
                    }
                    if (args[i] instanceof HttpServletResponse) {
                        continue;
                    }
                    //如果还有其他类型参数,也要略过,否则是很大的对象内容,这里只关心需要的。
                    paramMap.put(parameters[i].getName(), args[i]);//(1)
                }
                respRecorder.write(xxx, paramMap, object, request, response); //xxx为通用模板参数对象
            } catch (Exception e) {
                log.error("recorder resp error.", e);
            }
        }
    
        private HttpServletRequest getRequest() {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return attributes.getRequest();
        }
    
        private HttpServletResponse getResponse() {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return attributes.getResponse();
        }
    }
    
    

    其中 RespRecorder 是已有实现的ELK日志写入类,不方便放代码,请见谅。 (1)标注的一行paramMap.put(parameters[i].getName(), args[i]);,需要在项目中添加编译参数-parameters,否则获取的参数名称为arg0,arg1类似值,无法实际区分,在jdk1.8才支持,默认是关闭的。maven项目可以在pom配置里添加如下内容:

                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                        <fork>true</fork>
                        <meminitial>1024m</meminitial>
                        <maxmem>1024m</maxmem>
                        <verbose>true</verbose>
                        <encoding>UTF-8</encoding>
                        <compilerArgs>
                            <compilerArg>-parameters</compilerArg>
                        </compilerArgs>
                    </configuration>
                </plugin>
    

    总结

    以上就是全部内容,很明显通过注解来实现记录指定方法的返回值是比较简便和高效的,同时也添加了异步处理和补充的参数日志。后续计划再补充一篇内容,介绍ELK部署和接入使用,最好能尝试完成全docker部署。

    感谢阅读!

    相关文章

      网友评论

          本文标题:SpringBoot定制记录请求的返回值

          本文链接:https://www.haomeiwen.com/subject/ycthuhtx.html