环境:SpringBoot3.2.5
1. 简介
一个应用的性能是决定用户体验好坏的关键因素。提高性能的最有效方法之一是减少服务器与客户端之间传输的数据大小。这正是压缩技术发挥作用的地方。Spring Boot 提供了对各种压缩技术的内置支持,以优化数据传输。
在本篇文章中,我们将探讨Deflate压缩,包括它为什么重要、何时使用它以及如何在Spring Boot应用程序中实现它。通过本篇文章你将清楚地了解如何使用Deflate压缩来优化应用程序的性能。
1.1 什么是Deflate压缩?
Deflate是一种无损数据压缩算法,它结合了LZ77算法和霍夫曼编码来减小数据的大小。在Web应用程序中,Deflate被广泛用于在向客户端发送HTTP响应之前对其进行压缩。
当客户端(例如浏览器或API使用者)从服务器请求数据时,服务器可以使用Deflate对响应进行压缩,从而减小通过网络传输的数据大小。客户端在接收到数据后进行解压缩。
1.2 为什么使用Deflate压缩?
- 提高性能
网络上的数据传输速度更快。延迟降低,特别是对于使用慢速或带宽有限的连接的用户而言。 - 节省带宽
压缩减少了通过网络发送的数据量,这对于流量高或负载大的应用程序(例如,JSON或XML响应)非常重要。 - 提升用户体验
更快的响应时间带来更好的用户体验,特别是对于移动用户或从远程位置访问的应用程序的用户而言。
1.3 应用场景
- 响应结果很大
如果你的API返回大的JSON或XML响应,压缩数据可以显著减少响应时间。 - 静态内容
压缩HTML、CSS和JavaScript文件等静态资源可以改善页面加载时间
注意:如果对应响应结果比较小的(小于2kb)时候反而使用压缩技术会对性能造成影响。
2. 实战案例
2.1 Deflate过滤器
public class DeflateCompressionFilter implements Filter {
private static final int MIN_RESPONSE_SIZE = 2 * 1024 ;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
String acceptEncoding = req.getHeader("Accept-Encoding");
// 这里我们更加请求header中的Accept-Encoding进行判断,只有包含指定的值才进行处理
if (acceptEncoding == null || !acceptEncoding.toLowerCase().contains("deflate")) {
chain.doFilter(request, response);
return;
}
// 自定义Response包装类,我们需要对响应结果进行获取处理
DeflateResponseWrapper responseWrapper = new DeflateResponseWrapper(resp);
chain.doFilter(request, responseWrapper);
// 只有响应的数据大小超过这里指定的值(2KB)才进行压缩处理
if (responseWrapper.getContentLength() > MIN_RESPONSE_SIZE) {
// 必须设置,否则客户端将无法解析解压数据
resp.setHeader("Content-Encoding", "deflate");
try (DeflaterOutputStream dos= new DeflaterOutputStream(resp.getOutputStream())) {
dos.write(responseWrapper.getCapturedData());
}
} else {
// Write the uncompressed response
resp.getOutputStream().write(responseWrapper.getCapturedData());
}
}
}
关键的注释已经在源码中进行了处理。
注意:这里没有判断响应数据的类型可以根据Content-Type进行判断。
2.2 Response包装类
public class DeflateResponseWrapper extends HttpServletResponseWrapper {
private final ByteArrayOutputStream capture;
private ServletOutputStream outputStream;
private PrintWriter writer;
public DeflateResponseWrapper(HttpServletResponse response) {
super(response);
capture = new ByteArrayOutputStream() ;
}
public ServletOutputStream getOutputStream() {
if (writer != null) {
throw new IllegalStateException("Writer already in use");
}
if (outputStream == null) {
outputStream = new ServletOutputStream() {
public void write(int b) throws IOException {
capture.write(b);
}
public void flush() throws IOException {
capture.flush();
}
public void close() throws IOException {
capture.close();
}
public boolean isReady() {
return true;
}
public void setWriteListener(WriteListener writeListener) {
}
};
}
return outputStream;
}
public PrintWriter getWriter() {
if (outputStream != null) {
throw new IllegalStateException("OutputStream already in use");
}
if (writer == null) {
writer = new PrintWriter(capture);
}
return writer;
}
public byte[] getCapturedData() {
return capture.toByteArray();
}
public int getContentLength() {
return capture.size();
}
}
我们需要将数据先写入到内存输出流中,这样我们才能得到当前写入到响应流中的数据。
2.3 注册过滤器
在Spring Boot中我们可以通过如下方式注册过滤器,也可以通过@WedFilter的方式注册。
@Bean
FilterRegistrationBean<DeflateCompressionFilter> deflateCompressionFilter() {
FilterRegistrationBean<DeflateCompressionFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new DeflateCompressionFilter());
// 对所有的请求都进行过去了处理
registrationBean.addUrlPatterns("/*") ;
registrationBean.setName("DeflateCompressionFilter") ;
registrationBean.setOrder(1) ;
return registrationBean ;
}
以上我们就完成了所有的代码,接下来我们进行测试。
2.4 测试
接下来,我们通过如下接口进行测试:
@GetMapping("/data")
public List<User> getData() {
List<User> data = new ArrayList<>() ;
for (long i = 0; i < 10000; i++) {
data.add(new User(i, "姓名 - " + i, new Random().nextInt(100))) ;
}
return data;
}
public static record User (Long id, String name, Integer age) {}
首先,我们将请求的Accept-Encoding随意写一个值,响应结果
图片
最后,我们在将Accept-Encoding设置为deflate,响应结果:
图片
与压缩前相比:压缩了近6.7倍。
显著提升应用程序的性能,减少带宽使用,并增强用户体验。
注意:你完全可以使用GZIP进行压缩,并且使用GZIP也是当前最为推荐流行的方式,并且兼容性要比deflate好。
如果启用GZIP?如下配置即可:
server:
compression:
enabled: true
min-response-size: 1024
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml
而本篇文章的目标是让你了解这压缩技术的实现原理。而在上面自定义的过滤器中,我们也完全可以使用GZIP对应的输出流进行压缩数据,如下代码:
if (responseWrapper.getContentLength() > MIN_RESPONSE_SIZE) {
// 设置响应的内容编码类型为gzip
resp.setHeader("Content-Encoding", "gzip");
// 使用gzip进行压缩数据
try (GZIPOutputStream gos = new GZIPOutputStream(resp.getOutputStream())) {
gos.write(responseWrapper.getCapturedData()) ;
}
} else {
resp.getOutputStream().write(responseWrapper.getCapturedData());
}