Springboot?中的?Filter?實(shí)現(xiàn)超大響應(yīng)?JSON?數(shù)據(jù)壓縮的方法
簡(jiǎn)介
項(xiàng)目中,請(qǐng)求時(shí)發(fā)送超大 json 數(shù)據(jù)外;響應(yīng)時(shí)也有可能返回超大 json數(shù)據(jù)。上一篇實(shí)現(xiàn)了請(qǐng)求數(shù)據(jù)的 gzip 壓縮。本篇通過(guò) filter 實(shí)現(xiàn)對(duì)響應(yīng) json 數(shù)據(jù)的壓縮。
先了解一下以下兩個(gè)概念:
- 請(qǐng)求頭:
Accept-Encoding : gzip告訴服務(wù)器,該瀏覽器支持 gzip 壓縮 - 響應(yīng)頭:
Content-Encoding : gzip告訴瀏覽器,輸出信息使用了 gzip 進(jìn)行壓縮

pom.xml 引入依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.olive</groupId> <artifactId>response-compression</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>response-compression</name> <url>http://maven.apache.org</url> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.14</version> <relativePath /> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba.fastjson2</groupId> <artifactId>fastjson2</artifactId> <version>2.0.14</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.9.0</version> </dependency> </dependencies> </project>
對(duì)Response進(jìn)行包裝
GzipResponseWrapper 類(lèi)重新定義了輸出流,攔截需要輸出的數(shù)據(jù),直接緩存到 ByteArrayOutputStream 中。
package com.olive.filter;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
@Slf4j
public class GzipResponseWrapper extends HttpServletResponseWrapper {
/**
* 字節(jié)數(shù)組緩沖流,用來(lái)保存截獲到的輸出數(shù)據(jù)
*/
private ByteArrayOutputStream buffer;
/**
* 重新定義servlet輸出流,改變輸出目的地將響應(yīng)內(nèi)容輸出到給定的字節(jié)數(shù)組緩沖流中
*/
private GzipResponseWrapper.CustomServletOutputStream servletOutputStream;
/**
* 同上
*/
private PrintWriter writer;
public GzipResponseWrapper(HttpServletResponse response) {
super(response);
//original HttpServletResponse object
buffer = new ByteArrayOutputStream();
servletOutputStream = new GzipResponseWrapper.CustomServletOutputStream(buffer);
try {
writer = new PrintWriter(new OutputStreamWriter(buffer, response.getCharacterEncoding()), true);
} catch (UnsupportedEncodingException e) {
log.error("GZipHttpServletResponse", e);
}
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
return servletOutputStream;
}
@Override
public PrintWriter getWriter() throws IOException {
return writer;
}
@Override
public void flushBuffer() throws IOException {
if (servletOutputStream != null) {
servletOutputStream.flush();
}
if (writer != null) {
writer.flush();
}
}
/**
* 向外部提供一個(gè)獲取截獲數(shù)據(jù)的方法
* @return 從response輸出流中截獲的響應(yīng)數(shù)據(jù)
*/
public byte[] getOutputData() throws IOException {
flushBuffer();
return buffer.toByteArray();
}
private static class CustomServletOutputStream extends ServletOutputStream {
/**
* 字節(jié)數(shù)組緩沖流,用來(lái)保存截獲到的輸出數(shù)據(jù)
*/
private ByteArrayOutputStream buffer;
public CustomServletOutputStream(ByteArrayOutputStream buffer) {
this.buffer = buffer;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setWriteListener(WriteListener listener) {
}
/**
* 重寫(xiě)輸出流相關(guān)的方法
* 將輸出數(shù)據(jù)寫(xiě)出到給定的ByteArrayOutputStream緩沖流中保存起來(lái)
* @param b 輸出的數(shù)據(jù)
* @throws IOException
*/
@Override
public void write(int b) throws IOException {
buffer.write(b);
}
}
}定義GzipFilter對(duì)輸出進(jìn)行攔截
GzipFilter 攔截器獲取緩存的需要輸出的數(shù)據(jù),進(jìn)行壓縮,在輸出數(shù)據(jù)之前先設(shè)置響應(yīng)頭Content-Encoding : gzip。
package com.olive.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.zip.GZIPOutputStream;
/**
* 壓縮過(guò)濾器
*
* 功能:對(duì)于返回給客戶端的數(shù)據(jù)進(jìn)行g(shù)zip壓縮,提高響應(yīng)速度
* 實(shí)現(xiàn)說(shuō)明:
* 要對(duì)response對(duì)象的輸出數(shù)據(jù)進(jìn)行g(shù)zip壓縮,首先得拿到后面servlet(controller)進(jìn)行業(yè)務(wù)處理后往response對(duì)象里寫(xiě)入的數(shù)據(jù)
* 可以通過(guò)重寫(xiě)response對(duì)象,修改該對(duì)象內(nèi)部的輸出流,使該流寫(xiě)出數(shù)據(jù)時(shí)寫(xiě)出到給定的字節(jié)數(shù)組緩沖流當(dāng)中,
* 并在重寫(xiě)后的response對(duì)象內(nèi)部提供一個(gè)獲取該字節(jié)數(shù)組緩沖流的方法,這樣就可以截獲響應(yīng)數(shù)據(jù)
* 然后就可以對(duì)截獲的響應(yīng)數(shù)據(jù)通過(guò)Gzip輸出流進(jìn)行壓縮輸出即可;
* 因?yàn)轫憫?yīng)數(shù)據(jù)是gzip壓縮格式,不是普通的文本格式所以需要通過(guò)response對(duì)象(響應(yīng)頭)告知瀏覽器響應(yīng)的數(shù)據(jù)類(lèi)型
*/
@Slf4j
public class GzipFilter implements Filter {
private final String GZIP = "gzip";
public void destroy() {
log.info("GzipFilter destroy");
}
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
log.info("GzipFilter start");
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String acceptEncoding = request.getHeader(HttpHeaders.ACCEPT_ENCODING);
//searching for 'gzip' in ACCEPT_ENCODING header
if( acceptEncoding != null && acceptEncoding.indexOf(GZIP) >= 0){
GzipResponseWrapper gzipResponseWrapper = new GzipResponseWrapper(response);
//pass the customized response object to controller to capture the output data
chain.doFilter(request, gzipResponseWrapper);
//get captured data
byte[] data = gzipResponseWrapper.getOutputData();
log.info("截獲到數(shù)據(jù):" + data.length + " bytes");
//get gzip data
ByteArrayOutputStream gzipBuffer = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = new GZIPOutputStream(gzipBuffer);
gzipOut.write(data);
gzipOut.flush();
gzipOut.close();
byte[] gzipData = gzipBuffer.toByteArray();
log.info("壓縮后數(shù)據(jù):" + gzipData.length + " bytes");
//set response header and output
response.setHeader(HttpHeaders.CONTENT_ENCODING, GZIP);
response.getOutputStream().write(gzipData);
response.getOutputStream().flush();
}else{
chain.doFilter(req, resp);
}
}
public void init(FilterConfig config) throws ServletException {
log.info("GzipFilter init");
}
}注冊(cè) GzipFilter 攔截器
package com.olive.config;
import com.olive.filter.GzipFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 注冊(cè)filter
*/
@Configuration
public class FilterRegistration {
@Bean
public FilterRegistrationBean<GzipFilter> gzipFilterRegistrationBean() {
FilterRegistrationBean<GzipFilter> registration = new FilterRegistrationBean<>();
//Filter可以new,也可以使用依賴注入Bean
registration.setFilter(new GzipFilter());
//過(guò)濾器名稱(chēng)
registration.setName("gzipFilter");
//攔截路徑
registration.addUrlPatterns("/*");
//設(shè)置順序
registration.setOrder(1);
return registration;
}
}定義 Controller
該 Controller 非常簡(jiǎn)單,主要讀取一個(gè)大文本文件,作為輸出的內(nèi)容。
package com.olive.controller;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import com.olive.vo.ArticleRequestVO;
import org.apache.commons.io.FileUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@RequestMapping("/getArticle")
public Map<String, Object> getArticle(){
Map<String, Object> result = new HashMap<>();
result.put("code", 200);
result.put("msg", "success");
byte[] bytes = null;
try {
bytes = FileUtils.readFileToByteArray(new File("C:\\Users\\2230\\Desktop\\凱平項(xiàng)目資料\\改裝車(chē)項(xiàng)目\\CXSSBOOT_DB_DDL-1.0.9.sql"));
}catch (Exception e){
}
String content = new String(bytes);
ArticleRequestVO vo = new ArticleRequestVO();
vo.setId(1L);
vo.setTitle("BUG弄潮兒");
vo.setContent(content);
result.put("body", vo);
return result;
}
}Controller 返回?cái)?shù)據(jù)的 VO
package com.olive.vo;
import lombok.Data;
import java.io.Serializable;
@Data
public class ArticleRequestVO implements Serializable {
private Long id;
private String title;
private String content;
}定義 Springboot 引導(dǎo)類(lèi)
package com.olive;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}測(cè)試
測(cè)試的curl
curl -X POST http://127.0.0.1:8080/getArticle

