目录

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; @Autowired
public 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:.+}")
@ResponseBody
public 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);
@Bean
Stream<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);
}
@Bean
CommandLineRunner 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; @Autowired
public FileSystemStorageService(StorageProperties properties) {
System.out.println(properties.test);
this.rootLocation = Paths.get(properties.getLocation());
} @Override
public 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 check
throw 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);
}
} @Override
public 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);
} } @Override
public Path load(String filename) {
return rootLocation.resolve(filename);
} @Override
public 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);
}
} @Override
public void deleteAll() {
FileSystemUtils.deleteRecursively(rootLocation.toFile());
} @Override
public 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;
}
}

没解决的一个问题

  1. @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 { @Override
public void init() {
// TODO Auto-generated method stub } @Override
public void store(MultipartFile file) {
// TODO Auto-generated method stub } @Override
public Stream<Path> loadAll() {
// TODO Auto-generated method stub
return null;
} @Override
public Path load(String filename) {
// TODO Auto-generated method stub
return null;
} @Override
public Resource loadAsResource(String filename) {
// TODO Auto-generated method stub
return null;
} @Override
public 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不知道选择哪一个了。

解决办法

根据上面的提示,我们去解决它。

  1. 在冲突的两个Service中的某一个上,增加注解 @Primary
//在主要的实现类 FileSystemStorageService 前面加@Primary
@Primary()
public class FileSystemStorageService implements StorageService {
}
  1. 对每个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.xls
at 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上传文件例子的内容

【Spring Boot】关于上传文件例子的剖析的更多相关文章

  1. spring boot(十七)上传文件

    上传文件是互联网中常常应用的场景之一,最典型的情况就是上传头像等,今天就带着带着大家做一个Spring Boot上传文件的小案例. 1.pom包配置 我们使用Spring Boot最新版本1.5.9. ...

  2. Spring Boot (30) 上传文件

    文件上传 上传文件和下载文件是Java Web中常见的一种操作,文件上传主要是将文件通过IO流传输到服务器的某一个文件夹下. 导入依赖 在pom.xml中添加上spring-boot-starter- ...

  3. Spring Boot应用上传文件时报错

    问题描述 Spring Boot应用(使用默认的嵌入式Tomcat)在上传文件时,偶尔会出现上传失败的情况,后台报错日志信息如下:"The temporary upload location ...

  4. 使用Spring boot + jQuery上传文件(kotlin)

    文件上传也是常见的功能,趁着周末,用Spring boot来实现一遍. 前端部分 前端使用jQuery,这部分并不复杂,jQuery可以读取表单内的文件,这里可以通过formdata对象来组装键值对, ...

  5. spring mvc(注解)上传文件的简单例子

    spring mvc(注解)上传文件的简单例子,这有几个需要注意的地方1.form的enctype=”multipart/form-data” 这个是上传文件必须的2.applicationConte ...

  6. Spring框架学习笔记(7)——Spring Boot 实现上传和下载

    最近忙着都没时间写博客了,做了个项目,实现了下载功能,没用到上传,写这篇文章也是顺便参考学习了如何实现上传,上传和下载做一篇笔记吧 下载 主要有下面的两种方式: 通过ResponseEntity实现 ...

  7. Spring Boot:上传文件大小超限制如何捕获 MaxUploadSizeExceededException 异常

    Spring Boot 默认上传文件大小限制是 1MB,默认单次请求大小是 10MB,超出大小会跑出 MaxUploadSizeExceededException 异常 spring.servlet. ...

  8. Android+Spring Boot 选择+上传+下载文件

    2021.02.03更新 1 概述 前端Android,上传与下载文件,使用OkHttp处理请求,后端使用Spring Boot,处理Android发送来的上传与下载请求.这个其实不难,就是特别多奇奇 ...

  9. RestTemplate OR Spring Cloud Feign 上传文件

    SpringBoot,通过RestTemplate 或者 Spring Cloud Feign,上传文件(支持多文件上传),服务端接口是MultipartFile接收. 将文件的字节流,放入ByteA ...

随机推荐

  1. poj 2955 Brackets (区间dp 括号匹配)

    Description We give the following inductive definition of a “regular brackets” sequence: the empty s ...

  2. python中logger模块的应用

    logger模块是python内置的一个模块,主要用于输出运行日志,可以输出日志的等级,日志的保存路径等 具体详见博客https://www.cnblogs.com/qianyuliang/p/723 ...

  3. JAVA WEB开发环境与搭建

    一:jdk下载与安装 (1)官网下载地址:https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-506665 ...

  4. css实现文本两端对齐

    display:inline-block; text-align:center; text-align-last:justify;

  5. Javascript arguments.callee和caller的区别

    一.callee 在学习callee之前,需要先学习arguments. arguments: 含义:该对象代表正在执行的函数和调用它的函数的参数. 语法: 1 [function.]argument ...

  6. 怎样给手机安装fiddler证书

    如果需要抓取手机端的HTTPS包,就要在手机上面安装fiddler证书. 1.使用手机连接WiFi做好代理: 2.代理成功后打开手机浏览器: 3.在浏览器输入:http://IP地址:端口号后搜索(如 ...

  7. dubbo负载均衡与集群集群容错

    1.负载均衡 在集群负载均衡时,Dubbo 提供了多种均衡策略,缺省为 random 随机调用. 1.  负载均衡策略 Random LoadBalance 随机,按权重设置随机概率.(默认值)在一个 ...

  8. day03 数据类型与运算符

    今日内容: 1.变量及常量的命名规范 2.与用户的交互 3.字符串的格式化输出 4.基本的数据类型 5.运算符 6.注释 今日重点: 1.变量及常量的命名规范 (1)强制规范[如果违反会报错] 1&g ...

  9. Java消息队列--ActiveMq 初体验

    1.下载安装ActiveMQ ActiveMQ官网下载地址:http://activemq.apache.org/download.html ActiveMQ 提供了Windows 和Linux.Un ...

  10. ansible 使用记录

    copy: ansible server -m copy -a 'src=/etc/ansible/port/iptables dest=/etc/sysconfig/iptables owner=r ...