• Spring Boot 3.x Web MVC实战:实现流缓存的request


    上一节《Spring Boot 3.x Filter实战:记录请求日志》实践最后遇到了request对象的流不可重复读的问题,本小节我们将通过流数据缓存以及流的装饰器模式来解决这个问题。如果觉得对你有帮助,记得点赞收藏,关注小卷,后续更精彩!

    在这里插入图片描述

    装饰流操作对象

    我们知道,在Java语言的流模块设计中大量采用了装饰器模式,来扩展流的特性。这里我们同样会对接收字节数组流数据的ByteArrayInputStream对象进行包装,将其作为一个从ServletInputStream扩展的CachedServletInputStream类型对象的成员变量,进行缓存。看下代码实现:

    package com.juan.demo.common.web.support.servlet;
    
    import ...
    
    @Slf4j
    public class CachedServletInputStream extends ServletInputStream {
    
        private final InputStream cachedInputStream;
    
        public CachedServletInputStream(byte[] cachedByteArray) {
            this.cachedInputStream = new ByteArrayInputStream(cachedByteArray);
        }
    
        @Override
        public boolean isFinished() {
            try {
                return cachedInputStream.available() == 0;
            } catch (IOException e) {
                log.error(e.getMessage());
            }
            return false;
        }
    
        @Override
        public boolean isReady() {
            return true;
        }
    
        @Override
        public void setReadListener(ReadListener listener) {
            throw new UnsupportedOperationException();
        }
    
        @Override
        public int read() throws IOException {
            return cachedInputStream.read();
        }
    }
    

    该流操作对象扩展自ServletInputStream,可以提供给外部作为请求的流对象使用,在调用ServletInputStream相关流操作方法时,实际调用的是该包装类重写的相关方法。这里主要关注isFinished()read()方法,它们的实现其实就是调用被缓存和包装的流对象的相关方法。

    缓存流数据的请求包装器

    对于http servlet请求,Java EE提供了HttpServletRequestWrapper包装器类对原始的请求对象进行包装,这对应用层的开发人员来说是透明的,咱们仅关注面向HttpServletRequest接口的编程,实际运行时的调用会对包装器对象调用内部被包装对象来完成相应的功能。

    这里我们仅仅要做的就是扩展HttpServletRequestWrapper,并缓存最原始的字节输出流数据。而在调用目标请求对象相关方法(这里是getInputStream()getReader()方法)时,进行重写,实现每次都返回一个携带原始流数据的ServletInputStreamBufferedReader对象即可。

    而要返回的ServletInputStream类型,前面我们已经自定义了一个接收原始字节数据完成构造的CachedServletInputStream类型。

    看完成的代码实现:

    package com.juan.demo.common.web.support.servlet;
    
    import ...
    
    public class CachedHttpServletRequestWrapper extends HttpServletRequestWrapper {
    
        private byte[] cachedByteArray;
    
        public CachedHttpServletRequestWrapper(HttpServletRequest request) {
            super(request);
        }
    
        @Override
        public ServletInputStream getInputStream() throws IOException {
            copyInputStreamIfNecessary();
            return new CachedServletInputStream(this.cachedByteArray);
        }
    
        @Override
        public BufferedReader getReader() throws IOException {
            copyInputStreamIfNecessary();
            return new BufferedReader(new InputStreamReader(new ByteArrayInputStream(this.cachedByteArray)));
        }
    
        private void copyInputStreamIfNecessary() throws IOException {
            if (this.cachedByteArray == null) {
                this.cachedByteArray = StreamUtils.copyToByteArray(getRequest().getInputStream());
            }
        }
    }
    
    

    延迟拷贝流数据

    注意我们这里的设计,并不是在构造CachedHttpServletRequestWrapper时就进行流数据的拷贝,而是推迟到需要使用时才调用copyInputStreamIfNecessary()去做拷贝。这种方式更加的安全,也考虑到一般一个请求的处理在一个请求线程上,不会有线程安全问题。

    RequestLogFilter应用

    最后,我们在RequestLogFilterdoFilterInternal方法中应用前面对请求包装器的实现:

    @Override
    protected void doFilterInternal(...) throws ... {
        ...
    
        CachedHttpServletRequestWrapper requestWrapper = new CachedHttpServletRequestWrapper(request);
    
        logParams(requestWrapper);
    
        logRequestBody(requestWrapper);
    
        filterChain.doFilter(requestWrapper, response);
    }
    

    注意,这里记录日志以及放行请求的后续处理操作的都是包装过的requestWrapper对象。

    最后,测试一下之前的添加购物车API,ok!

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    【软考 系统架构设计师】案例分析⑨ 数据库优化
    对辊柱塞式成型机总体设计
    供应链寒冬,看qlik如何为空客Airbus数智破局
    变脸软件成犯罪帮凶​,上万个限用微信号被“复活”
    数据指标体系如何搭建才最有效,从 0 到 1 带你快速入门丨 02 期直播回顾
    KMP算法详解,3000字详解,带你学会next数组
    如何在Windows下使用gitblit搭建一个自己的git服务器
    2024年最新版FL Studio21.2.3 Build 4004 for Mac 版激活下载和图文激活教程
    Mac实现Gitlab CICD
    17-ROS tf包介绍
  • 原文地址:https://blog.csdn.net/felix_alone2012/article/details/141068048