zoukankan      html  css  js  c++  java
  • 【实践】切面打印请求参数

    加打印语句,将请求参数打印出来。后面想想,以后可能还会遇到这样的情况,如果每次遇到,我都去对应的方法中加日志打印,就变成重复工作。并且日志打印跟我们的业务本身没有任何关系。

    记录日志网上主要有三种方法:

    1. aop
    2. filter
    3. interceptor

    我选择了filter。为什么选择它,因为我觉得它相对于定义切点,然后切点前后处理来说,更加方便;相对于 interceptor, 我更加熟悉这种方式。

    定义Filter

    定义一个 LogFilter  。 里面对 HttpServletRequest  进行拦截,根据对应的 content-type 解析请求参数。主要代码如下

    /**
     * 功能描述: 打印请求参数
     * @author lkb
     * @date 2020/5/6
     * @param
     * @return
     */
    @Slf4j
    public class LogFilter extends OncePerRequestFilter {
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
    
            //日志
            doLog(request, response);
    
            // 将request 传到下一个Filter
            filterChain.doFilter(request, response);
        }
    
    
        private void doLog(HttpServletRequest request,  HttpServletResponse response){
            // 输出请求体
            log.info("request. uri = {}, method = {}, requestParam = {}", request.getRequestURI(), request.getMethod(), getRequestParam(request));
            //todo 返回结果也可以进行处理
        }
    
    
        private String getRequestParam(HttpServletRequest request){
            String requestParam = "";
    
            String requestContentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
            try {
                if(StringUtils.isNotEmpty(requestContentType)){
                    if (requestContentType.startsWith(MediaType.APPLICATION_JSON_VALUE)
                            || requestContentType.startsWith(MediaType.APPLICATION_XML_VALUE)) {
                        // xml json
                        requestParam = getRequestBody(request);
                    }
                    else if (requestContentType.startsWith(MediaType.MULTIPART_FORM_DATA_VALUE)) {
                        // 文件表单提交
                        requestParam = getFormParam(request);
                    }else if(requestContentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED_VALUE)){
                        // 普通表单提交
                        requestParam = toJson(request.getParameterMap());
                    }
                }else{
                    // 默认普通表单提交
                    requestParam = toJson(request.getParameterMap());
                }
            }catch (Exception e){
                log.error("getRequestParam error");
                log.error(e.getMessage(),e);
            }
            return requestParam;
        }
        
        ...
    }

    然后,注册这个filter

    @Configuration
    public class FilterConfig {
    
        @Bean
        public FilterRegistrationBean logFilter() {
            final FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
            final LogFilter logFilter = new LogFilter();
            filterRegistrationBean.setFilter(logFilter);
            return filterRegistrationBean;
        }
    
    }

    上面两步之后,重启项目,可以看到请求过来后会打印出请求的uri、method、param 。

    InputStream 只能读取一次

    本来以为这样就万事大吉了。但是事实并不是如此。加上上面代码后会发现,再controller 加上的 @requestBody 没有效果,取不到任何数据,并抛出异常,告诉我们请求已经被读取过。 为什么呢?

    原因很简单。因为在 doLog 中获取请求参数的时候,我们已经将请求的 inputStream 给读取了。读取inputStream 时有一个offset,它表示你从哪里开始读取输入流。因为我们读取了一遍 inputStream,所以offset已经在流的最末端了。我们再去读取,就会发现没有东西可以读了。如果想重复读取 inputStream 就需要每次读取后重置 offset 的值。

    当然为了方便,我并没有去重新inputStream 中的reset 方法。而是选择,在读取请求后,将请求缓存起来。

    首先,BufferedServletInputStream 继承自 ServletInputStream。

    public class BufferedServletInputStream extends ServletInputStream {
    
        private ByteArrayInputStream inputStream;
    
        public BufferedServletInputStream(byte[] buffer) {
            this.inputStream = new ByteArrayInputStream( buffer );
        }
    
        @Override
        public int available(){
            return inputStream.available();
        }
    
        @Override
        public int read(){
            return inputStream.read();
        }
    
        @Override
        public int readLine(byte[] b, int off, int len){
            return inputStream.read( b, off, len );
        }
    
        @Override
        public boolean isFinished() {
            return false;
        }
    
        @Override
        public boolean isReady() {
            return false;
        }
    
        @Override
        public void setReadListener(ReadListener readListener) {
    
        }
    }

    然后,BufferedServletRequestWrapper 继承 HttpServletRequestWrapper。

    @Slf4j
    public class BufferedServletRequestWrapper extends HttpServletRequestWrapper {
    
        private byte[] buffer;
    
        public BufferedServletRequestWrapper(HttpServletRequest request) throws IOException {
            super(request);
            InputStream is = request.getInputStream();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            byte buff[] = new byte[1024];
            int read;
            while ((read = is.read(buff)) > 0) {
                baos.write(buff, 0, read);
            }
            this.buffer = baos.toByteArray();
        }
    
        @Override
        public ServletInputStream getInputStream() {
            return new BufferedServletInputStream(this.buffer);
        }
    }

    里面使用一个 byte[] buffer 数组将请求缓存起来。

    最后,在 LogFilter 中 doLog 前,对请求进行包装。

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {
        // 备份HttpServletRequest
        request = new BufferedServletRequestWrapper(request);
    
        //日志
        doLog(request, response);
    
        // 将request 传到下一个Filter
        filterChain.doFilter(request, response);
    }

    经过上诉处理,我们就可以愉快地用日志记录请求参数了。

    总结

    最后总结一下:

    1. 记录请求参数日志的方式最好采用切面的思想

    2. inputStream 默认只能读取一次,多次读取要重新处理inputStream

    3. @requestBody 的原理可以了解一下

  • 相关阅读:
    Locust 场景执行:Web UI 中执行
    第1章 计算机网络和因特网
    目录
    计算机网络--自定向下的方法
    mysql索引总结
    7_异常处理
    6_面向对象-下之类的结构:内部类
    6_面向对象-下之关键字:interface
    6_面向对象-下之关键字:abstract
    6_面向对象-下之关键字:final
  • 原文地址:https://www.cnblogs.com/catlkb/p/12845607.html
Copyright © 2011-2022 走看看