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 "上传失败";
	}
}

结果:

springboot filter 请求返回日志 springboot返回modelandview_html

如果开发者需要对图片上传的细节进行配置,配置如下:

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的视图找不到,就会出现以下界面:

springboot filter 请求返回日志 springboot返回modelandview_html_02

配置(只需要在templates目录下创建错误页面)

springboot filter 请求返回日志 springboot返回modelandview_文件上传_03

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
404
</body>
</html>