关注公众号,学习更多 Java 干货!

1. 业务背景

  • adName:广告名字
  • adTag:广告渲染的 HTML 代码,超级大数据库中都是用 text 类型来存放的,我看到最大的 adTag 足足有 60kb 大小…

2. 实现思路

前置知识:
  • Http 请求结构以及 Content-Encoding 属性
  • GZIP 压缩方式
  • Servlet Filter
  • HttpServletRequestWrapper
  • Spring Boot
  • Java 输入输出流
另外,如果你近期准备面试跳槽,建议在Java面试库小程序在线刷题,涵盖 2000+ 道 Java 面试题,几乎覆盖了所有主流技术面试题。
实现流程图
推荐一个开源免费的 Spring Boot 最全教程:
https://github.com/javastacks/spring-boot-best-practice
核心代码:
创建一个 SpringBoot 项目,先编写一个接口,功能很简单就是传入一个 JSON 对象并返回,以模拟将广告数据保存到数据库:
编写并注册一个拦截器
/**

 * @ClassName: GZIPFilter

 * @Author zhangjin

 * @Date 2022/3/26 0:36

 * @Description:

 */

@Slf4j

@Component

public class GZIPFilter implements Filter {


    private static final String CONTENT_ENCODING = 
"Content-Encoding"
;

    private static final String CONTENT_ENCODING_TYPE = 
"gzip"
;


    @Override

    public void init(FilterConfig filterConfig) throws ServletException {

        log.info(
"init GZIPFilter"
);

    }


    @Override

    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        long start = System.currentTimeMillis();

        HttpServletRequest httpServletRequest = (HttpServletRequest)servletRequest;


        String encodeType = httpServletRequest.getHeader(CONTENT_ENCODING);

if
 (CONTENT_ENCODING_TYPE.equals(encodeType)) {

            log.info(
"请求:{} 需要解压"
, httpServletRequest.getRequestURI());

            UnZIPRequestWrapper unZIPRequestWrapper = new UnZIPRequestWrapper(httpServletRequest);

            filterChain.doFilter(unZIPRequestWrapper,servletResponse);

        }

else
 {

            log.info(
"请求:{} 无需解压"
, httpServletRequest.getRequestURI());

            filterChain.doFilter(servletRequest,servletResponse);

        }

        log.info(
"耗时:{}ms"
, System.currentTimeMillis() - start);

    }


    @Override

    public void 
destroy
() {

        log.info(
"destroy GZIPFilter"
);

    }

}


/**

 * @ClassName: FilterRegistration

 * @Author zhangjin

 * @Date 2022/3/26 0:36

 * @Description:

 */

@Configuration

public class FilterRegistration {


    @Resource

    private GZIPFilter gzipFilter;


    @Bean

    public FilterRegistrationBean<GZIPFilter> 
gzipFilterRegistrationBean
() {

        FilterRegistrationBean<GZIPFilter> registration = new FilterRegistrationBean<>();

        //Filter可以new,也可以使用依赖注入Bean

        registration.setFilter(gzipFilter);

        //过滤器名称

        registration.setName(
"gzipFilter"
);

        //拦截路径

        registration.addUrlPatterns(
"/*"
);

        //设置顺序

        registration.setOrder(1);

return
 registration;

    }

}

实现 RequestWrapper 实现解压和写回 Body 的逻辑
/**

 * @ClassName: UnZIPRequestWrapper

 * @Author zhangjin

 * @Date 2022/3/26 11:02

 * @Description: JsonString经过压缩后保存为二进制文件 -> 解压缩后还原成JsonString转换成byte[] 写回body中

 */

@Slf4j

public class UnZIPRequestWrapper extends HttpServletRequestWrapper {


    private final byte[] bytes;


    public UnZIPRequestWrapper(HttpServletRequest request) throws IOException {

        super(request);

        try (BufferedInputStream bis = new BufferedInputStream(request.getInputStream());

             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {

            final byte[] body;

            byte[] buffer = new byte[1024];

            int len;

while
 ((len = bis.read(buffer)) > 0) {

                baos.write(buffer, 0, len);

            }

            body = baos.toByteArray();

if
 (body.length == 0) {

                log.info(
"Body无内容,无需解压"
);

                bytes = body;

return
;

            }

            this.bytes = GZIPUtils.uncompressToByteArray(body);

        } catch (IOException ex) {

            log.info(
"解压缩步骤发生异常!"
);

            ex.printStackTrace();

            throw ex;

        }

    }


    @Override

    public ServletInputStream getInputStream() throws IOException {

        final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);

return
 new 
ServletInputStream
() {


            @Override

            public boolean 
isFinished
() {

returnfalse
;

            }


            @Override

            public boolean 
isReady
() {

returnfalse
;

            }


            @Override

            public void setReadListener(ReadListener readListener) {


            }


            public int 
read
() throws IOException {

return
 byteArrayInputStream.read();

            }

        };

    }


    @Override

