Springboot文件下载实现和原理分析

Springboot文件下载实现和原理分析

需求
  1. 客户端发送请求,可以下载服务端指定文件
  2. 无论什么文件,不允许浏览器自动解析,必须作为附件下载
分析
  1. 采用springboot实现文件下载,本质上使用的也是javaEE的Servlet+Tomcat技术
  2. 下载文件的本质是获取文件的读取流,在服务端需要将文件内容写入到Response的OutputStream中(注意这个写入流不需要flush)
  3. 为了防止浏览器解析,需要在响应头中,将文件类型设置为附件。
代码
/**
 * 文件下载的controller
 */
@GetMapping("/download")
public void downloadFile(HttpServletResponse response) throws IOException {
    downloadFileService.download(response);
}

/**
 * 文件下载的service代码
 */
public void download(HttpServletResponse response) throws IOException {
    /**
     * 1、获取文件读取流
     * 2、获取response写入流
     * 3、将文件读取流的数据写入到写入流中
     */
    File file = new File("d:\\01.jpg");
    // 在常规的HTTP应答中,Content-Disposition 响应头指示回复的内容该以何种形式展示,
    // 是以内联的形式(即网页或者页面的一部分), 还是以附件的形式下载并保存到本地。
  	// fileName不是必须的,用于作为文件下载之后的默认名称
    response.setHeader("content-disposition", "attachment;filename=" + file.getName());
    try (FileInputStream fileInputStream = new FileInputStream(file)) {
        ServletOutputStream outputStream = response.getOutputStream();
        byte[] bytes = new byte[1024 * 8];
        int readBytes = 0;
        while ((readBytes = fileInputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, readBytes);
        }
    }
}
下载过程Tomcat源码分析

通过http实现文件下载的原理是怎样的?

  1. Servlet下载文件的核心代码是从某个输入流中读取数据,写到response的输出流中

    ServletOutputStream outputStream = response.getOutputStream();
    byte[] bytes = new byte[1024 * 8];
    int readBytes = 0;
    while ((readBytes = fileInputStream.read(bytes)) != -1) {
        outputStream.write(bytes, 0, readBytes);
    }
    
  2. 从上面的代码中自然就带出第一个问题:输出流的数据目的地是哪里?数据被写到哪里去了?接下来通过debug跟write方法的源码来分析这个问题:

    //首先调用write方法
    outputStream.write(bytes, 0, readBytes);
    //然后调用org.apache.catalina.connector.CoyoteOutputStream的write方法
    ob.write(b, off, len);
    //然后调用org.apache.catalina.connector.OutputBuffer的方法
    writeBytes(b, off, len);
    //进一步调用org.apache.catalina.connector.OutputBuffer的方法
    append(b, off, len);//Add data to the buffer
    //当达到一定条件时,将数据写到客户端的output中
    realWriteBytes(ByteBuffer.wrap(src, off, limit));
    //再进一步调用org.apache.coyote.http11.filters.ChunkedOutputFilter
    doWrite(ByteBuffer chunk)
    buffer.doWrite(chunk);
    //在进一步调用org.apache.tomcat.util.net.NioEndpoint
    doWrite(boolean block, ByteBuffer from)
    //继续调用org.apache.tomcat.util.net.NioSelectorPool,到这一步,已经快到了NIO的层面了
    write(ByteBuffer buf, NioChannel socket, Selector selector, long writeTimeout)
    //继续调用org.apache.tomcat.util.net.NioBlockingSelector
    write(ByteBuffer buf, NioChannel socket, long writeTimeout)
    //进一步调用org.apache.tomcat.util.net.NioChannel
    socket.write(buf)
    //再进一步调用java.nio.channels.SocketChannel
    sc.write(src);//此时就调用到了java的nio包,这部分的源码好像就不开放了,只能看到反编译的内容
    
    1. 通过以上源码分析可以推断,数据从原始文件到客户端,从最上层的outputStream的write一直调用到socket的write,最底层的实现还是socket通信。而且数据不是一次性发送完成的,而是分成多个数据包发送,每次数据会首先缓存到io buffer中,当buffer达到临界值,就会调用socket的方法,把数据发送到客户端。
    2. 为了从效果上更加明显地验证数据的分包传输,可以直接把断点打在outputStream的write方法上,可以发现每执行几次write方法,客户端下载的临时文件就会变大。因此,第一个问题可以有比较靠谱的答案了。那就是outputStream的目标是io buffer,最终分批次通过socket写到客户端。
文件下载的buffer原理【高级】

通过分析ServletResponse下载文件的过程,可以得出以下非常有意思的结论:

  1. 定理1:文件下载的本质是网络数据传输,虽然表现为io流的方式,但是逐层跟源码可以发现,response io流的底层还是socket通信。
  2. 定理2:response的outputStream数据实际上是先写入到io buffer中,达到临界条件时,数据通过socket写到客户端去,如此循环,实现分批次传输。
  3. 定理3:文件下载不是一次性把一个文件写出去,而是按照buffer分批次传输,是一个流式的过程。
    1. 推论1:无论下载多大的文件,都不会导致服务内存溢出
    2. 推论2:由于下载文件的过程是按buffer分批次传输的,因此用中间服务代理底层存储不会导致较大的性能损失。
Spring Boot是一个基于Spring框架的开源项目,它的主要目标是使Spring应用程序的开发变得更加快速和简单。Spring Boot的底层实现原理主要包括以下几个方面: 1. 自动配置 Spring Boot提供了丰富的自动配置功能,通过分析应用程序的类路径和配置属性,自动配置Spring应用程序所需要的各种组件。例如,如果应用程序中使用了JPA,Spring Boot会自动配置数据源和实体管理器等必要的组件。 2. 独立运行 Spring Boot的应用程序可以独立运行,不需要任何外部Web容器或应用服务器。它内置了一个嵌入式的Tomcat、Jetty或Undertow服务器,可以直接启动一个可执行的JAR文件来运行应用程序。 3. 简化配置 Spring Boot通过约定大于配置的方式,简化了应用程序的配置。例如,它默认配置了许多常用的Spring特性,如Web MVC、数据访问等,开发者只需要在配置文件中指定少量的属性即可完成应用程序的配置。 4. 松耦合 Spring Boot的各个组件之间松耦合,可以根据需要进行替换或扩展。例如,可以使用自定义的数据源、视图解析器、错误处理器等,而不必修改现有的组件代码。 5. 提供Actuator Spring Boot提供了Actuator组件,可以监控和管理应用程序的运行状态。它提供了多个端点(endpoint),可以查看应用程序的健康状况、环境信息、配置属性等,还可以通过HTTP API进行管理操作,如关闭应用程序、刷新配置等。 总之,Spring Boot的底层实现原理主要是通过自动配置、独立运行、简化配置、松耦合和提供Actuator等功能,来简化Spring应用程序的开发、部署和运维。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值