目录

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. opencv 增强现实(二):特征点匹配

    import cv2 as cv import numpy as np # def draw_keypoints(img, keypoints): # for kp in keypoints: # x ...

  2. shell 基础(一)

    废话少说 往下看 1. 查看 Shell Shell 是一个程序,一般都是放在/bin或者/user/bin目录下,当前 Linux 系统可用的 Shell 都记录在/etc/shells文件中./e ...

  3. 微服务下的容器部署和管理平台Rancher

    Rancher是什么 Rancher是一个开源的企业级容器管理平台.通过Rancher,企业再也不必自己使用一系列的开源软件去从头搭建容器服务平台.Rancher提供了在生产环境中使用的管理Docke ...

  4. <二>企业级开源仓库nexus3实战应用–使用nexus3配置docker私有仓库

    1,安装nexus3. 这个地方略了,安装部署可以参考:nexus3安装配置. 2,配置走起. 1,创建blob存储. 登陆之后,先创建一个用于存储镜像的空间. 定义一个name,下边的内容会自动补全 ...

  5. CMDB资产管理系统开发【day25】:需求分析

    本节内容 浅谈ITIL CMDB介绍 Django自定义用户认证 Restful 规范 资产管理功能开发 浅谈ITIL TIL即IT基础架构库(Information Technology Infra ...

  6. DUMP102 企业级电商FE

    101 完成 webpack 配置后,有一套类似 live-reload 自动刷新提供 REPL 环境. [配置 webpack.config.js 别名,方便 js 文件做require 支持路径别 ...

  7. 数据库的URL格式

    Oracle数据库: 驱动jar包: ojdbc6.jar 驱动程序类名字:oracle.jdbc.OracleDriver JDBC URL:jdbc:oracle:thin:@//<host ...

  8. 405 css样式的研究 list-style-type 属性研究

    CSS 列表的样式 list-style-type.list-style-position和list-style-image 属性 在CSS中,列表元素是一个块框,列表中的每个表项也是一个块框,只是在 ...

  9. Queue的相关API

    public interface Queue<E> extends Collection<E> :队列通常是以FIFO(先进先出)方式排序元素. boolean add(E e ...

  10. 20175204 张湲祯 2018-2019-2《Java程序设计》第三周学习总结

    20175204 张湲祯 2018-2019-2<Java程序设计>第三周学习总结 教材学习内容总结 -第四章类与对象要点: -面向对象语言三个特性:封装性:继承:多态: -类:1.类是组 ...