SpringMVC拦截器和全局异常处理
1.拦截器定义与使用
什么是拦截器?
拦截器(Interceptor)是一种动态拦截Controller方法调用的对象,它可以在指定的方法调用前或者调用后,执行预先设定的代码。
拦截器作用类似于Filter(过滤器),但是它们的技术归属和拦截内容不同。Filter采用Servlet技术,拦截器采用Spring MVC技术;Filter会对所有的请求进行拦截,拦截器只针对Spring MVC的请求进行拦截。
拦截器的用途:
登录验证:在用户访问需要登录的页面之前,拦截器可以检查用户是否已经登录,如果没有登录则重定向到登录页面
权限检查:在用户访问需要特定权限的资源之前,拦截器可以检查用户是否具有相应的权限,如果没有则返回无权限的错误信息
日志记录:在前端访问后端每个接口时,拦截器可以记录日志,包括请求的 IP 地址、请求时间、请求的 URL 等信息
数据校验:在请求处理之前,拦截器可以校验请求参数的有效性,如果参数不合法则返回错误信息
统一异常处理:在请求处理出现异常时,拦截器可以统一处理异常信息,避免程序抛出异常页面
过滤器 | 拦截器 | AOP编程 | |
---|---|---|---|
拦截对象 | URL | URL | 方法 |
执行流程 | 一次请求执行完成前后 | 一次请求的相应处理器执行前后 | 符合条件的方法调用前后 |
控制粒度 | 过滤器 -> 拦截器 -> 切面,越来越细致 | ||
执行顺序 | 过滤器 -> 拦截器 -> 切面 | ||
适用场景 | 对服务器性能影响越小,如权限校验、敏感词过滤、字符集编码设置、响应数据压缩 | 业务判断,如日志记录 |
拦截器定义方式
实现HandlerInterceptor接口定义拦截器
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
//在控制器方法调用前执行安全控制、权限校验等操作。
//当返回值为true时,表示继续向下执行;当返回值为false时,整个请求就结束了,后续的所有操作都会中断(包括调用下一个拦截器和控制器类中的方法执行等)
return false;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
//在控制器方法调用之后且视图解析之前,对请求域中的模型和视图做出进一步的修改。
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) throws Exception {
//在整个请求完成后执行,即视图渲染结束之后执行,完成一些资源清理、日志信息记录等工作。
}
}
在springmvc.xml中注册自定义的拦截器MyInterceptor:
<!-- 配置拦截器 -->
<mvc:interceptors>
<!--拦截所有请求-->
<bean class="edu.cqie.ssm.interceptor.MyInterceptor1"/>
<mvc:interceptor>
<!-- 配置拦截器作用的路径 -->
<mvc:mapping path="/**"/>
<!-- 配置不需要拦截器作用的路径 -->
<mvc:exclude-mapping path="/login"/>
<!-- 对匹配路径的请求才进行拦截-->
<bean class="edu.cqie.ssm.interceptor.MyInterceptor2" />
</mvc:interceptor>
</mvc:interceptors>
Ø单个拦截器执行流程
Ø多个拦截器执行流程
2.全局异常处理
定义:异常是程序在运行过程中出现的一些错误,使用面向对象思想把这些错误用类来描述,那么一旦产生一个错误,即创建某一个错误的对象,这个对象就是异常对象。
类型:
自定义异常:
public class BizException extends RuntimeException {
static final long serialVersionUID = 1234719074324978L;
public BizException(){}
public BizException(String message){
super(message);
}
//测试自定义异常
public static void main(String[] args) {
throw new BizException("自定义运行时异常");
}
}
Ø为什么全局异常处理?
编译时异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发、测试通过手段减少运行时异常的发生。在开发中,不管是Dao层、Service层还是Controller层,都有可能抛出异常。在SpringMVC中,能将所有类型的异常处理从各处理过程解耦出来,既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护。使用一个友好的页面显示,而不是一堆看不懂的错误信息。
Ø全局统一异常实现方式
•自带的SimpleMappingExceptionResolver
配置xml
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 定义默认的异常处理页面 -->
<property name="defaultErrorView" value="error"/>
<!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
<property name="exceptionAttribute" value="ex"/>
<!-- 定义需要特殊处理的异常,这是重要点 -->
<property name="exceptionMappings">
<props>
<!--异常类型 错误视图-->
<prop key="java.lang.RuntimeException">error</prop>
<!-- 还可以定义其他的自定义异常 -->
</props>
</property>
</bean>
•实现HandlerExceptionResolver接口
public class GlobalExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
ModelAndView mv = new ModelAndView();
mv.setViewName("error");//进入错误页面
if(ex instanceof RuntimeException){
RuntimeException rtex = (RuntimeException)ex;
mv.addObject("msg", rtex.getMessage());
}
return mv;
}
}
同时注册bean
<bean class="edu.cqie.ssm.exception.GlobalExceptionResolver"/>
•@ControllerAdvice + @ExceptionHandler注解
@Component
@ControllerAdvice
@Log4j
@ResponseBody
public class GlobalExceptionHandler {
/** 400 - Bad Request */
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public Result handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
log.error(“参数解析失败”, e);
return Result.fail(“Couldn‘t read request param.”);
}
/** 405 - Method Not Allowed */
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public Result handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
log.error(“不支持当前请求方法”, e);
return Result.fail(“Request method is not supported.”);
}
/** 415 - Unsupported Media Type */
@ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public Result handleHttpMediaTypeNotSupportedException(Exception e) {
log.error(“不支持当前媒体类型”, e);
return Result.fail(“The current content type is not supported.”);
}
/** 500 - Internal Server Error */
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
if (e instanceof BizException){
log.error("业务逻辑出错啦"+e.getMessage());
return Result.fail("The system has a business error.");
}
log.error("服务运行异常", e);
e.printStackTrace();
return Result.fail("Server error.");
}
}
3.文件上传
(1)引入依赖
<!-- commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.5</version>
</dependency>
<!-- commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.15.0</version>
</dependency>
(2)修改SpringMVC配置文件:在项目的src/main/resources目录下,新建spring-mvc.xml文件作为配置文件,并在该配置文件中创建id为multipartResolver的Bean。
<?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:context="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
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="edu.cqie.ssm"/>
<!--1.配置静态资源处理器-->
<mvc:default-servlet-handler/>
<!--2.支持mvc注解驱动-->
<mvc:annotation-driven></mvc:annotation-driven>
<!--3.视图解析器-->
<mvc:view-resolvers>
<mvc:jsp prefix="/WEB-INF/pages/" suffix=".jsp"/>
</mvc:view-resolvers>
<!--4.多部件解析器(Spring6以下版本)-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设定默认编码 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 设定文件上传的最大值为5MB,5*1024*1024 -->
<property name="maxUploadSize" value="5242880"></property>
<!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240 -->
<property name="maxInMemorySize" value="40960"></property>
<!-- 上传文件的临时路径 -->
<property name="uploadTempDir" value="tmp/file"></property>
<!-- 延迟文件解析 -->
<property name="resolveLazily" value="true"/>
</bean>
</beans>
(3)修改web.xml文件:从Spring6以后移除了org.springframework.web.multipart.commons.*包,要实现上传文件功能,只需在web.xml中节点内添加以下配置。
(4)添加控制器和视图:创建个人中心视图personal.jsp,提交修改后的用户信息。创建UserController控制器类,接收上传文件请求
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>个人中心</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/personal/modify" method="post" enctype="multipart/form-data">
<input type="file" name="header" accept="image/*"/>
<input type="text" name="name" />
<input type="text" name="email" />
<input type="submit" value="提交">
</form>
</body>
</html>
@RequestMapping("/personal/modify")
public ModelAndView modify(UserInfo userInfo, @RequestParam(value = "header", required = false)MultipartFile part, HttpServletRequest request){
log.info("请求参数POJO包装类的值为:" +userInfo.toString());
String name = "";
if(!part.isEmpty()) {
File dir = new File(request.getServletContext().getRealPath("/upload"));
if (!dir.exists()) {
dir.mkdirs();
}
name = String.valueOf(new Date().getTime() + "_" + part.getOriginalFilename());
File destFile = new File(dir, name);
try {
part.transferTo(destFile);
} catch (IllegalStateException | IOException e) {
e.printStackTrace();
}
}
// 访问的url
String url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/upload/" + name;
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("user", userInfo);
modelAndView.addObject("img", url);
modelAndView.setViewName("personal/detail");
return modelAndView;
}
注意:
大多数文件上传都是通过表单形式提交给后台服务器,要实现文件上传功能,就需要提供一个文件上传的表单,并且必须满足以下3个条件:
•form表单的method属性设置为post。
•form表单的enctype属性设置为multipart/form-data。
•提供的文件上传选择框。
MultipartFile****常用方法
方法声明 | 功能描述 |
---|---|
byte[] getBytes() | 将文件转换为字节数组形式 |
String getContentType() | 获取文件的内容类型 |
InputStream getInputStream() | 读取文件内容,返回一个InputStream流 |
String getName() | 获取多部件form表单的参数名称 |
String getOriginalFilename() | 获取上传文件的初始化文件名 |
long getSize() | 获取上传文件的大小,单位是字节 |
boolean isEmpty() | 判断上传的文件是否为空 |
ng getContentType() | 获取文件的内容类型 |
InputStream getInputStream() | 读取文件内容,返回一个InputStream流 |
String getName() | 获取多部件form表单的参数名称 |
String getOriginalFilename() | 获取上传文件的初始化文件名 |
long getSize() | 获取上传文件的大小,单位是字节 |
boolean isEmpty() | 判断上传的文件是否为空 |
void transferTo(File file) | 将上传文件保存到目标目录下 |