流数据是将数据发送到 Web 浏览器的一种全新的方法,因为它提供了大大加快页面加载时间。很多时候,我们需要允许用户在 Web 应用程序中下载文件。当数据太大时,为用户提供良好的体验就变得相当困难。
Spring 支持通过流响应Body进行异步请求处理。在此方法中,应用程序将数据直接写入响应 OutputStream,而不保留 Servlet 容器线程。Spring 中还有其他一些处理异步请求处理的方法。
在本文中,我们将介绍如何使用流式响应Body下载文件的示例。在此方法中,数据被处理,并以块方式写入 OutputStream。
设置弹簧启动项目
创建示例弹簧启动应用程序。下面是我的项目结构示例。我已经手动创建了项目,但您也可以使用弹簧仪创建。
让我们向 Maven POM 添加一些基本依赖项。
<?xml version="1.0" encoding="UTF-8"?>
<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.techshard.streamingresponse</groupId>
<artifactId>springboot-download</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org
apache.commons_lt;/组Id>
<工件Id>公文-io</工件Id>
<版本>1.3.2</版本>
</依赖项>
</依赖项>
</项目>
现在,我们将创建一个控制器并添加一个 API 终结点进行下载。这是我的完整控制器。
package com.techshard.download.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@RestController
@RequestMapping ("/api")
public class DownloadController {
private final Logger logger = LoggerFactory.getLogger(DownloadController.class);
@GetMapping (value = "/download", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<StreamingResponseBody> download(final HttpServletResponse response) {
response.setContentType("application/zip");
response.setHeader(
"Content-Disposition",
"attachment;filename=sample.zip");
StreamingResponseBody stream = out -> {
final String home = System.getProperty("user.home");
final File directory = new File(home + File.separator + "Documents" + File.separator + "sample");
final ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream());
if(directory.exists() && directory.isDirectory()) {
try {
for (final File file : directory.listFiles()) {
final InputStream inputStream=new FileInputStream(file);
final ZipEntry zipEntry=new ZipEntry(file.getName());
zipOut.putNextEntry(zipEntry);
byte[] bytes=new byte[1024];
int length;
while ((length=inputStream.read(bytes)) >= 0) {
zipOut.write(bytes, 0, length);
}
inputStream.close();
}
zipOut.close();
} catch (final IOException e) {
logger.error("Exception while reading and streaming data {} ", e);
}
}
};
logger.info("steaming response {} ", stream);
return new ResponseEntity(stream, HttpStatus.OK);
}
}
在此 API 终结点中,我们从目录中读取多个文件并创建 zip 文件。我们正在流响应机构中执行此过程。它将数据直接写入OutputStream,然后再使用响应实体将写入的信息传回客户端。这意味着在服务器处理和写入数据以区块形式写入数据时,下载过程将在客户端上立即开始。
启动服务器并使用http://localhost:8080/api/downloadTask执行器是一个接口,用于抽象可运行的执行。
让我们配置任务执行器。下面是 AsyncConfiguration 类,它使用 WebMvcCo 图记器配置超时,并注册一个拦截器,该拦截器在有超时时调用,以防需要一些特殊处理。
package com.techshard.download;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
import org.springframework.web.context.request.async.TimeoutCallableProcessingInterceptor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.concurrent.Callable;
@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {
private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
@Override
@Bean (name = "taskExecutor")
public AsyncTaskExecutor getAsyncExecutor() {
log.debug("Creating Async Task Executor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(25);
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
/** Configure async support for Spring MVC. */
@Bean
public WebMvcConfigurer webMvcConfigurerConfigurer(AsyncTaskExecutor taskExecutor, CallableProcessingInterceptor callableProcessingInterceptor) {
return new WebMvcConfigurer() {
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(360000).setTaskExecutor(taskExecutor);
configurer.registerCallableInterceptors(callableProcessingInterceptor);
WebMvcConfigurer.super.configureAsyncSupport(configurer);
}
};
}
@Bean
public CallableProcessingInterceptor callableProcessingInterceptor() {
return new TimeoutCallableProcessingInterceptor() {
@Override
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
log.error("timeout!");
return super.handleTimeout(request, task);
}
};
}
}
结论
使用流式响应Body,我们现在可以轻松地流式传输高并发应用程序的数据。我希望你喜欢这篇文章。请让我知道,如果你有任何意见或建议在下面的评论部分。
本文的示例可以在GitHub 存储库中找到。