【Spring Boot】关于上传文件例子的剖析
目录
- Spring Boot 上传文件
- 功能实现
- 增加Controller
FileUploadController
- 增加Service
StorageService
- 增加一个Thymeleaf页面
- 修改一些简单的配置
application.properties
- 修改Spring Boot Application类
- 官网没有说明其他的Service类的定义
- 至此,运行项目。可以再上传与下载文件
- 增加Controller
- 介绍
@ConfigurationProperties
的用法- 上面例子的
@ConfigurationProperties
- 扩展:自定义一个Properties供自己使用
- 没解决的一个问题
- 上面例子的
- 介绍面向本例中的面向接口编程实现的Java的多态
- 创建第二个实现
StorageService
的类之后的错误 - 解决办法
- 至此,可以在代码运行时,动态的指定实现类。
- 创建第二个实现
- 关于自定义异常处理
- @Exceptionhandler
- ErrorController
- 关于模板引擎
Thymeleaf
的用法- POM.xml增加依赖
- 修改默认模板路径
- 使用
- 以上是总结Spring Boot上传文件例子的内容
- 功能实现
Spring Boot 上传文件
文件上传是一个基本需求,话不多说,我们直接演练
功能实现
增加ControllerFileUploadController
代码
package com.example.kane.Controller;
import org.springframework.stereotype.Controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.*;import java.io.IOException;
import java.util.stream.Collectors;import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;import com.example.kane.service.StorageException;
import com.example.kane.service.StorageFileNotFoundException;
import com.example.kane.service.StorageService;import com.example.kane.Controller.FileUploadController;
@Controller
public class FileUploadController {@Autowired()//@Qualifier("FileSystemStorageService") private StorageService storageService;@Autowiredpublic FileUploadController(StorageService storageService) {this.storageService = storageService;System.out.println(this.storageService);}@GetMapping("/")public String listUploadedFiles(Model model) throws IOException {System.out.println(this.storageService);model.addAttribute("files", storageService.loadAll().map(path -> MvcUriComponentsBuilder.fromMethodName(FileUploadController.class,"serveFile", path.getFileName().toString()).build().toString()).collect(Collectors.toList()));return "uploadForm";}@GetMapping("/files/{filename:.+}")@ResponseBodypublic ResponseEntity<Resource> serveFile(@PathVariable String filename) {Resource file = storageService.loadAsResource(filename);return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,"attachment; filename=\"" + file.getFilename() + "\"").body(file);}@PostMapping("/")public String handleFileUpload(@RequestParam("file") MultipartFile file,RedirectAttributes redirectAttributes) {storageService.store(file);redirectAttributes.addFlashAttribute("message","You successfully uploaded " + file.getOriginalFilename() + "!");return "redirect:/";}@ExceptionHandler(StorageFileNotFoundException.class)public ResponseEntity<?> handleStorageFileNotFound(StorageFileNotFoundException exc) {System.out.println(11);return ResponseEntity.notFound().build();}
}
逻辑分析
- 控制类汇总有一个私有的
StorageService
的类,做逻辑处理。 - 发送GET 请求,URL 匹配到
/
时,进入的文件上传的页面,页面汇总包含已上传文件列表、上传按钮。此处使用了Thymeleaf模板引擎,后面会介绍 - 发送GET请求,URL匹配到
/files/{filename}
时进行下载文件功能。 - 发送POST请求,URL匹配到
/
时,进行上传文件的请求 - 当遇到
StorageFileNotFoundException
的时候异常处理
增加ServiceStorageService
代码
package com.example.kane.service;import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.context.annotation.Bean;
import java.nio.file.Path;
import java.util.stream.Stream;
@Service
public interface StorageService {void init();void store(MultipartFile file);@BeanStream<Path> loadAll();Path load(String filename);Resource loadAsResource(String filename);void deleteAll();}
逻辑分析
上面的Service,只是一个接口,本例中,官方是面向接口编程,实现了JAVA的多态。后面会有介绍。
增加一个Thymeleaf页面
注:Thymeleaf后面整体介绍,此处简单的HTML 页面,不多做说明。
<html xmlns:th="http://www.thymeleaf.org">
<body><div th:if="${message}"><h2 th:text="${message}"/></div><div><form method="POST" enctype="multipart/form-data" action="/"><table><tr><td>File to upload:</td><td><input type="file" name="file" /></td></tr><tr><td></td><td><input type="submit" value="Upload" /></td></tr></table></form></div><div><ul><li th:each="file : ${files}"><a th:href="${file}" th:text="${file}" /></li></ul></div></body>
</html>
修改一些简单的配置application.properties
spring.servlet.multipart.max-file-size=128KB # file size
spring.servlet.multipart.max-request-size=128KB # request size
修改Spring Boot Application类
代码
package com.example.kane;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;import org.slf4j.Logger;import com.example.kane.config.db_config;
import com.example.kane.service.StorageService;import com.example.kane.service.StorageProperties;import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate;import com.example.kane.Model.Customer;@SpringBootApplication
@EnableConfigurationProperties(StorageProperties.class)
//@EnableScheduling
public class RestfulWebService1Application{private static final Logger log = LoggerFactory.getLogger(RestfulWebService1Application.class);public static void main(String args[]) {SpringApplication.run(RestfulWebService1Application.class, args);}@BeanCommandLineRunner init(StorageService storageService) {return (args) -> {//storageService.deleteAll(); storageService.init();};}
}
逻辑分析
- 开启Spring Boot项目
- 定义了一个项目启动后需要运行删除所有文件的逻辑。
CommandLineRunner
之前有做介绍。
官网没有说明其他的Service类的定义
按照官网至此已经创建完成了上传文件的应用,但是少了一部分内容,就是其他的Service的定义情况。下面做补充。
接口StorageService
的实现类FileSystemStorageService
package com.example.kane.service;import java.util.stream.Stream;import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.stream.Stream;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import org.springframework.util.FileSystemUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
@Service(value="FileSystemStorageService")
@Primary()
public class FileSystemStorageService implements StorageService {private final Path rootLocation;@Autowiredpublic FileSystemStorageService(StorageProperties properties) {System.out.println(properties.test);this.rootLocation = Paths.get(properties.getLocation());}@Overridepublic void store(MultipartFile file) {String filename = StringUtils.cleanPath(file.getOriginalFilename());try {if (file.isEmpty()) {throw new StorageException("Failed to store empty file " + filename);}if (filename.contains("..")) {// This is a security checkthrow new StorageException("Cannot store file with relative path outside current directory "+ filename);}try (InputStream inputStream = file.getInputStream()) {Files.copy(inputStream, this.rootLocation.resolve(filename),StandardCopyOption.REPLACE_EXISTING);}}catch (IOException e) {throw new StorageException("Failed to store file " + filename, e);}}@Overridepublic Stream<Path> loadAll() {try {return Files.walk(this.rootLocation, 1).filter(path -> !path.equals(this.rootLocation)).map(this.rootLocation::relativize);}catch (IOException e) {throw new StorageException("Failed to read stored files", e);}}@Overridepublic Path load(String filename) {return rootLocation.resolve(filename);}@Overridepublic Resource loadAsResource(String filename) {try {Path file = load(filename);Resource resource = new UrlResource(file.toUri());if (resource.exists() || resource.isReadable()) {return resource;}else {throw new StorageFileNotFoundException("Could not read file: " + filename);}}catch (MalformedURLException e) {throw new StorageFileNotFoundException("Could not read file: " + filename, e);}}@Overridepublic void deleteAll() {FileSystemUtils.deleteRecursively(rootLocation.toFile());}@Overridepublic void init() {try {Files.createDirectories(rootLocation);}catch (IOException e) {throw new StorageException("Could not initialize storage", e);}}
}
属性类StorageProperties
package com.example.kane.service;
import org.springframework.boot.context.properties.ConfigurationProperties;@ConfigurationProperties("storage")
public class StorageProperties {/*** Folder location for storing files*/private String location = "upload-dir";public String test;public String getLocation() {return location;}public void setLocation(String location) {this.location = location;}public void settest(String test) {this.test=test;}public String gettest() {return test;}}
异常类StorageFileNotFoundException
StorageException
定义
//StorageFileNotFoundException
package com.example.kane.service;public class StorageFileNotFoundException extends StorageException{public StorageFileNotFoundException(String message) {super(message);}public StorageFileNotFoundException(String message, Throwable cause) {super(message, cause);}
}
//StorageException
package com.example.kane.service;public class StorageException extends RuntimeException{public StorageException(String message) {super(message);System.out.println(111111111);}public StorageException(String message, Throwable cause) {super(message, cause);System.out.println(111111111);}
}
至此,运行项目。可以再上传与下载文件
注:在StorageProperties
中定义了文件在服务中的位置upload-dir
介绍@ConfigurationProperties
的用法
上面例子的 @ConfigurationProperties
@ConfigurationProperties
可以使用application.properties中的属性。在官网的例子中,application.properties可以任意定义storage.test=123
然后在类StorageProperties
中书写get、set方法之后,就可以使用了。官网的例子并没有使用,我们可以将location的默认赋值改成如下做法
application.properties
storage.location= upload-dir #只需要加这一行就可以
Storage.Properties
@ConfigurationProperties("storage")
public class StorageProperties {private String location;public String getLocation() {return location;}public void setLocation(String location) {this.location = location;}
}
按照如上操作,定义的@ConfigurationProperties("storage")才是有意义的,否则根本没有使用到。
扩展:自定义一个Properties供自己使用
默认情况下Spring Boot 会使用 Application.properties,我们再同级目录下创建文件storage.properties
storage.properties
storage.location=upload-dir #注意这里要不加任何引号
Storage.Properties
package com.example.kane.service;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Primary //标注当前类为主要的bean类
@Configuration//让当前类能够被Spring识别
@ConfigurationProperties(prefix="storage")
@PropertySource("classpath:storage.properties") //配置路径
public class StorageProperties {/*** Folder location for storing files*///private String location = "upload-dir";private String location;public String getLocation() {return location;}public void setLocation(String location) {this.location = location;}
}
没解决的一个问题
- @Primary这个注解是必须的。
我在这里遇到这个问题,报错信息如下,可以看到是spring没法识别使用哪个bean了,这块没弄明白,应为第一个properties是打包后的target目录下的,第二则是实际代码中的,但是确有冲突,我Primary加上之后,会让spring拿当前类为首要的当做configuration的类。如果是别的原因造成的望指正
Parameter 0 of constructor in com.example.kane.service.FileSystemStorageService required a single bean, but 2 were found:- storageProperties: defined in file [C:\Workflow\Eclipse WS\Restful-Web-Service-1\target\classes\com\example\kane\service\StorageProperties.class]- storage-com.example.kane.service.StorageProperties: defined in null
介绍面向本例中的面向接口编程实现的Java的多态
在官网的例子中,在FileUploadController
中定义了私有变量是,接口StorageService
,而由于当前项目中只有一个类FileSystemStorageService
实现了这个接口,所以项目能够正常运行。而加入我们项目中存在第二个类去实现StorageService
会怎么样呢。
创建第二个实现StorageService
的类之后的错误
- 创建Service类
twostorageservice
,不需要做什么具体实现。
package com.example.kane.service;import java.nio.file.Path;
import java.util.stream.Stream;
import org.springframework.stereotype.Service;import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
@Service(value="twostorageservice")
public class twostorageservice implements StorageService {@Overridepublic void init() {// TODO Auto-generated method stub}@Overridepublic void store(MultipartFile file) {// TODO Auto-generated method stub}@Overridepublic Stream<Path> loadAll() {// TODO Auto-generated method stubreturn null;}@Overridepublic Path load(String filename) {// TODO Auto-generated method stubreturn null;}@Overridepublic Resource loadAsResource(String filename) {// TODO Auto-generated method stubreturn null;}@Overridepublic void deleteAll() {// TODO Auto-generated method stub}}
- 结果报错如下
Parameter 0 of constructor in com.example.kane.Controller.FileUploadController required a single bean, but 2 were found:- FileSystemStorageService: defined in file [C:\Workflow\Eclipse WS\Restful-Web-Service-1\target\classes\com\example\kane\service\FileSystemStorageService.class]- twostorageservice: defined in file [C:\Workflow\Eclipse WS\Restful-Web-Service-1\target\classes\com\example\kane\service\twostorageservice.class]Action:Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
信息很明显,出现了两个bean Spring不知道选择哪一个了。
解决办法
根据上面的提示,我们去解决它。
- 在冲突的两个Service中的某一个上,增加注解
@Primary
//在主要的实现类 FileSystemStorageService 前面加@Primary
@Primary()
public class FileSystemStorageService implements StorageService {
}
- 对每个Service定义一个name,在使用时进行选择
//FileSystemStorageService
@Service(value="FileSystemStorageService")
@Primary
public class FileSystemStorageService implements StorageService {}
//twostorageservice
@Service(value="twostorageservice")
public class twostorageservice implements StorageService {}
// --------------------------使用的位置
@Autowired()
@Qualifier("twostorageservice")
private StorageService storageService;
// ------------------------controller中 我们打印一下Service,不管功能实现@GetMapping("/")public String listUploadedFiles(Model model) throws IOException {System.out.println(this.storageService);}
// 输出结果
com.example.kane.service.twostorageservice@1364c
注:实现时发现一个问题,@Primary 与@Qualifier不是两个并行的解决办法。方法2中也需要指定一个Service为主要实现类,否则还是会报错。
至此,可以在代码运行时,动态的指定实现类。
关于自定义异常处理
@Exceptionhandler
@Exceptionhandler在Controller中定义,对不同的Exception定义不同的处理方法。官网的例子中对StorageFileNotFoundException
定义了处理方法。
- 我们删除一个文件,然后点击其连接下载
查看控制台输出
2019-03-18 15:56:43.071 WARN 16004 --- [nio-8080-exec-2] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [com.example.kane.service.StorageFileNotFoundException: Could not read file: Data Analize.xls]
查看页面输出
找不到 localhost 的网页 找不到与以下网址对应的网页:http://localhost:8080/files/Data%20Analize.xls
HTTP ERROR 404
- 我们将Controller中@Exceptionhandler方法注释掉,在看控制台输出
是一大串很长的Exception
com.example.kane.service.StorageFileNotFoundException: Could not read file: Data Analize.xlsat com.example.kane.service.FileSystemStorageService.loadAsResource(FileSystemStorageService.java:83) ~[classes/:na]at com.example.kane.Controller.FileUploadController.serveFile(FileUploadController.java:55) ~[classes/:na]at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_172]at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_172]at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_172]at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_172]at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:189) ~[spring-web-5.1.5.RELEASE.jar:5.1.5.RELEASE]
- 我们再Controller中增加对异常类
StorageException
的处理方法
@ExceptionHandler(StorageException.class)public ResponseEntity<?> storageException(StorageException exc) {return new ResponseEntity<Object>("test",HttpStatus.GATEWAY_TIMEOUT);}
//对异常页面做到自定义
- 上传一个空文件,出发
StorageException
异常
查看控制台输出
2019-03-18 16:04:15.591 WARN 16004 --- [nio-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [com.example.kane.service.StorageException: Failed to store empty file New Text Document.txt]
查看页面输出
test
ErrorController
我们还可以定一个一个类实现ErrorController
来对Controller的异常进行处理。
关于模板引擎 Thymeleaf
的用法
POM.xml增加依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
修改默认模板路径
默认的路径是resources/templates
,我们可以再application.properties
文件中配置如下属性spring.thymeleaf.prefix= classpath:/templates/test/
进行修改
使用
@GetMapping("/")public String listUploadedFiles(Model model) throws IOException {//往模板文件中增加变量model.addAttribute("files", storageService.loadAll().map(path -> MvcUriComponentsBuilder.fromMethodName(FileUploadController.class,"serveFile", path.getFileName().toString()).build().toString()).collect(Collectors.toList()));return "uploadForm"; //模板文件的名字不带html}
以上是总结Spring Boot上传文件例子的内容
转载于:https://www.cnblogs.com/primadonna/p/10552907.html
【Spring Boot】关于上传文件例子的剖析相关推荐
- spring boot进行上传文件
1.pom文件添加依赖 <!-- 添加thymeleaf --><dependency> <groupId>org.springframework.boot< ...
- Spring Boot (30) 上传文件
文件上传 上传文件和下载文件是Java Web中常见的一种操作,文件上传主要是将文件通过IO流传输到服务器的某一个文件夹下. 导入依赖 在pom.xml中添加上spring-boot-starter- ...
- Spring Boot——获取上传文件的MD5值解决方案
解决方案 方法一: /*** 上传文件* @param file 文件* @return ResponseBean*/@ApiOperation(value = "上传文件",no ...
- spring boot +ajax上传文件前后端分离完整实现示例代码
1.案例场景 此处,我这里需要前端实现上传身份证OCR识别证件号码. 2.前端实现方式 2.1页面按钮 <div class="title-icon"></div ...
- spring mvc(注解)上传文件的简单例子
spring mvc(注解)上传文件的简单例子,这有几个需要注意的地方 1.form的enctype="multipart/form-data" 这个是上传文件必须的 2.appl ...
- Spring Boot 批量上传: The field files exceeds its maximum permitted size of 1048576 bytes.
Spring Boot 批量上传异常: org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededExcep ...
- spring boot配置对上传文件的大小限制
spring boot配置对上传文件的大小限制 spring-boot 2.0之前 Spring Boot1.4版本后配置更改为(默认单个文件最大1Mb,单次请求文件总数大小最大10Mb): spri ...
- Spring Boot:上传文件大小超限制如何捕获 MaxUploadSizeExceededException 异常
Spring Boot 默认上传文件大小限制是 1MB,默认单次请求大小是 10MB,超出大小会跑出 MaxUploadSizeExceededException 异常. 问题来了,当文件超过 1M ...
- Spring boot项目上传Linux服务器后 上传图片后,图片路径报404,图片路径映射问题
问题 spring boot项目上传到Linux服务器后,头像上传后报404.但是我的打包的jar包在idea客户端运行是没问题的 解决 错误代码 @Overridepublic void addRe ...
最新文章
- 【2.CentOS6.8安装JDK】
- 服务器控件GridView的排序问题
- servlet action is not available
- java jsessionid 会话,如何使用JSESSIONID手动加载Java会话?
- 因开源协议“大打出手”,AWS 宣布创建 Elasticsearch、Kibana 分支
- php open_basedir
- Visual C++编译选项
- Android最佳实践之流畅设计
- 连接服务器成功获取角色信息,客户端 获取 服务器 角色属性
- 客快物流大数据项目(一):物流项目介绍和内容大纲
- Mixly编程使用Arduino ESP32实现ESP-NOW组网
- win10自带c语言编程猫,编程猫教程:编程猫如何做游戏?
- 烤氏历史第4集:TD往事-商用分水岭
- oracle语句报错 * ERROR at line 2: ORA-00923: FROM keyword not found where expected
- 计算机桌面操作系统版本,如何看电脑操作系统版本
- java.io.IOException: Cleartext HTTP traffic to xxx.xxx.xxx.xxx not permitted
- 通过js实现单击显示隐藏图片
- 分页第一页用0还是1_洗脸用冷水还是热水好?这些错误只要犯1个,你的脸就白洗了!...
- GEE学习记录~~~
- C++ qsort用法