    public BufferedReader getReader() throws IOException {

return
 new BufferedReader(new InputStreamReader(this.getInputStream()));

    }


}

附上压缩工具类
public class GZIPUtils {


    public static final String GZIP_ENCODE_UTF_8 = 
"UTF-8"
;


    /**

     * 字符串压缩为GZIP字节数组

     * @param str

     * @
return
     */

    public static byte[] compress(String str) {

return
 compress(str, GZIP_ENCODE_UTF_8);

    }


    /**

     * 字符串压缩为GZIP字节数组

     * @param str

     * @param encoding

     * @
return
     */

    public static byte[] compress(String str, String encoding) {

if
 (str == null || str.length() == 0) {

return
 null;

        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        GZIPOutputStream gzip;

        try {

            gzip = new GZIPOutputStream(out);

            gzip.write(str.getBytes(encoding));

            gzip.close();

        } catch (IOException e) {

            e.printStackTrace();

        }

return
 out.toByteArray();

    }


    /**

     * GZIP解压缩

     * @param bytes

     * @
return
     */

    public static byte[] uncompress(byte[] bytes) {

if
 (bytes == null || bytes.length == 0) {

return
 null;

        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        ByteArrayInputStream 
in
 = new ByteArrayInputStream(bytes);

        try {

            GZIPInputStream ungzip = new GZIPInputStream(
in
);

            byte[] buffer = new byte[256];

            int n;

while
 ((n = ungzip.read(buffer)) >= 0) {

                out.write(buffer, 0, n);

            }

        } catch (IOException e) {

            e.printStackTrace();

        }

return
 out.toByteArray();

    }


    /**

     * 解压并返回String

     * @param bytes

     * @
return
     */

    public static String uncompressToString(byte[] bytes) throws IOException {

return
 uncompressToString(bytes, GZIP_ENCODE_UTF_8);

    }


    /**

     *

     * @param bytes

     * @
return
     */

    public static byte[] uncompressToByteArray(byte[] bytes) throws IOException {

return
 uncompressToByteArray(bytes, GZIP_ENCODE_UTF_8);

    }


    /**

     * 解压成字符串

     * @param bytes 压缩后的字节数组

     * @param encoding 编码方式

     * @
return
 解压后的字符串

     */

    public static String uncompressToString(byte[] bytes, String encoding) throws IOException {

        byte[] result = uncompressToByteArray(bytes, encoding);

return
 new String(result);

    }


    /**

     * 解压成字节数组

     * @param bytes

     * @param encoding

     * @
return
     */

    public static byte[] uncompressToByteArray(byte[] bytes, String encoding) throws IOException {

if
 (bytes == null || bytes.length == 0) {

return
 null;

        }

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        ByteArrayInputStream 
in
 = new ByteArrayInputStream(bytes);

        try {

            GZIPInputStream ungzip = new GZIPInputStream(
in
);

            byte[] buffer = new byte[256];

            int n;

while
 ((n = ungzip.read(buffer)) >= 0) {

                out.write(buffer, 0, n);

            }

return
 out.toByteArray();

        } catch (IOException e) {

            e.printStackTrace();

            throw new IOException(
"解压缩失败!"
);

        }

    }


    /**

     * 将字节流转换成文件

     * @param filename

     * @param data

     * @throws Exception

     */

    public static void saveFile(String filename,byte [] data)throws Exception{

if
(data != null){

            String filepath =
"/"
 + filename;

            File file  = new File(filepath);

if
(file.exists()){

                file.delete();

            }

            FileOutputStream fos = new FileOutputStream(file);

            fos.write(data,0,data.length);

            fos.flush();

            fos.close();

            System.out.println(file);

        }

    }

}

Spring Boot 基础就不介绍了,推荐看这个免费教程:
https://github.com/javastacks/spring-boot-best-practice

3. 测试效果

注意一个大坑:千万不要直接将压缩后的 byte[] 当作字符串进行传输,否则你会发现压缩后的请求数据竟然比没压缩后的要大得多 🐶!一般有两种传输压缩后的 byte[]的方式:
  • 将压缩后的 byet[] 进行 base64 编码再传输字符串,这种方式会损失掉一部分 GZIP 的压缩效果,适用于压缩结果要存储在 Redis 中的情况
  • 将压缩后的 byte[] 以二进制的形式写入到文件中,请求时直接在 body 中带上文件即可,用这种方式可以不损失压缩效果
Postman 测试 GZIP 压缩数据请求:
  • 请求头指定数据压缩方式:
  • Body 带上压缩后的 byte[] 写入的二进制文件
  • 执行请求,服务端正确处理了请求并且请求 size 缩小了将近一半,效果还是很不错的。
注:针对json 数据大小优化,也可以通过修改 nginx 配置项来解决,开启 contentType: gzip 内容传输编码支持,并对Tomcat进行配置即可。
版权声明:本文为CSDN博主「jinchange」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/weixin_43441509/article/details/123816603
继续阅读
阅读原文