1、文件上传
Spring MVC 为文件上传提供了直接的支持,这种支持是通过即插即用的 MultipartResolver 实现的。Spring 用Jakarta Commons FileUpload 技术实现了一个MultipartResolver 实现类:CommonsMultipartResovler
Spring MVC 上下文中默认没有装配 MultipartResovler,因此默认情况下不能处理文件的上传工作,如果想使用 Spring 的文件上传功能,需现在上下文中配置 MultipartResolver
文件上传的步骤:
1、在工程下添加jar包:commons-fileupload-1.2.1.jar 和 commons-io-2.0.jar
2、配置文件配置MultipartResolver(CommonsMultipartResovler)
3、方法入参使用 MultipartFile 接收
1.1 单文件上传
1.1.1 添加jar包到工程中
添加 commons-fileupload-1.2.1.jar 和 commons-io-2.0.jar 到lib中
1.1.2 配置 CommonsMultipartResovler
defaultEncoding: 必须和用户 JSP 的 pageEncoding 属性 一致,以便正确解析表单的内容;
为了让 CommonsMultipartResovler 正确工作,必须先将 Jakarta Commons FileUpload 及 Jakarta Commons io的类包添加到类路径下(1.1.1已经做了该步骤)。
<!--配置文件上传的解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="defaultEncoding" value="UTF-8"/>
<property name="maxUploadSize" value="1024000"/>
</bean>
1.1.3 文件上传方法入参以MultipartFile 接收
package com.springmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
@Controller
@RequestMapping("multipartFile")
public class MultipartFileController {
//单个文件上传
@RequestMapping("uploadFile")
public String uploadFile(@RequestParam("file") MultipartFile file,
@RequestParam("desc") String desc) throws Exception {
System.out.println("desc:"+desc);
String filename = file.getOriginalFilename();
System.out.println("文件名称:"+filename);
System.out.println("文件大小:"+file.getSize());
//把文件保存到其他地方
//保存文件的路径
String savePath = "D:/upload/";
//得到文件目录
File fileFloader = new File(savePath);
if(!fileFloader.exists()){//目录不存在,就创建
fileFloader.mkdirs();
}
FileOutputStream outputStream = new FileOutputStream(new File(savePath,filename));
IOUtils.copy(file.getInputStream(),outputStream);
return "success";
}
}
1.1.4 jsp页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/multipartFile/uploadFile" method="post" enctype="multipart/form-data">
文件:<input type="file" name="file"/>
描述:<input type="text" name="desc"/>
<input type="submit"/>
</form>
</body>
</html>
1.2 多文件上传
多文件上传也需要jar包和CommonsMultipartResovler,不同的是jsp页面和Controlelr方法,controller方法中,接收多个文件需要使用:@RequestParam("files") MultipartFile[] file
1.2.1 Controller方法
//多文件上传
@RequestMapping("uploadList")
public String uploadList(@RequestParam("files") MultipartFile[] files) throws IOException {
//保存文件的路径
String savePath = "D:/upload/";
//得到文件目录
File fileFloader = new File(savePath);
System.out.println(fileFloader.isDirectory());
if(!fileFloader.exists()){//目录不存在,就创建
fileFloader.mkdirs();
}
System.out.println(fileFloader.isDirectory());
//判断file数组不能为空并且长度大于0
if(files != null && files.length>0){
//循环遍历每个文件
for (MultipartFile file:files) {
String filename = file.getOriginalFilename();
System.out.println("文件名称:"+filename);
System.out.println("文件大小:"+file.getSize());
if(!file.isEmpty()){//判断文件不为空
// 转存文件
file.transferTo(new File(savePath+filename));
}
}
}
return "success";
}
1.2.2 jsp页面
<h2>上传多个文件实例</h2>
<form action="${pageContext.request.contextPath}/multipartFile/uploadList" method="post" enctype="multipart/form-data">
<p>选择文件:<input type="file" name="files"></p>
<p>选择文件:<input type="file" name="files"></p>
<p><input type="submit" value="提交"></p>
</form>
2、自定义拦截器
2.1 概述
Spring MVC 也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口。该接口有如下三个方法:
preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对
用户请求request进行处理。
afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
2.2 自定义拦截器
2.2.1 定义拦截器类
自定义的拦截器必须实现接口:HandlerInterceptor,重写该接口的三个方法
package com.springmvc;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//自定义拦截器
public class FirstInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
System.out.println(o);
System.out.println("业务处理方法执行之前,先执行该preHandle方法");
return true;//返回true就会执行其他的拦截器的preHandle方法,或者执行业务处理方法
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {
System.out.println(o);
System.out.println("业务处理方法执行后,响应页面之前,先执行该postHandle方法");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception {
System.out.println(o);
System.out.println("DispatcherServlet完全执行后,执行该afterCompletion方法,进行释放资源的操作");
}
}
2.2.2 配置拦截器
在springmvc.xml中配置拦截器:
<!--拦截器的配置-->
<mvc:interceptors>
<!--该拦截器拦截所有资源-->
<bean class="com.springmvc.FirstInterceptor"/>
</mvc:interceptors>
2.2.3 jsp页面和后台方法测试
jsp添加一个测试连接,后台添加一个测试方法
<h2>测试拦截器</h2>
<a href="${pageContext.request.contextPath}/multipartFile/testInterceptors">点击执行</a>
@RequestMapping("testInterceptors")
public String testInterceptors(){
System.out.println("--------------业务处理方法执行--------------");
return "success";
}
测试,打印如下:
com.springmvc.MultipartFileController@14c45f6
业务处理方法执行之前,先执行该preHandle方法
--------------业务处理方法执行--------------
com.springmvc.MultipartFileController@14c45f6
业务处理方法执行后,响应页面之前,先执行该postHandle方法
com.springmvc.MultipartFileController@14c45f6
DispatcherServlet完全执行后,执行该afterCompletion方法,进行释放资源的操作
2.3 多个拦截器的执行顺序,以及拦截器的其他配置
2.3.1 再定义一个拦截器
package com.springmvc;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//自定义拦截器
public class SecondInterceptor implements HandlerInterceptor {
/**
* 该方法在目标方法之前被调用.
* 若返回值为 true, 则继续调用后续的拦截器和目标方法.
* 若返回值为 false, 则不会再调用后续的拦截器和目标方法.
*
* 可以考虑做权限. 日志, 事务等.
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object o) throws Exception {
System.out.println(222+"==业务处理方法执行之前,先执行该SecondInterceptor.preHandle方法");
return true;//返回true就会执行其他的拦截器的preHandle方法,或者执行业务处理方法
}
/**
* 调用目标方法之后, 但渲染视图之前.
* 可以对请求域中的属性或视图做出修改.
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object o, ModelAndView modelAndView) throws Exception {
System.out.println(222+"==业务处理方法执行后,响应页面之前,先执行该SecondInterceptor.postHandle方法");
}
/**
* 渲染视图之后被调用. 释放资源
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object o, Exception e) throws Exception {
System.out.println(222+"==DispatcherServlet完全执行后,执行该SecondInterceptor.afterCompletion方法,进行释放资源的操作");
}
}
2.3.2 配置拦截器
拦截器还可以配置拦截哪些路径,或者配置不拦截哪些路径,如下:
<!--拦截器的配置-->
<mvc:interceptors>
<!--该拦截器拦截所有资源-->
<bean class="com.springmvc.FirstInterceptor"/>
<!--配置拦截指定路径的拦截器-->
<mvc:interceptor>
<!--<mvc:exclude-mapping path=""/> 可以配置不拦截哪些路径-->
<!--表示该拦截器只拦截path指定的路径-->
<mvc:mapping path="/multipartFile/testInterceptors"/>
<bean class="com.springmvc.SecondInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
2.3.3 页面和后台增加测试
<a href="${pageContext.request.contextPath}/multipartFile/testInterceptors222">点击执行222</a>
@RequestMapping("testInterceptors222")
public String testInterceptors222(){
System.out.println("--------------业务处理方法执行--------------");
return "success";
}
1、点击新增的/multipartFile/testInterceptors222,只有第一个烂机器会拦截该路径,所以打印如下:
com.springmvc.MultipartFileController@7c202499
业务处理方法执行之前,先执行该preHandle方法
--------------业务处理方法执行--------------
com.springmvc.MultipartFileController@7c202499
业务处理方法执行后,响应页面之前,先执行该postHandle方法
com.springmvc.MultipartFileController@7c202499
DispatcherServlet完全执行后,执行该afterCompletion方法,进行释放资源的操作
2、点击之前的/multipartFile/testInterceptors,这个会被两个拦截器都拦截,所以打印如下:
com.springmvc.MultipartFileController@7c202499
业务处理方法执行之前,先执行该preHandle方法
222==业务处理方法执行之前,先执行该SecondInterceptor.preHandle方法
--------------业务处理方法执行--------------
222==业务处理方法执行后,响应页面之前,先执行该SecondInterceptor.postHandle方法
com.springmvc.MultipartFileController@7c202499
业务处理方法执行后,响应页面之前,先执行该postHandle方法
222==DispatcherServlet完全执行后,执行该SecondInterceptor.afterCompletion方法,进行释放资源的操作
com.springmvc.MultipartFileController@7c202499
DispatcherServlet完全执行后,执行该afterCompletion方法,进行释放资源的操作
2.3.4 多个拦截器的执行顺序
当有多个拦截器拦截同一个请求时,那么拦截器的执行顺序是怎么样的呢?
根据拦截器配置的顺序做对比,在业务方法之前,拦截器是顺序执行的;在业务方法之后,拦截器是倒序执行的。可以从上面的打印结果看的出来。
疑问:如果第二拦截的preHandle方法,返回的是false,那么拦截器是怎么执行的呢?
3、异常处理
3.1 HandlerExceptionResolver
Spring MVC 通过 HandlerExceptionResolver 处理程序的异常,包括 Handler 映射、数据绑定以及目标方法执行时发生的异常。
SpringMVC 提供的 HandlerExceptionResolver 的实现类如下:
DispatcherServlet 默认装配的 HandlerExceptionResolver:
1、没有使用 <mvc:annotation-driven/> 配置:
随便在哪个处理方法打个断点,找到doDispatch方法,可以查看:
2、使用了 <mvc:annotation-driven/> 配置(一般都是会配上):
从上面可以看出,当配置文件中存在:<mvc:annotation-driven/> 的配置,那么DispatcherServlet默认装配的异常处理器是:ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver和DefaultHandlerExceptionResolver。
3.1.1 ExceptionHandlerExceptionResolver
ExceptionHandlerExceptionResolver:主要处理 Handler(处理器) 中用 @ExceptionHandler 注解定义的方法。
@ExceptionHandler 注解定义的方法优先级问题:例如,发生的是NullPointerException,但是声明的异常有RuntimeException 和 Exception,此时会根据异常的最近继承关系找到继承深度最浅的那个 @ExceptionHandler注解方法,即标记了 RuntimeException 的方法。
ExceptionHandlerMethodResolver 内部若找不到@ExceptionHandler 注解的话,会找 @ControllerAdvice 标注的类 中的@ExceptionHandler 注解方法。(全局异常处理实现方式)
3.1.1.1 当前处理器的异常处理器
1、新建一个处理器Controller
package com.springmvc;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("exception")
public class TestExceptionController {
/**
* 1、该方法只能处理本处理器中的异常,其他Controller中的异常处理不了
* 2、在 @ExceptionHandler 修饰的方法的入参中 加入Exception作为参数,该参数对应的是产生的异常对象
* 3、@ExceptionHandler 修饰的方法如果想把异常返回到页面上,那么返回值必须是 ModelAndView,然后把异常添加到该对象中,(使用Map无效)
* ArithmeticException : 表示是数学异常
*/
@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleException(Exception ex){
System.out.println("出异常了:"+ex);
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("exception",ex);
return modelAndView;
}
@ExceptionHandler({RuntimeException.class})
public ModelAndView handleException2(Exception ex){
System.out.println("[====出异常了====]:"+ex);
ModelAndView modelAndView = new ModelAndView("error");
modelAndView.addObject("exception",ex);
return modelAndView;
}
@RequestMapping("testExceptionHandlerExceptionResolver")
public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i){
int t = 100/i;
return "success";
}
}
上面定义了两个被注解@ExceptionHandler 修饰的,用来处理异常的方法,方法的入参就是产生的异常对象,方法的返回值是ModelAndView(为了把异常信息返回给浏览器,只能使用ModelAndView作为返回值);两个方法分别处理的是数学异常和运行时异常,他们是有优先级的,当出现的是数学异常(/by zero)时,处理数学异常的方法会执行。需要注意的是:这两个方法只能处理当前处理器中的方法出现的异常。
2、定义页面超链接
<br/>
<a href="${pageContext.request.contextPath}/exception/testExceptionHandlerExceptionResolver?i=10">ExceptionHandlerExceptionResolver</a>
3、定义个error.jsp页面
定义一个错误页面,用来显示异常信息:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>失败页面</title>
</head>
<body>
<h2>ERROR PAGE</h2>
异常信息:${exception}
</body>
</html>
启动项目,访问地址:http://localhost:8080/Spring4MVC_02_war_exploded/exception/testExceptionHandlerExceptionResolver?i=0
这个地址请求到后台,就会导致除数为0,会出现异常,所以会执行处理数学异常的方法,并且会把异常信息返回到error.jsp,
后台打印:出异常了:java.lang.ArithmeticException: / by zero
error.jsp页面信息:
ERROR PAGE
异常信息:java.lang.ArithmeticException: / by zero
3.1.1.2 全局异常处理器
3.1.1.1 中的异常处理方法,只能处理所在的处理器中的方法产生的异常。这样的话每个处理器都要写这样的方法,会比较繁琐。这个时候,我们看到前面有一句话:ExceptionHandlerMethodResolver 内部若找不到@ExceptionHandler 注解的话,会找 @ControllerAdvice 标注的类 中的@ExceptionHandler 注解方法。 其实这就是实现 全局异常处理器 的方式。
1、定义全局异常处理器
package com.springmvc;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
/**
* 全局异常处理器
* @ControllerAdvice : 该注解修饰的,表明这是一个全局异常处理类,该类中可以定义多个异常处理方法,规则和本地异常处理一样
*/
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理所有的异常
*/
@ExceptionHandler
public ModelAndView handlerAllException(Exception exception){
System.out.println("全局异常处理器,捕获的异常是:"+exception);
ModelAndView modelAndView = new ModelAndView("error");
if(exception instanceof ArithmeticException){//如果是数学异常就进行相关逻辑
System.out.println("这么蠢啊,除数不能为0都不知道");
modelAndView.addObject("exception",exception+" 这么蠢啊,除数不能为0都不知道");
}else{
modelAndView.addObject("exception",exception);
}
return modelAndView;
}
}
2、可以删除之前在TestExceptionController中定义的异常执行方法,然后进行测试,也可以进行异常的处理
3.1.2 ResponseStatusExceptionResolver
ResponseStatusExceptionResolver也是 HandlerExceptionResolver 的一个实现类。它的作用就是可以对自定义的异常进行个性化的定义返回状态码和错误消息。也就是在异常及异常父类中找到 @ResponseStatus 注解,然后使用这个注解的属性进行处理。
一般情况,如果我们没有对自定义的异常进行返回消息和状态码的定制,那么SpringMVC会自行处理,比如如果后台方法出现异常,一般我们的页面看到的就是500错误页面,如下:
现在我们可以在自定义的注解上面加上注解:@ResponseStatus,指定返回状态码和错误消息
1、自定义异常
在自定义的异常类上加上注解:@ResponseStatus
package com.springmvc;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
//自定义异常
@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户名或密码错误,请稍后再试")
public class PasswordErrorException extends RuntimeException {
}
若在处理器方法中抛出了上述异常:若ExceptionHandlerExceptionResolver 不解析述异常。由于触发的异常 PasswordErrorException 带有@ResponseStatus注解。因此会被ResponseStatusExceptionResolver 解析到。最后响应HttpStatus.FORBIDDEN 代码给客户端。HttpStatus.FORBIDDEN 代表响应码403,表示禁止访问。
2、controller方法
@RequestMapping("testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("password") String password){
if("123".equals(password)){
throw new PasswordErrorException();
}
return "success";
}
3、页面index.jsp添加连接
<br/>
<a href="${pageContext.request.contextPath}/exception/testResponseStatusExceptionResolver?password=123">ResponseStatusExceptionResolver</a>
测试,浏览器的错误显示如下(测试的时候需要注释掉全局异常处理器,不然异常会被全局异常处理所处理):
3.1.3 DefaultHandlerExceptionResolver
DefaultHandlerExceptionResolver 也是 HandlerExceptionResolver 的实现类,它的作用就是对Spring的特定异常进行处理:比如NoSuchRequestHandlingMethodException、HttpRequestMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException等。
3.1.4 SimpleMappingExceptionResolver
如果希望对所有异常进行统一处理,可以使用 SimpleMappingExceptionResolver,它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。(就是一旦发生异常,跳到一个统一的错误页面,进行异常信息的友好提示)。对它里面的属性进行配置,可以处理的不同类型的异常,跳转到不同的错误页面。
在没有进行SimpleMappingExceptionResolver配置的时候,如果系统出现,异常会直接在当前请求路径进行显示:
1、配置SimpleMappingExceptionResolver
<!--使用 SimpleMappingExceptionResolver 来映射异常-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!--配置异常映射的路径-->
<property name="exceptionMappings">
<props>
<!--表示:出现了ArrayIndexOutOfBoundsException 异常就挑到error.jsp页面-->
<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
</props>
</property>
</bean>
2、构建一个测试索引越界的异常
@RequestMapping("testSimpleMappingExceptionResolver")
public String testSimpleMappingExceptionResolver(@RequestParam("index") int index){
int[] arr = new int[10];
System.out.println(arr[index]);//如果前端传的值index超出数组长度的下标,就会出现索引越界异常
return "success";
}
3、index.jsp添加测试的超链接
<br/>
<a href="${pageContext.request.contextPath}/exception/testSimpleMappingExceptionResolver?index=100">SimpleMappingExceptionResolver</a>
4、测试
注意:测试的时候可以闲关掉全局异常处理,不然会被全局异常捕获到,进行对应的逻辑。
此时再次测试,会跳到error.jsp页面,同时也可以打印异常信息,因为我们在error.jsp页面使用了:${exception}来展示异常信息,exception是默认的,可以在配置SimpleMappingExceptionResolver的时候进行修改:
<property name="exceptionAttribute" value="ex"/>
ERROR PAGE
异常信息:java.lang.ArrayIndexOutOfBoundsException: 100
4、SpringMVC的流程分析
4.1 图示流程