到此這篇關(guān)于Springboot 中的 Filter 實(shí)現(xiàn)超大響應(yīng) JSON 數(shù)據(jù)壓縮的文章就介紹到這了,更多相關(guān)Springboot JSON 數(shù)據(jù)壓縮內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Javacsv實(shí)現(xiàn)Java讀寫(xiě)csv文件
這篇文章主要為大家詳細(xì)介紹了Javacsv實(shí)現(xiàn)Java讀寫(xiě)csv文件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(2)
下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你2021-07-07
詳解使用Jenkins自動(dòng)編譯部署web應(yīng)用
本篇主要介紹基于Jenkins實(shí)現(xiàn)持續(xù)集成的方式,通過(guò)案例介紹線上自動(dòng)編譯及部署的配置過(guò)程,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
SpringBoot中全局異常處理的5種實(shí)現(xiàn)方式小結(jié)
在實(shí)際開(kāi)發(fā)中,異常處理是一個(gè)非常重要的環(huán)節(jié),合理的異常處理機(jī)制不僅能提高系統(tǒng)的健壯性,還能大大提升用戶體驗(yàn),下面我們就來(lái)看看SpringBoot中全局異常處理的5種實(shí)現(xiàn)方式吧2025-03-03
JsonFormat與@DateTimeFormat注解實(shí)例解析
這篇文章主要介紹了JsonFormat與@DateTimeFormat注解實(shí)例解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
SpringCloud?OpenFeign?服務(wù)調(diào)用傳遞?token的場(chǎng)景分析
這篇文章主要介紹了SpringCloud?OpenFeign?服務(wù)調(diào)用傳遞?token的場(chǎng)景分析,本篇文章簡(jiǎn)單介紹?OpenFeign?調(diào)用傳遞?header?,以及多線程環(huán)境下可能會(huì)出現(xiàn)的問(wèn)題,其中涉及到?ThreadLocal?的相關(guān)知識(shí),需要的朋友可以參考下2022-07-07
SpringBoot中的錯(cuò)誤處理機(jī)制源碼解析
這篇文章主要介紹了SpringBoot中的錯(cuò)誤處理機(jī)制源碼解析,springboot根據(jù)訪問(wèn)者的request中的Accept屬性來(lái)判斷要返回什么樣的數(shù)據(jù),SpringBoot存在一個(gè)錯(cuò)誤處理機(jī)制,會(huì)根據(jù)不同請(qǐng)求返回不同的結(jié)果,需要的朋友可以參考下2023-12-12

