SpringBoot整合Web开发
3、文件上传
SpringMvc对于文件上传进行了简化,而SpringBoot更是更进一步简化了文件上传
Java文件上传涉及到两个组件:1、CommonsMultipartResolver 2、StandardServletMultipartResolver
(1)CommonsMultipartResolver :使用commons-fileupload来处理multipart
(2)StandardServletMultipartResolver:基于 Servlet3.0来处理mulpart请求
因此使用StandardServletMultipartResolver不需要添加格外的jar,tomcat7就支持了Servlet3.0,而SpringBoot2.0.4内嵌的Tomcat是Tomcat8.5.32,因此可以直接使用StandardServletMultipartResolver,而SpringBoot提供了文件上传的自动化配置类
MultipartAutoConfiguration,默认也是采用StandardServletMultipartResolver,部分源码如下:
@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 = {"multipartResolver"}
)
@ConditionalOnMissingBean({MultipartResolver.class})
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}
}
根据这里的配置可以看到,如果开发者没有提供MultipartResolver,那么默认采用的MultipartResolver就是StandardServletMultipartResolver,因此SpringBoot的上传文件可以做到零配置,具体的步骤如下:
(1)单文件上传
创建一个upload.html文件:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/chapter02/file/upload" method="post" enctype="multipart/form-data">
<input type="file" name="uploadFile" value="请选择文件">
<input type="submit" value="提交2">
</form>
</body>
</html>
后台接口代码如下:
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
@RestController
@RequestMapping("/file")
public class UrlController {
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd");
@RequestMapping("/fileUploadUrl")
public ModelAndView HomePage(){
ModelAndView modelAndView=new ModelAndView();
modelAndView.setViewName("fileUpload");//模板文件名
return modelAndView;
}
@RequestMapping("/upload")
public String upload(MultipartFile uploadFile, HttpServletRequest request){
String realPath=request.getSession().getServletContext().getRealPath("/uploadFile");
String format=simpleDateFormat.format(new Date());
File flodeer=new File(realPath+format); //生成的文件夹名称
if (!flodeer.isDirectory()){ //判断文件夹是否存在
flodeer.mkdir();
}
String oldName=uploadFile.getOriginalFilename();
String newName= UUID.randomUUID().toString()+oldName.substring(oldName.lastIndexOf("."),oldName.length()); //文件名称
try {
uploadFile.transferTo(new File(flodeer,newName));//文件上传方法
String filePath=request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+"/uploadFile/"+format+newName;
return filePath;
}catch (IOException e){
e.printStackTrace();
}
return "上传失败";
}
}
结果:
如果开发者需要对图片上传的细节进行配置,配置如下:
spring.servlet.multipart.enabled=true #是否开启文件上传支持,默认为TRUE
spring.servlet.multipart.file-size-threshold=0 #文件写入磁盘的阈值,默认为0
spring.servlet.multipart.location=E:\\temp #上传文件的临时地址
spring.servlet.multipart.max-file-size=1MB #上传文件的单个文件最大大小,默认为1MB
spring.servlet.multipart.max-request-size=10MB #多个文件上传的总大小,默认为10MB
spring.servlet.multipart.resolve-lazily=false #文件上传是否延迟解析,默认为FALSE
(2)多文件上传
多文件上传的HTML和单文件上传HTML相同,请参考单页面的HTML
<form action="/chapter02/file/uploads" method="post" enctype="multipart/form-data">
<input type="file" name="uploadFiles" value="请选择文件" multiple> //multiple 是多选文件的标识
<input type="submit" value="多文件上传">
</form>
后端方法代码如下:
//多文件上传
@RequestMapping("/uploads")
public String uploads(MultipartFile[] uploadFiles, HttpServletRequest request){
//遍历uploadFiles数组分别存储
}
4、注解@ControllerAdvice
顾名思义@ControllerAdvice就是@Controller的加强版,主要用于处理全局数据,一般搭配@ExceptionHandler、@ModelAttribute及InitBinder使用
(1)全局异常处理:对于之前说到的文件上传,就很有可能出现异常,比如如果用户限制的文件大小,就会出现异常,此时就可以通过@ControllerAdvice结合@ ExceptionHandler定义全局异常捕获机智:代码如下:
@ControllerAdvice
public class CustomerExcpetionhandler {
@ExceptionHandler(MaxUploadSizeExceededException.class)
public void UploadException(MaxUploadSizeExceededException e, HttpServletResponse response)throws IOException {
response.setContentType("text/html;charset=utf-8");
PrintWriter out=response.getWriter();
out.write("上传文件大小超过限制");
out.flush();
out.close();
}
}
只需在系统里面定义CustomerExceptionHandler类,然后加上@ControllerAdvice注解即可,当系统启动时,该类将自动被扫描到容器中,然后定义uploadException方法,在该方法上添加@ExceptionHandler注解,其中定义的MaxUploadSizeExceededException.class表明该方法用了处理MaxUploadSizeExceededException类型的异常,如果想该方法处理所有类型的异常只需将其改成Exception即可
(2)添加全局数据
@ControllerAdvice十一个全局数据处理组件,因此也可以在@ControllerAdvice中配置全局数据,使用@ModelAttribute注解进行配置
ControllerAdvice
public class GlobalConfig {
@ModelAttribute(value = "info")
public Map<String,String> userInfo(){
HashMap<String,String> map=new HashMap<>();
map.put("username","罗贯中");
map.put("gender","男");
return map;
}
}
代码解析:
在全局配置中添加userInfo方法,返回一个map,该方法有一个注解@ModelAttribute,其中的value代表的是返回数据的key,而方法的返回值是返回数据的value
controller代码如下:
@GetMapping("/hello")
@ResponseBody
public void hello(Model model){
Map<String,Object> map=model.asMap();
Set<String> keyset=map.keySet();
Iterator<String> iterator=keyset.iterator();
while (iterator.hasNext()){
String key=iterator.next();
Object value=map.get(key);
System.out.println("key:"+key+"<<<<<<<"+"value"+value);
}
}
结果如下:
key:info<<<<<<<value{gender=男, username=罗贯中}
(3)请求参数预处理
@ControllerAdvice结合@InitBinder还能实现请求参数预处理,即将表单中的数据绑定到实体类上时进行一些额外处理:
两个实体类:
public class Book {
private Integer id;
private String name;
private String author;
}
public class User {
private String name;
private String address;
}
在Controller上需要接受两个实体类的数据,Controller中的方法如下:
@GetMapping
@ResponseBody
public String Book(Book b,User u){
return book.toString()+">>"+u.toString();
}
但是由于两个实体类中的name属性名称相同,所以避免搞混淆:
@GetMapping
@ResponseBody
public String Book(@ModelAttribute("b") Book b,@ModelAttribute("u") User u){
return book.toString()+">>"+u.toString();
}
然后再配置@ControllerAdvice
@ControllerAdvice
public class GlobalConfig{
@InitBinder("b") //处理方法中第一个@ModelAttribute参数 a
public void init(WebDataBinder binder){
binder.setFieldDefaultPrefix("b.");
}
@InitBinder("u") //处理方法中第二个@ModelAttribute参数 u
public void init(WebDataBinder binder){
binder.setFieldDefaultPrefix("u.");
}
}
5、自定义错误页面
在程序中总会有些错误发生,比如404、403、500等,这些错误系统给了特定的页面(HTML)或者错误的信息(JSON),SpringBoot中的错误默认是有BasicErrorController类来处理的,该类的核心有两个:
@RequestMapping(
produces = {"text/html"}
)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
HttpStatus status = this.getStatus(request);
Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML)));
response.setStatus(status.value());
ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);
return modelAndView != null ? modelAndView : new ModelAndView("error", model);
}
@RequestMapping
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
HttpStatus status = this.getStatus(request);
if (status == HttpStatus.NO_CONTENT) {
return new ResponseEntity(status);
} else {
Map<String, Object> body = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
return new ResponseEntity(body, status);
}
}
其中errorHtml是用来处理HTML的错误页面,error是用来处理JSON的错误信息,是返回页面还是返回JSON,则需要看请求的Accept参数,在errorHTML中利用resolveErrorView方法来获取一个错误的视图ModelAndview,而 resolveErrorView方法的调用来源于DefaultErrorViewResolver类中
DefaultErrorViewResolver类是SpringBoot中默认的错误信息视图解析器,源码如下:
public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
private static final Map<Series, String> SERIES_VIEWS;
private ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final TemplateAvailabilityProviders templateAvailabilityProviders;
private int order = 2147483647;
private ModelAndView resolve(String viewName, Map<String, Object> model) {
String errorViewName = "error/" + viewName;
TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext);
return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model);
}
static {
Map<Series, String> views = new EnumMap(Series.class);
views.put(Series.CLIENT_ERROR, "4xx");
views.put(Series.SERVER_ERROR, "5xx");
SERIES_VIEWS = Collections.unmodifiableMap(views);
}
}
从源码中可以看出SpringBoot默认是在error目录下查看404、500等错误页面,如果找不到会回到errorHTML方法中,然后使用 error作为默认的错误页面视图名,如果error的视图找不到,就会出现以下界面:
配置(只需要在templates目录下创建错误页面)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
404
</body>
</html>