目录
一、拦截器
1.1 应用场景
~~~~~~ 如果我们想在多个Handler方法执行之前或者之后都进行一些处理,甚至某些情况下需要拦截掉,不让Handler方法执行。那么可以使用SpringMVC为我们提供的拦截器。
1.2 拦截器和过滤器的区别
~~~~~~ 过滤器是在Servlet执行之前或者之后进行处理。而拦截器是对Handler(处理器)执行前后进行处理。如图:
1.3 创建并配置拦截器
①创建类实现HandlerInterceptor接口
@Component
public class FirstInterceptorimplements HandlerInterceptor {
}
②实现方法
@Component
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor--->preHandle");
//返回值代表是否放行,如果为true则放行,如果为fasle则拦截,目标方法执行不到
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor--->postHandle");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("FirstInterceptor--->afterCompletion");
}
③配置拦截器
<!--配置拦截器-->
<mvc:interceptors>
<!-- bean和ref标签所配置的拦截器默认对DispatcherServlet处理的所有的请求进行拦截-->
<!--<bean class="com.lx.interceptor.FirstInterceptor"></bean>-->
<!--<ref bean="firstInterceptor"></ref>-->
<mvc:interceptor>
<!--配置需要拦截的请求的请求路径, /**表示所有请求-->
<mvc:mapping path="/**"/>
<!--配置需要排除拦截的请求的请求路径-->
<mvc:exclude-mapping path="/abc"/>
<!--配置使用哪个拦截器-->
<ref bean="firstInterceptor"/>
</mvc:interceptor>
<ref bean="secondInterceptor"/>
</mvc:interceptors>
1.4 拦截器方法及参数详解
- preHandle方法会在Handler方法执行之前执行,我们可以在其中进行一些前置的判断或者处理。
- postHandle方法会在Handler方法执行之后执行,我们可以在其中对域中的数据进行修改,也可以修改要跳转的页面。
- afterCompletion方法会在最后执行,这个时候已经没有办法对域中的数据进行修改,也没有办法修改要跳转的页面。我们在这个方法中一般进行一些资源的释放。
preHandle()按照配置的顺序执行,而postHandle()和afterCompletion()按照配置的反序执行
1.5 多拦截器执行顺序
如果我们配置了多个拦截器,拦截器的顺序是按照配置的先后顺序的。
这些拦截器中方法的执行顺序如图(preHandler都返回true的情况下):
如果拦截器3的preHandle方法返回值为false。执行顺序如图:
-
只有所有拦截器都放行了,postHandle方法才会被执行。
-
只有当前拦截器放行了,当前拦截器的afterCompletion方法才会执行。
二、统一异常处理
~~~~~~ 我们在实际项目中Dao层和Service层的异常都会被抛到Controller层。但是如果我们在Controller的方法中都加上异常的try…catch处理也会显的非常的繁琐。
~~~~~~ 所以SpringMVC为我们提供了统一异常处理方案。可以把Controller层的异常进行统一处理。这样既提高了代码的复用性也让异常处理代码和我们的业务代码解耦。
~~~~~~ 一种是实现HandlerExceptionResolver接口的方式,一种是使用@ControllerAdvice注解的方式。
2.1 HandlerExceptionResolver
①实现接口
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
}
②重写方法
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
//如果handler中出现了异常,就会调用到该方法,我们可以在本方法中进行统一的异常处理。
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
//获取异常信息,把异常信息放入域对象中
String msg = ex.getMessage();
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg",msg);
//跳转到error.jsp
modelAndView.setViewName("/WEB-INF/page/error.jsp");
return modelAndView;
}
}
③注入容器
~~~~~~ 可以使用注解注入也可以使用xml配置注入。这里使用注解注入的方式。在类上加@Component注解,注意要保证类能被组件扫描扫描到。
@Component
public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
//....省略无关代码
}
2.2 @ControllerAdvice(重要)
①创建类加上@ControllerAdvice注解进行标识
@ControllerAdvice
public class MyControllerAdvice {
}
②定义异常处理方法
定义异常处理方法,使用@ExceptionHandler标识可以处理的异常。
//将当前类标识为异常处理的组件
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(ArithmeticException.class)
public String handleExecption(Throwable ex, Model model){
model.addAttribute("ex",ex);
return "error";
}
}
③注入容器
~~~~~~ 可以使用注解注入也可以使用xml配置注入。这里使用注解注入的方式。在类上加@ControllerAdvice注解,注意要保证类能被组件扫描扫描到。
2.3 总结
~~~~~~ 我们在实际项目中一般会选择使用@ControllerAdvice 来进行异常的统一处理。
~~~~~~ 因为如果在前后端不分离的项目中,异常处理一般是跳转到错误页面,让用户有个更好的体验。而前后端分离的项目中,异常处理一般是把异常信息封装到Json中写入响应体。无论是哪种情况,使用@ControllerAdvice的写法都能比较方便的实现。
~~~~~~ 例如下面这种方式就是前后端分离的异常处理方案,把异常信息封装到对象中,转换成json写入响应体。
@ControllerAdvice
@Component
public class MyControllerAdvice {
@ExceptionHandler({
NullPointerException.class,ArithmeticException.class})
@ResponseBody
public Result handlerException(Exception ex){
Result result = new Result();
result.setMsg(ex.getMessage());
result.setCode(500);
return result;
}
}
三、文件上传
3.1 文件上传要求
~~~~~~ Http协议规定了我们在进行文件上传时的请求格式要求。所以在进行文件上传时,除了在表单中增加一个用于上传文件的表单项(input标签,type=file)外必须要满足以下的条件才能进行上传。
①请求方式为POST请求
~~~~~~ 如果是使用表单进行提交的话,可以把form标签的method属性设置为POST。例如:
<form action="/upload" method="post">
</form>
②请求头Content-Type必须为multipart/form-data
~~~~~~ 如果是使用表单的话可以把表单的entype属性设置成multipart/form-data。例如:
<form action="/upload" method="post" enctype="multipart/form-data">
</form>
3.2 SpringMVC接收上传过来的文件
~~~~~~ SpringMVC使用commons-fileupload的包对文件上传进行了封装,我们只需要引入相关依赖和进行相应配置就可以很轻松的实现文件上传的功能。
①依赖
<!--commons文件上传,如果需要文件上传功能,需要添加本依赖-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.4</version>
</dependency>
②配置
<!--
文件上传解析器
注意:id 必须为 multipartResolver
-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置默认字符编码 -->
<property name="defaultEncoding" value="utf-8"/>
<!-- 一次请求上传的文件的总大小的最大值,单位是字节-->
<property name="maxUploadSize" value="#{1024*1024*100}"/>
<!-- 每个上传文件大小的最大值,单位是字节-->
<property name="maxUploadSizePerFile" value="#{1024*1024*50}"/>
</bean>
③接收上传的文件数据并处理
上传表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="uploadFile">
<input type="submit">
</form>
</body>
</html>
@Controller
public class UploadController {
@RequestMapping("/upload")
public String upload(MultipartFile uploadFile) throws IOException {
//文件存储 把上传上来的文件存储下来
uploadFile.transferTo(new File("test.sql"));
return "success";
}
}
注意:方法参数名要和提交上来的参数名一致。
3.3 MultipartFile常见用法
- 获取上传文件的原名
uploadFile.getOriginalFilename()
- 获取文件类型的MIME类型
uploadFile.getContentType()
- 获取上传文件的大小
uploadFile.getSize()
- 获取对应上传文件的输入流
uploadFile.getInputStream()
四、文件下载
4.1 文件下载要求
~~~~~~ 如果我们想提供文件下载的功能。HTTP协议要求我们的必须满足如下规则。
①设置响应头Content-Type
~~~~~~ 要求把提供下载文件的MIME类型作为响应头Content-Type的值
②设置响应头Content-disposition
~~~~~~ 要求把文件名经过URL编码后的值写入响应头Content-disposition。但是要求符合以下格式,因为这样可以解决不同浏览器中文文件名 乱码问题。
Content-disposition: attachment; filename=%E4%B8%8B%E6%B5%B7%E5%81%9Aup%E4%B8%BB%E9%82%A3%E4%BA%9B%E5%B9%B4.txt;filename*=utf-8''%E4%B8%8B%E6%B5%B7%E5%81%9Aup%E4%B8%BB%E9%82%A3%E4%BA%9B%E5%B9%B4.txt
③文件数据写入响应体中
4.2 代码实现
~~~~~~ 我们可以使用之前封装的下载工具类实现文件下载
工具类代码:
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.net.URLEncoder;
public class DownLoadUtils {
/**
* 该方法可以快速实现设置两个下载需要的响应头和把文件数据写入响应体
* @param filePath 该文件的相对路径
* @param context ServletContext对象
* @param response
* @throws Exception
*/
public static void downloadFile(String filePath, ServletContext context, HttpServletResponse response) throws Exception {
String realPath = context.getRealPath(filePath);
File file = new File(realPath);
String filename = file.getName();
FileInputStream fis = new FileInputStream(realPath);
String mimeType = context.getMimeType(filename);//获取文件的mime类型
response.setHeader("content-type",mimeType);
String fname= URLEncoder.encode(filename,"UTF-8");
response.setHeader("Content-disposition","attachment; filename="+fname+";"+"filename*=utf-8''"+fname);
ServletOutputStream sos = response.getOutputStream();
byte[] buff = new byte[1024 * 8];
int len = 0;
while((len = fis.read(buff)) != -1){
sos.write(buff,0,len);
}
sos.close();
fis.close();
}
}
Handler方法定义
@Controller
public class DownLoadController {
@RequestMapping("/download")
public void download(HttpServletRequest request, HttpServletResponse response) throws Exception {
//文件下载
DownLoadUtils.downloadFile("/WEB-INF/file/下海做UP主那些年.txt",request.getServletContext(),response);
}
}
还有一种下载方法是使用ResponseEntity<byte[]>作为返回值
@RequestMapping("/test/down")
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException{
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径
String realpath = servletContext.getRealPath("/img/1.jpg");
//创建输入流
InputStream is = new FileInputStream(realpath);
//创建字节数组
byte[] bytes = new byte[is.available()];
//将流读到字节数组中
is.read(bytes);
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition","attachment;filename=1.jpg");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte[]>(bytes, headers,statusCode);
//关闭输入流
is.close();
return responseEntity;
}
五、SpringMVC执行流程
因为我们有两种开发模式,我们分别来讲解两种模式在SpringMVC中的执行流程。
一种是类似JSP的开发流程:
把数据放入域对象中,然后进行页面跳转。
另外一种是前后端分离的开发模式,这也是目前市场上主流的模式:
把数据转化为json放入响应体中。
5.1 前后端不分离开发模式执行流程
-
用户发起请求被DispatchServlet所处理
-
DispatchServlet通过HandlerMapping根据具体的请求查找能处理这个请求的Handler。(HandlerMapping主要是处理请求和Handler方法的映射关系的)
-
HandlerMapping返回一个能够处理请求的执行链给DispatchServlet,这个链中除了包含Handler方法也包含拦截器。
-
DispatchServlet拿着执行链去找HandlerAdater执行链中的方法。
-
HandlerAdater会去执行对应的Handler方法,把数据处理转换成合适的类型然后作为方法参数传入
-
Handler方法执行完后的返回值会被HandlerAdapter转换成ModelAndView类型。(HandlerAdater主要进行Handler方法参数和返回值的处理。)
-
返回ModelAndView给DispatchServlet.
-
如果对于的ModelAndView对象不为null,则DispatchServlet把ModelAndView交给 ViewResolver 也就是视图解析器解析。
-
ViewResolver 也就是视图解析器把ModelAndView中的viewName转换成对应的View对象返回给DispatchServlet。(ViewResolver 主要负责把String类型的viewName转换成对应的View对象)
-
DispatchServlet使用View对象进行页面的展示。
5.2 前后端分离开发模式执行流程
前后端分离的开发模式中我们会使用@ResponseBody来把数据写入到响应体中。所以不需要进行页面的跳转。
故流程如下:
-
用户发起请求被DispatchServlet所处理
-
DispatchServlet通过HandlerMapping根据具体的请求查找能处理这个请求的Handler。(HandlerMapping主要是处理请求和Handler方法的映射关系的)
-
HandlerMapping返回一个能够处理请求的执行链给DispatchServlet,这个链中除了包含Handler方法也包含拦截器。
-
DispatchServlet拿着执行链去找HandlerAdater执行链中的方法。
-
HandlerAdater会去执行对应的Handler方法,把数据处理转换成合适的类型然后作为方法参数传入
-
Handler方法执行完后的返回值会被HandlerAdapter转换成ModelAndView类型。由于使用了@ResponseBody注解,返回的ModelAndView会为null ,并且HandlerAdapter会把方法返回值放到响应体中。(HandlerAdater主要进行Handler方法参数和返回值的处理。)
-
返回ModelAndView给DispatchServlet。
-
因为返回的ModelAndView为null,所以不用去解析视图解析和其后面的操作。