Spring-MVC框架之文件上传&拦截器&异常处理
文件上传
springMVC框架也实现了文件上传功能,底层是融合了commons-fileupload这个功能,实现起来非常简单易用。
前端页面将form表单的method设置为post,enctype属性设置为multipart/form-data,并且在jsp中的input标签中type属性设置为file,
平时我们使用的form表单是纯文本,也就是enctype=application/x-www-form-urlencoded这个类型,这种类型,在内容格式是以键值对形式进行传递,当enctype变为multipart/form-data的时候正文内容就被分为多个部分,同时request.getParameter将失效。
此时我们用到了spring整合后的fileupload功能,首先导入依赖jar包
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>javaee</groupId>
<artifactId>day07_springMVC_fileUpload</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.3</version>
</dependency>
</dependencies>
</project>
然后开始配置spring-mvc文件,特别注意文件上传的bean配置id必须为multipartResolver,否则会报空指针异常
spring-mvc.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:contxt="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!--开启注解扫描-->
<contxt:component-scan base-package="com.dyh"/>
<!--开启json格式-->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<!--json数据乱码-->
<value>text/html;charset=UTF-8</value>
<value>application/json;charset=UTF-8</value>
</list>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--设置默认编码格式-->
<property name="defaultEncoding" value="UTF-8"/>
<!--设置单个文件最大上传大小,以字节位单位-->
<property name="maxUploadSizePerFile" value="5242880"/>
<!--设置所有文件最大上传大小,以字节位单位-->
<property name="maxUploadSize" value="5242880"/>
</bean>
</beans>
写一个controller去进行上传文件
controller
package com.dyh.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.UUID;
@Controller
@RequestMapping("/upload")
public class FileUpload {
@RequestMapping("/upload01")
@ResponseBody //返回的类型是json字符串
public String uploadFile01(String name, MultipartFile uploadFile) throws Exception {
// 输出上传的用户名和文件对象
System.out.println(name);
// 如果文件的结尾不是.txt结尾则抛出异常,返回到异常页面
if (!uploadFile.getOriginalFilename().endsWith(".txt")) throw new Exception();
// 生成随机文件名
String newName = UUID.randomUUID() + uploadFile.getOriginalFilename();
// 上传文件到指定路径
uploadFile.transferTo(new File("D:/test/" + newName));
return "上传成功!";
}
}
在这里我们有使用jsp页面进行测试,我是通过postman进行测试
运行结果
上传的是图片的时候就无法上传。
下面在附上一张上传成功的截图
如果是多文件上传,post表单name相同是,controller使用数组接收即可
package com.dyh.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.util.UUID;
@Controller
@RequestMapping("/upload")
public class FileUpload {
@RequestMapping("/upload01")
@ResponseBody //返回的类型是json字符串
public String uploadFile01(String name, MultipartFile[] uploadFile) throws Exception {
for(MultipartFile file : uploadFile){
// 如果文件的结尾不是.txt结尾则抛出异常,返回到异常页面
if (!file.getOriginalFilename().endsWith(".txt")) throw new Exception();
// 生成随机文件名
String newName = UUID.randomUUID() + file.getOriginalFilename();
// 上传文件到指定路径
file.transferTo(new File("D:/test/" + newName));
}
return "上传成功!";
}
}
拦截器
关于拦截器,从功能上来讲,它和过滤器是是一样的,本质上都用于队处理器进行预处理和后处理。但是从底层来说,他们的实现原理还是有区别的,也使面试时经常会问到的问题。
名称 | 说明 |
---|---|
过滤器 | 是servlet规范中的一部分,在任何javaweb工程中都可以使用,在url-pattern配置了/*后,可以对所有要访问的资源进行拦截 |
拦截器 | 是springMVC框架提供的,只有在springMVC中才可以使用,一般只对控制器中的方法进行拦截,但是通过一定手段也可以拦截所有资源 |
我们只需要实现HandlerInterceptor接口, 里面的方法都被default修饰过了,所以可以选择性实现里面的preHandle()(前置拦截),postHandle()(返回前拦截),afterCompletion()(后置拦截)方法,用法和AOP有些相似,如果在执行中出现异常则postHandle()不执行。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
手动写一个拦截器
package com.dyh.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
// 前置拦截
@Override
public boolean preHandle(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response, Object handler) {
System.out.println("模拟前置拦截");
return true;
}
// 返回拦截
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
System.out.println("模拟返回前拦截");
}
// 后置拦截
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("模拟后置拦截");
}
}
随后在配置文件中加上这么一段配置
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--对所有方法进行拦截-->
<mvc:mapping path="/**"/>
<!--配置自己写的拦截器-->
<bean class="com.dyh.interceptor.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
测试的controller
package com.dyh.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class TestController {
/**
* 监听器快速入门
*/
@ResponseBody
@RequestMapping("/test")
public void test() {
System.out.println("模拟执行某方法。。。");
}
}
访问后执行结果
可以看到我们对这个方法进行了拦截处理,我们再执行过程中手写一个异常,测试下返回前拦截在遇到异常后是否会执行。
/**
* 监听器快速入门
*/
@ResponseBody
@RequestMapping("/test")
public void test() throws Exception {
System.out.println("模拟执行某方法。。。");
//手动抛出异常
throw new Exception();
}
运行结果
这里和AOP一样,返回前的增强和返回前的拦截在遇到异常后都不执行。
异常处理
我们在遇到异常时,不能总是给用户返回404,405,500等错误页面,这样用户体验是非常差劲的。为了避免这种情况,spring也拥有自己的异常处理机制。
spring总共有三种异常处理机制
1:使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver
2:实现Spring的异常处理接口HandlerExceptionResolver 自定义异常处理器
3:使用@ExceptionHandler注解实现异常处理
简单异常处理器
第一种我们可以在配置文件中写入这么一段配置,用来配置异常情况下的处理方式
<!--配置简单映射异常处理器-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--默认错误的跳转页面-->
<property name="defaultErrorView" value="error"/>
<!--多条错误页面设置-->
<property name="exceptionMappings">
<map>
<!--配置异常的全限定类名和返回的错误页面-->
<entry key="java.lang.RuntimeException" value="error2"/>
<entry key="java.lang.ClassCastException" value="error3"/>
</map>
</property>
</bean>
这样在出现指定错误时,将会跳转到相应的错误页面。但是缺点就是不灵活,不能给页面传递一些错误信息。
自定义异常处理器
第二种方式相比第一种要自由的多,我们可以实现spring提供的HandlerExceptionResolver接口,实现resolveException()方法,返回值是ModelAndView,参数有HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex,可以自由的在里面进行异常类型判断,进行页面的跳转和错误信息的传递。
自定义异常类
package com.dyh.exception;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义异常类
*/
//通过注解将自定义异常类交给spring管理,如果发现异常将使用这种方式进行返回。
@Component("myException")
public class MyException implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
ModelAndView modelAndView = new ModelAndView();
if (e instanceof ClassCastException){
modelAndView.setViewName("error");
modelAndView.addObject("errorInfo","类型转换异常");
return modelAndView;
}else if (e instanceof RuntimeException){
modelAndView.setViewName("error2");
modelAndView.addObject("errorInfo","运行时异常");
return modelAndView;
}else {
modelAndView.setViewName("error3");
modelAndView.addObject("errorInfo","其他异常");
return modelAndView;
}
}
}
通过这种方式我们可以通过各类异常返回异常信息,和异常跳转的错误页面。
@ExceptionHandler注解
第三种使用@ExceptionHandler注解实现异常处理,自己写一个自定义异常类,随后加上@ExceptionHandler注解声明这是一个异常类,随后如果哪个controller需要处理异常,就继承这个自定义异常。
package com.dyh.exception;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义异常类
*/
@ExceptionHandler
public class MyException{
public String ex(HttpServletRequest request, Exception ex) {
request.setAttribute("ex", ex);
// 根据不同错误转向不同页面
if(ex instanceof RuntimeException) {
return "error";
}else if(ex instanceof ClassCastException) {
return "error1";
} else {
return "error2";
}
}
}
使用这种方法简单,扩展性好,但是缺点也同样明显,在异常处理时,不能捕获除这些异常以外的数据。所以不建议使用。
总结
- 文件上传:使用form表单的post请求进行提交,form表单格式调整为多部分form表达。在配置文件上传的bean配置时,id必须写为multipartResolver,否则回报空指针异常。
多文件上传时将前端文件类型的name设置为相同的名字,后端使用数组接收,循环遍历上传即可。 - 拦截器:自定义拦截器,需要继承HandlerInterceptor 接口,在xml进行配置拦截器,可以配置多个映射路径和排除路径,也可以配置多个拦截器,形成拦截链。
- 异常处理:三种异常处理方式,简单异常处理器,自定义异常处理器,注解配置异常。
简单异常处理器:指定各类异常跳转的错误页面,不灵活,不能自定义异常信息。
自定义异常处理器:同样可以根据异常类型返回不同错误界面,并且可以设置自定义异常提示信息
@ExceptionHandler注解:自己写异常类,并且加上ExceptionHandler注解,当controller需要使用异常处理机制时继承该类。