# springmvc中拦截器
1. 拦截器 Interceptor 拦截 中断
类似于javaweb中Filter过滤器(具有拦截请求、放行请求、拦截响应、放行响应)
拦截器也是拦截请求的,而有一点不同的是过滤器可以拦截所有的请求,而拦截器只能拦截控制器相关的请求
2. 作用
通过将控制器中的公共代码放在拦截器中执行,减少控制器中代码的冗余
3. 拦截器特点
1). 请求到达会经过拦截器 响应回来同样会经过拦截器
2). 拦截器只能拦截控制器相关的请求 不能拦截jsp 静态资源相关的请求
3). 拦截器可以中断请求轨迹
4. 开发拦截器
1. 类 implements HandlerInterceptor 实现接口中方法
2. 配置拦截器
a. 注册拦截器对象 bean id class=""
b. 配置拦截器拦截请求路径(拦截器并不是默认所有请求都拦,需要明确告诉拦截器要拦哪些请求,配置的才拦,没有配置的不拦)
在实现系统定义的HandlerInterceptor接口时,需要重写preHandle、postHandle、afterCompletion三个方法。在请求经过拦截器时会优先进入preHandle方法执行其中的内容,如果preHandle方法返回值为true,代表放行请求,返回值为false,代表中断请求;当控制器的方法执行结束之后,会返回拦截器中执行postHandle方法,postHandle方法执行完之后会响应请求;在响应请求完成之后会执行afterCompletion,注意:无论控制器方法执行时是否出现异常,afterCompletion方法都会执行,而如果控制器方法出现异常postHandle方法不会再执行。
三个方法和控制方法的执行顺序:
preHandle —> 控制器方法 --> postHandle --> afterCompletion
接下来我们来开发一个拦截器尝试验证一下上面四个方法的执行顺序:
- 自定义拦截器,并实现系统定义的HandlerInterceptor拦截器接口
/**
* 自定义拦截器
*/
public class MyInterceptor implements HandlerInterceptor {
// 1. 请求经过拦截器会优先进入拦截器中的preHandle方法执行preHandle中方法
// 2. 如果preHandle返回为true代表放行请求 如果返回值为false 中断请求
// 3. 如果preHandle返回为true,会执行当前请求对应的控制器中方法
// 4. 当控制器方法执行结束之后,会返回拦截器中执行拦截器中postHandle方法
// 5. postHandle执行结束之后响应请求,在响应请求完成后会执行afterCompletion方法
@Override
// 参数1:当前请求对象 参数2:当前请求对应响应对象 参数3:当前请求的控制器对应的方法对象
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println("========1===========");
return true;
}
// 参数1:当前请求对象 参数2:当前请求对应响应对象 参数3:当前请求的控制器对应的方法对象 参数4:当前请求的控制器方法返回的modelandview对象 modelandview模型和视图
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println("========3===========");
}
// 注意:无论正确还是失败都会执行
// 参数1:当前请求对象 参数2:当前请求对应响应对象 参数3:当前请求的控制器对应的方法对象 参数4: 请求过程中出现异常时的异常对象
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
System.out.println("========4===========");
}
}
- 在springmvc.xml文件中配置拦截器
<bean class="com.baizhi.interceptors.MyInterceptor" id="myInterceptor"/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/json/*"/>
<mvc:exclude-mapping path="/json/test"/>
<ref bean="myInterceptor"/>
mvc:interceptor>
mvc:interceptors>
- 测试
/**
* 用来测试springmvc中响应json格式数据
*
*/
@Controller
@RequestMapping("json")
public class JsonController {
@RequestMapping("test")
public String test(){
System.out.println("========2===========");
return "index";
}
}
结果:
- 实现接口时的三个方法的具体细节
我们知道在自定义拦截器时需要实现系统定义的HandlerInterceptor接口,并需要重写preHandle、postHandle、afterCompletion方法,接下来我们来具体讲讲这三个方法的参数以及适合用于什么情况。
/**
* 自定义拦截器
*/
public class MyInterceptor implements HandlerInterceptor {
// 1. 请求经过拦截器会优先进入拦截器中的preHandle方法执行preHandle中方法
// 2. 如果preHandle返回为true代表放行请求 如果返回值为false 中断请求
// 3. 如果preHandle返回为true,会执行当前请求对应的控制器中方法
// 4. 当控制器方法执行结束之后,会返回拦截器中执行拦截器中postHandle方法
// 5. postHandle执行结束之后响应请求,在响应请求完成后会执行afterCompletion方法
@Override
// 参数1:当前请求对象 参数2:当前请求对应响应对象 参数3:当前请求的控制器对应的方法对象
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println(((HandlerMethod)o).getMethod().getName());
System.out.println("========1===========");
// 强制用户登录
// 始终没往session中放命名属性,永远找不到,永远找不到始终是null,直接会重定向到登录页面
/*Object user = httpServletRequest.getSession().getAttribute("user");
if(user == null){
// 重定向到登录页面
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/login.jsp");
}*/
return true;
}
// 参数1:当前请求对象 参数2:当前请求对应响应对象 参数3:当前请求的控制器对应的方法对象 参数4:当前请求的控制器方法返回的modelandview对象 modelandview模型和视图
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
System.out.println(modelAndView);
System.out.println("========3===========");
}
// 注意:无论正确还是失败都会执行
// 参数1:当前请求对象 参数2:当前请求对应响应对象 参数3:当前请求的控制器对应的方法对象 参数4: 请求过程中出现异常时的异常对象
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
if(e != null)
System.out.println(e.getMessage());
System.out.println("========4===========");
}
}
preHandle方法
请求经过拦截器会优先进入preHandle方法,执行preHandle方法中的内容,返回值是boolean类型,返回true代表放行请求,返回false代表中断请求
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o)
如果想要实现强制登录,可以使用这个方法,在进入控制器之前做一次判断,如果没有登录呢,就跳转到登录页面,直到登录才放行去执行控制器中的方法。
@Override
// 参数1:当前请求对象 参数2:当前请求对应响应对象 参数3:当前请求的控制器对应的方法对象
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
System.out.println(((HandlerMethod)o).getMethod().getName());
System.out.println("========1===========");
// 强制用户登录
// 始终没往session中放命名属性,永远找不到,永远找不到始终是null,直接会重定向到登录页面
Object user = httpServletRequest.getSession().getAttribute("user");
if(user == null){
// 重定向到登录页面
httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + "/login.jsp");
}
return true;
}
postHandle方法
**在控制器中的方法执行完成之后返回控制器时会执行这个方法,**如果控制器方法出现了异常,则这个方法不会再执行,如果控制器方法没有出现异常,并且之前preHandle方法放行了,这个方法才会执行
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception
afterCompletion方法
在控制器方法执行完了之后会执行这个方法,无论控制器方法是否出现异常都会执行这个方法,在响应请求完成之后会执行这个方法,控制器没有出现异常,响应成功,执行这个方法,控制器出现异常,响应失败,也执行这个方法。
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception {
- 配置拦截器时可以使用通配符
<bean class="com.baizhi.interceptors.MyInterceptor" id="myInterceptor"/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/json/*"/>
<mvc:exclude-mapping path="/json/test"/>
<ref bean="myInterceptor"/>
mvc:interceptor>
mvc:interceptors>
拦截json控制器类的所有方法
<mvc:mapping path="/json/*"/> 代表拦截json控制器类的所有方法
拦截所有控制器
<mvc:mapping path="/*"/>
- 拦截器本质
拦截器底层也是AOP,动态代理。
- 三个方法的适用情景
如果想让业务需求在控制器方法执行之前必须去做的事,可以放到preHandle中去做
如果想让业务需求在控制器执行完了之后做的事,可以放到postHandle中去做
如果有一些业务需求无论控制器正常与否都要做的事就放到afterCompletion中去做,比如日志的记录
- 在配置拦截器时如果拦截了所有控制器或拦截了某个控制器的所有方法,这时候如果不想拦截某个方法,我们就需要用到不拦截谁了,我们可以配置不拦截谁
排除拦截的请求,path里面写不想拦截的方法的路径
<mvc:exclude-mapping path="/json/test"/>
总结:
springmvc中全局异常处理机制
1. springmvc作为一个控制器主要作用
1. 处理请求 接收请求数据 调用业务对象
2. 请求响应 跳转对应视图展示数据
2. 现有控制器开发存在问题
1). 在处理用户请求出现运行时异常时直接响应给用户的是一个错误界面,
对于用户的使用体验不友好
3. 全局异常处理机制
作用:用来解决整个系统中任意一个控制器抛出异常时的统一处理入口
4. 全局异常处理开发
1. 类 implements HandlerExceptionResolver
2. 配置全局异常处理类
接下来我们来尝试开发一个全局异常处理类
- 自定义一个全局异常处理类,并实现系统定义的HandlerExceptionResolver接口
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
System.out.println("进入全局异常处理器获取的异常信息为:" + e.getMessage());
ModelAndView modelAndView = new ModelAndView();
// 基于不同的业务异常跳转到不同的页面
if(e instanceof UserNameNotFoundException){
modelAndView.setViewName("redirect:/login.jsp");
}
modelAndView.setViewName("redirect:/error.jsp"); // 跳转到error, return "error" =====> /error.jsp
// modelAndView 默认放入request作用域;如果使用redirect跳转,model中数据会自动拼接到跳转url中
modelAndView.addObject("msg", e.getMessage());
return modelAndView;
}
error.jsp:
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>全局错误页面title>
head>
<body id="bd">
<h1>系统出现错误!${requestScope.msg}h1>
body>
html>
- 在springmvc.xml文件中配置全局异常处理类
<bean class="com.baizhi.handlerexception.GlobalExceptionResolver"/>
- 测试
@Controller
@RequestMapping("json")
public class JsonController {
@RequestMapping("test")
public String test() throws Exception {
System.out.println("========2===========");
throw new RuntimeException("出现错误");
//return "index";
// ModelAndView view index model requset.setAttribute(o, s)
}
}
补充
在全局异常处理类中推荐使用redirect跳转,因为使用forward跳转,地址栏不会发生改变,每次刷新都要重新发送请求,效率降低。
modelAndView
// redirect跳转
modelAndView.setViewName("redirect:/login.jsp")
// 前台页面获取信息
${requestScope.msg}
// forward跳转
modelAndView.setViewName("error");
// 前台页面获取信息
在使用全局异常处理类的时候可以基于不同的业务业务跳转到不同的页面
什么叫基于不同的业务业务跳转到不同的页面呢,我们往下看:
自定义一个异常类(用户名找不到时抛出):
public class UserNameNotFoundException extends RuntimeException {
public UserNameNotFoundException(String message) {
super("用户名找不到");
}
}
自定义全局异常处理类中的方法:
/**
* 自定义全局异常处理类
*/
public class GlobalExceptionResolver implements HandlerExceptionResolver {
/**
* 用来处理发生异常时的方法
* @param httpServletRequest 当前请求对象
* @param httpServletResponse 当前请求对应的响应对象
* @param o 当前请求的方法对象
* @param e 当前出现异常时的异常对象
* @return 出现异常时展示视图和数据
*/
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
System.out.println("进入全局异常处理器获取的异常信息为:" + e.getMessage());
ModelAndView modelAndView = new ModelAndView();
// 基于不同的业务异常跳转到不同的页面
if(e instanceof UserNameNotFoundException){
modelAndView.setViewName("redirect:/login.jsp");
}else {
modelAndView.setViewName("redirect:/error.jsp"); // 跳转到error, return "error" =====> /error.jsp
}
// modelAndView.setViewName("error");
// modelAndView 默认放入request作用域;如果使用redirect跳转,model中数据会自动拼接到跳转url中
modelAndView.addObject("msg", e.getMessage());
return modelAndView;
}
}
login.jsp:
<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>用户登录title>
head>
<body>
<h1>用户登录界面h1>
<h1>登陆失败:${param.msg}h1>
body>
html>
测试:
@Controller
@RequestMapping("json")
public class JsonController {
@RequestMapping("test")
public String test() throws Exception {
System.out.println("========2===========");
throw new UserNameNotFoundException("出现错误");
//return "index";
// ModelAndView view index model requset.setAttribute(o, s)
}
}
在用户名找不到时抛出用户名找不到异常跳转到指定页面,这就是基于不同的业务异常跳转到不同的页面
总结: