以下文章来源Java架构师技术,回复”Spring“获惊喜礼包
大家好,我是Java架构师

文章目录

  1. 业务背景
  2. 实现思路
  3. 测试效果
  4. Demo地址

1. 业务背景

是这样的,业务背景是公司的内部系统有一个广告保存接口,需要ADX那边将投放的广告数据进行保存供后续使用。 广告数据大概长这样:
  • adName是广告名字
  • adTag是广告渲染的HTML代码,超级大数据库中都是用text类型来存放的,我看到最大的adTag足足有60kb大小…
{

"adName"
:
""
,

"adTag"
:
""
}

因此,对与请求数据那么大的接口我们肯定是需要作一个优化的否则太大的数据传输有以下几个弊端:
  • 占用网络带宽,而有些云产品就是按照带宽来计费的,间接浪费了钱
  • 传输数据大导致网络传输耗时
为了克服这几个问题团队中的老鸟产生一个想法:
请求广告保存接口时先将Json对象字符串进行GZIP压缩,那请求时传入的就是压缩后的数据,而GZIP的压缩效率是很高的,因此可以大大减小传输数据,而当数据到达广告保存接口前再将传来的数据进行解压缩,还原成JSON对象就完成了整个GZIP压缩数据的请求以及处理流程。
其实这样做也存在着弊端:
  • 请求变复杂了
    • 接口调用方那边需要对数据进行压缩
    • 接口执行方那边需要对拿到的数据进行解压
  • 需要额外占用更多的CPU计算资源
  • 可能会影响到原有的其他接口
对于以上几点基于我们公司当前的业务可以这样解决:
  • 对与需要占用而外的CPU计算资源来说,公司的内部系统属于IO密集型应用,因此用一些CPU资源来换取更快的网络传输其实是很划算的
  • 使用过滤器在请求数据到达Controller之前对数据进行解压缩处理后重新写回到Body中,避免影响Controller的逻辑,代码零侵入
  • 而对于改造接口的同时是否会影响到原来的接口这一点可以通过 HttpHeader 的Content-Encoding=gzip属性来区分是否需要对请求数据进行解压缩
那废话少说,下面给出实现方案

2. 实现思路

前置知识:
  • Http 请求结构以及Content-Encoding 属性
  • gzip压缩方式
  • Servlet Filter
  • HttpServletRequestWrapper
  • Spring Boot
  • Java 输入输出流
实现流程图:
核心代码:
创建一个SpringBoot项目,先编写一个接口,功能很简单就是传入一个Json对象并返回,以模拟将广告数据保存到数据库。另外,搜索公众号Linux就该这样学后台回复“猴子”,获取一份惊喜礼包。
/**

 * 
@ClassName
: ProjectController

 * 
@Author
 zhangjin

 * 
@Date
 2022/3/24 20:41

 * 
@Description
:

 */

@Slf
4j

@RestController
publicclassAdvertisingController
{


@PostMapping
(
"/save"
)

public Advertising saveProject(@RequestBody Advertising advertising)
{

        log.info(
"获取内容"
+ advertising);

return
 advertising;

    }

}


/**

 * 
@ClassName
: Project

 * 
@Author
 zhangjin

 * 
@Date
 2022/3/24 20:42

 * 
@Description
:

 */

@Data
publicclassAdvertising
{

private
 String adName;

private
 String adTag;

}

编写并注册一个拦截器
/**

 * 
@ClassName
: GZIPFilter

 * 
@Author
 zhangjin

 * 
@Date
 2022/3/26 0:36

 * 
@Description
:

 */

@Slf
4j

@Component
publicclassGZIPFilterimplementsFilter
{


privatestaticfinal
 String CONTENT_ENCODING = 
"Content-Encoding"
;

privatestaticfinal
 String CONTENT_ENCODING_TYPE = 
"gzip"
;


@Override
publicvoidinit(FilterConfig filterConfig)throws ServletException 
{

        log.info(
"init GZIPFilter"
);

    }


@Override
publicvoiddoFilter(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
publicvoiddestroy()
{

        log.info(
"destroy GZIPFilter"
);

    }

}


/**

 * 
@ClassName
: FilterRegistration

 * 
@Author
 zhangjin

 * 
@Date
 2022/3/26 0:36

 * 
@Description
:

 */

@Configuration
publicclassFilterRegistration
{


@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中

 */

@Slf
4j

publicclassUnZIPRequestWrapperextendsHttpServletRequestWrapper
{


privatefinalbyte
[] bytes;


publicUnZIPRequestWrapper(HttpServletRequest request)throws IOException 
{

super
(request);

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

             ByteArrayOutputStream baos = 
new
 ByteArrayOutputStream()) {

finalbyte
[] body;

byte
[] buffer = 
newbyte
[
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);

returnnew
 ServletInputStream() {


@Override
publicbooleanisFinished()
{

returnfalse
;

            }


@Override
publicbooleanisReady()
{

returnfalse
;

            }


@Override
publicvoidsetReadListener(ReadListener readListener)
{


            }


publicintread()throws IOException 
{

return
 byteArrayInputStream.read();

            }

        };

    }


@Override
public BufferedReader getReader()throws IOException 
{

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

    }


}

附上压缩工具类
publicclassGZIPUtils
{


publicstaticfinal
 String GZIP_ENCODE_UTF_8 = 
"UTF-8"
;


/**

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

     * 
@param
 str

     * 
@return
     */

publicstaticbyte
[] compress(String str) {

return
 compress(str, GZIP_ENCODE_UTF_8);

    }


/**

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

     * 
@param
 str

     * 
@param
 encoding

     * 
@return
     */

publicstaticbyte
[] compress(String str, String encoding) {

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

returnnull
;

        }

        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
     */

publicstaticbyte
[] uncompress(
byte
[] bytes) {

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

returnnull
;

        }

        ByteArrayOutputStream out = 
new
 ByteArrayOutputStream();

        ByteArrayInputStream in = 
new
 ByteArrayInputStream(bytes);

try
 {

            GZIPInputStream ungzip = 
new
 GZIPInputStream(in);

byte
[] buffer = 
newbyte
[
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
     */

publicstatic String uncompressToString(byte[] bytes)throws IOException 
{

return
 uncompressToString(bytes, GZIP_ENCODE_UTF_8);

    }


/**

     *

     * 
@param
 bytes

     * 
@return
     */

publicstaticbyte
[] uncompressToByteArray(
byte
[] bytes) 
throws
 IOException {

return
 uncompressToByteArray(bytes, GZIP_ENCODE_UTF_8);

    }


/**

     * 解压成字符串

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

     * 
@param
 encoding 编码方式

     * 
@return
 解压后的字符串

     */

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

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

returnnew
 String(result);

    }


/**

     * 解压成字节数组

     * 
@param
 bytes

     * 
@param
 encoding

     * 
@return
     */

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

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

returnnull
;

        }

        ByteArrayOutputStream out = 
new
 ByteArrayOutputStream();

        ByteArrayInputStream in = 
new
 ByteArrayInputStream(bytes);

try
 {

            GZIPInputStream ungzip = 
new
 GZIPInputStream(in);

byte
[] buffer = 
newbyte
[
256
];

int
 n;

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

                out.write(buffer, 
0
, n);

            }

return
 out.toByteArray();

        } 
catch
 (IOException e) {

            e.printStackTrace();

thrownew
 IOException(
"解压缩失败!"
);

        }

    }


/**

     * 将字节流转换成文件

     * 
@param
 filename

     * 
@param
 data

     * 
@throws
 Exception

     */

publicstaticvoidsaveFile(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);

        }

    }

3. 测试效果

注意一个大坑:千万不要直接将压缩后的byte[]当作字符串进行传输,否则你会发现压缩后的请求数据竟然比没压缩后的要大得多🐶!一般有两种传输压缩后的byte[]的方式:
  • 将压缩后的byet[]进行base64编码再传输字符串,这种方式会损失掉一部分GZIP的压缩效果,适用于压缩结果要存储在Redis中的情况
  • 将压缩后的byte[]以二进制的形式写入到文件中,请求时直接在body中带上文件即可,用这种方式可以不损失压缩效果
Postman测试Gzip压缩数据请求:
  • 请求头指定数据压缩方式:
  • Body带上压缩后的byte[]写入的二进制文件
  • 执行请求,服务端正确处理了请求并且请求size缩小了将近一半,效果还是很不错的,这样GZIP压缩数据的请求的处理就完成了,完整的项目代码在下方⬇️

4. Demo地址

  • https://gitee.com/wx_1bceb446a4/gziptest
欢迎有需要的同学试试,如果本文对您有帮助,也请帮忙点个 赞 + 在看 啦!❤️
在 GitHub猿 还有更多优质项目系统学习资源,欢迎分享给其他同学吧!
最后,整理了400多套项目,赠送读者。扫码下方二维码,后台回复赚钱即可获取。
--END--
继续阅读
阅读原文