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 類重新定義了輸出流,攔截需要輸出的數(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ù)類型 */ @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ò)濾器名稱 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)目資料\\改裝車項(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)類
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)文章
Can''t use Subversion command line client:svn 報(bào)錯(cuò)處理
這篇文章主要介紹了Can't use Subversion command line client:svn 報(bào)錯(cuò)處理的相關(guān)資料,需要的朋友可以參考下2016-09-09java使用htmlparser提取網(wǎng)頁(yè)純文本例子
這篇文章主要介紹了java使用htmlparser提取網(wǎng)頁(yè)純文本例子,需要的朋友可以參考下2014-04-04Java實(shí)現(xiàn)迅雷地址轉(zhuǎn)成普通地址實(shí)例代碼
本篇文章主要介紹了Java實(shí)現(xiàn)迅雷地址轉(zhuǎn)成普通地址實(shí)例代碼,非常具有實(shí)用價(jià)值,有興趣的可以了解一下。2017-03-03java 裝飾模式(Decorator Pattern)詳解
這篇文章主要介紹了java 裝飾模式(Decorator Pattern)詳解的相關(guān)資料,需要的朋友可以參考下2016-10-10Java基于二分搜索樹(shù)、鏈表的實(shí)現(xiàn)的集合Set復(fù)雜度分析實(shí)例詳解
這篇文章主要介紹了Java基于二分搜索樹(shù)、鏈表的實(shí)現(xiàn)的集合Set復(fù)雜度分析,結(jié)合實(shí)例形式詳細(xì)分析了Java基于二分搜索樹(shù)、鏈表的實(shí)現(xiàn)的集合Set復(fù)雜度分析相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2020-03-03解決MyEclipse出現(xiàn)the user operation is waiting的問(wèn)題
今天做項(xiàng)目的時(shí)候每次修改代碼保存后都會(huì)跳出一個(gè)框框,然后就有兩個(gè)進(jìn)度條,上面寫(xiě)the user operation is wating...小編去網(wǎng)上查了查解決了這個(gè)問(wèn)題,下面跟大家分享一下。2018-04-04Java Scanner的使用和hasNextXXX()的用法說(shuō)明
這篇文章主要介紹了Java Scanner的使用和hasNextXXX()的用法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10