SpringBoot文件上传下载
文件上传
下面的所以包都是支持
jakarta
的依赖,Spring Boot 3 已经完全抛弃javax
,如何当前的版本还有javax的依赖,请升级Spring Boot 版本
编写MultipartAutoConfiguration
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.Servlet;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.DispatcherServlet;
/**
* @Title: MultipartAutoConfiguration
* @Author David
* @Package com.work.spring.springbootwork.config
* @Date 2024/9/15 下午4:38
* @description:
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled", matchIfMissing = true)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableConfigurationProperties(MultipartProperties.class)
public class MultipartAutoConfiguration {
private final MultipartProperties multipartProperties;
public MultipartAutoConfiguration(MultipartProperties multipartProperties) {
this.multipartProperties = multipartProperties;
}
@Bean
@ConditionalOnMissingBean(MultipartConfigElement.class) // 移除了 CommonsMultipartResolver.class
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}
@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
}
编写FileUploadController
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
/**
* @Title: FileUploadController
* @Author David
* @Package com.work.spring.springbootwork.controller
* @Date 2024/9/15 下午4:54
*/
@RestController
public class FileUploadController {
// 上传文件保存目录
private static final String UPLOADED_FOLDER = System.getProperty("user.dir") + "/upload/";
@PostMapping("/api/upload")
public Map<String, Object> singleFileUpload(@RequestParam("file") MultipartFile file) throws IOException {
Map<String, Object> response = new HashMap<>();
if (file.isEmpty()) {
response.put("status", "error");
response.put("message", "文件为空,请选择你的文件上传");
return response;
}
saveFile(file);
response.put("status", "success");
response.put("message", "上传文件 " + file.getOriginalFilename() + " 成功");
response.put("url", "/api/download/" + file.getOriginalFilename());
return response;
}
// 保存上传的文件
private void saveFile(MultipartFile file) throws IOException {
Path path = Paths.get(UPLOADED_FOLDER+file.getOriginalFilename());
// 如果目录不存在,则创建目录
if (!Files.exists(path.getParent())) {
Files.createDirectories(path.getParent());
}
// 保存文件
file.transferTo(path);
}
// 下载文件的接口
@GetMapping("/api/download/{filename:.+}")
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) {
try {
Path filePath = Paths.get(UPLOADED_FOLDER).resolve(filename).normalize();
Resource resource = new UrlResource(filePath.toUri());
if (resource.exists() && resource.isReadable()) {
// 设置响应头,提示浏览器下载文件
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"")
.body(resource);
} else {
return ResponseEntity.status(404).body(null);
}
} catch (MalformedURLException e) {
return ResponseEntity.status(500).body(null);
}
}
}
浅谈MultipartAutoConfiguration中的细节
- 注解@ConditionalOnClass
我们先谈一个重要的概念,类路径,对月类路径你是怎么理解的,我取一个大家比较熟悉的语言Java和大家浅聊一下,在你启动SpringBoot项目的时候,你是否有思考过它的运行过程,它从@SpringBootApplication注解的启动类中进入程序,然后向什么方向发展呢?
不考虑类加载时机的化,这个程序的运行过程就像一棵由依赖组成的树,这棵树的根就是启动类。
在我们调用服务就像从根进入指定节点一样,在到达这个节点前,我们需要经过其他节点,经过其他节点时,我们便经过了类路径,那些箭头就是所谓的类路径,是一个服务所需要的包或者是依赖。
@ConditionalOnClass
是 Spring Boot
中的一个条件注解,它的作用是根据类路径中是否存在指定的类,决定是否加载当前的配置类或 @Bean
定义。只有当指定的类存在于类路径时,配置才会被加载。这是 Spring Boot
自动配置的重要机制之一,它使得配置在合适的环境下才会生效,从而避免了不必要的配置加载。
主要功能
- 条件加载配置类或 @Bean:@ConditionalOnClass 只有在类路径中存在指定的类时,才会加载所标注的配置类或 @Bean 方法。 提高模块化和兼容性:通过这个注解,Spring Boot 可以在需要时加载相关功能模块,而不会在不需要时加载。它允许不同的库根据类路径中的可用类进行自定义自动配置。
示例
@ConditionalOnClass({ Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class })
- 注解@ConditionalOnProperty
@ConditionalOnProperty
是 Spring Boot 提供的一个非常强大的注解,用于根据配置文件中的属性值来决定是否启用某个 @Configuration
类或 @Bean
。它允许你通过 Spring 配置文件(如 application.properties
或 application.yml
)中的属性值,动态控制配置的加载。这使得应用程序更加灵活,可以根据不同的环境或需求启用或禁用特定的功能。
- 主要作用
@ConditionalOnProperty
的主要作用是:根据配置文件中的某个属性的值来决定是否启用某个 Spring 配置类或 @Bean
。
例如,你可以在 application.properties
中定义某个属性,当它为 true
时启用某个功能模块;为 false
或者没有定义时则不启用。
- 参数详解
prefix
:- 这是配置属性的前缀,通常用于结构化组织配置。例如,如果你有多个相关的配置项,前缀可以帮助你将它们组合在一起。例如
spring.servlet.multipart
是文件上传相关的配置项的前缀。
示例:
@ConditionalOnProperty(prefix = "spring.servlet.multipart", name = "enabled")
- 这是配置属性的前缀,通常用于结构化组织配置。例如,如果你有多个相关的配置项,前缀可以帮助你将它们组合在一起。例如
name
:- 这是具体要检查的配置属性的名称。你可以通过这个参数指定一个或多个属性名称。
示例:
@ConditionalOnProperty(name = "feature.enabled") // 配置文件中 "feature.enabled" 时才启用文件上传配置
havingValue
:- 这是指定的属性值,只有当属性的值等于
havingValue
时,条件才会成立,配置才会生效。如果没有指定havingValue
,则只检查属性是否存在。
示例:
@ConditionalOnProperty(name = "feature.mode", havingValue = "advanced") // 配置文件中 "feature.mode=advanced" 时才启用
- 这是指定的属性值,只有当属性的值等于
matchIfMissing
:- 当属性缺失时的行为。如果设置为
true
,即使属性没有出现在配置文件中,配置类也会被加载。如果设置为false
,则只有当属性存在并且满足条件时,配置类才会加载。
示例:
@ConditionalOnProperty(name = "feature.enabled", matchIfMissing = true) // 在没有配置的时候默认启用
- 当属性缺失时的行为。如果设置为
- 其余注解粗讲
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
:确保该配置类仅在 Servlet 类型的 Web 应用中生效,比如基于 Spring MVC 的应用。如果是响应式 Web 应用,则不会加载这个配置。@EnableConfigurationProperties(MultipartProperties.class)
:启用MultipartProperties
类的属性绑定功能,允许从配置文件中加载与文件上传相关的属性(如文件大小、临时存储路径等)。@ConditionalOnMissingBean(MultipartConfigElement.class)
:确保只有在没有其他MultipartConfigElement
Bean 定义时,才创建一个默认的MultipartConfigElement
Bean。这种机制常用于 Spring Boot 的自动配置中,以避免覆盖用户的自定义配置。