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

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

文章目录

装饰流操作对象

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

java 复制代码
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类型。

看完成的代码实现:

java 复制代码
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方法中应用前面对请求包装器的实现:

java 复制代码
@Override
protected void doFilterInternal(...) throws ... {
    ...

    CachedHttpServletRequestWrapper requestWrapper = new CachedHttpServletRequestWrapper(request);

    logParams(requestWrapper);

    logRequestBody(requestWrapper);

    filterChain.doFilter(requestWrapper, response);
}

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

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

相关推荐
世间万物皆对象3 小时前
Spring Boot核心概念:日志管理
java·spring boot·单元测试
qq_17448285754 小时前
springboot基于微信小程序的旧衣回收系统的设计与实现
spring boot·后端·微信小程序
代码小鑫6 小时前
A043-基于Spring Boot的秒杀系统设计与实现
java·开发语言·数据库·spring boot·后端·spring·毕业设计
真心喜欢你吖6 小时前
SpringBoot与MongoDB深度整合及应用案例
java·spring boot·后端·mongodb·spring
周全全7 小时前
Spring Boot + Vue 基于 RSA 的用户身份认证加密机制实现
java·vue.js·spring boot·安全·php
飞升不如收破烂~8 小时前
Spring boot常用注解和作用
java·spring boot·后端
计算机毕设源码qq-38365310418 小时前
(附项目源码)Java开发语言,215 springboot 大学生爱心互助代购网站,计算机毕设程序开发+文案(LW+PPT)
java·开发语言·spring boot·mysql·课程设计
岁岁岁平安8 小时前
springboot实战(15)(注解@JsonFormat(pattern=“?“)、@JsonIgnore)
java·spring boot·后端·idea
潜洋12 小时前
Spring Boot教程之五:在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序
java·spring boot·后端
灯雾️13 小时前
Spring Boot、Spring MVC和Spring间的区别
spring boot