# 异常处理
springmvc提供了对异常的处理注解,可以使用这个进行对异常的处理,从而实现解耦合操作
SpringMVC 框架处理异常的常用方式:使用@ExceptionHandler 注解处理异常
# @ExceptionHandler 注解
使用注解@ExceptionHandler 可以将一个方法指定为异常处理方法。该注解只有一个可
选属性 value,为一个 Class<?>数组,用于指定该注解的方法所要处理的异常类,即所要匹
配的异常。
而被注解的方法,其返回值可以是 ModelAndView、String,或 void,方法名随意,方法参数可以是 Exception 及其子类对象、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参数赋值。对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中。
@ExceptionHandler(value = AgeException.class)
public 返回值类型 otherEx(Exception e) {
逻辑代码
}
2
3
4
也就是,如果抛出了AgeException
异常,那么框架就会跳转到这个方法otherEx()
执行,并且会自动将异常赋值给形参Exception e
,通过这个形参可以获取到异常信息
这里的返回值类型,其实就是@RequestMapping
中的返回值类型,可以返回ModeAndView,对象,void,String
,如果返回对象,框架就会将其转化为json等,并返回给用户
# 使用步骤
- 定义异常类,也就是
throw对象
- 定义全局异常处理类
- 创建web应用,对处理的异常进行处理
- 在springmvc配置文件中,注册注解扫描器,因为使用到注解
- 测试
# 代码
//异常类
public class MyException extends Exception {
public MyException () {
}
public MyException (String message) {
super(message);
}
}
//子异常类
public class AgeException extends MyException {
public AgeException () {
}
public AgeException (String message) {
super(message);
}
}
public class NameException extends MyException {
public NameException () {
super();
}
public NameException (String message) {
super(message);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
web应用
@Controller
public class WebController {
@RequestMapping("myex")
public ModelAndView nameException(String name,Integer age) throws MyException {
ModelAndView mv = new ModelAndView();
System.out.println("name: "+name);
if (!name.equals("chuchen")) {
throw new NameException("用户名不是chuchen");
}
if (age > 100) {
throw new AgeException("年龄太大");
}
mv.addObject("name",name);
mv.addObject("age",age);
mv.setViewName("show");
return mv;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
异常处理类
@ControllerAdvice
public class ControlHandler {
@ExceptionHandler(value = NameException.class)
public ModelAndView nameEx(Exception e) {
ModelAndView mv = new ModelAndView();
System.out.println("发送异常");
mv.addObject("msg","名字发生异常");
mv.addObject("ex",e.getMessage());
mv.setViewName("exce");
return mv;
}
@ExceptionHandler(value = AgeException.class)
public ModelAndView ageEx(Exception e) {
ModelAndView mv = new ModelAndView();
System.out.println("年龄");
mv.addObject("msg","年龄发生异常");
mv.addObject("ex",e.getMessage());
mv.setViewName("exce");
return mv;
}
@ExceptionHandler()
public ModelAndView otherEx(Exception e) {
ModelAndView mv = new ModelAndView();
System.out.println("其他异常");
mv.addObject("msg","其他发生异常");
mv.addObject("ex",e.getMessage());
mv.setViewName("exce");
return mv;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
在异常处理类中,需要加上注解@ControllerAdvice
,表示控制器增强类,就是给控制器Controller
添加额外的功能,比如异常处理功能等,加强这个控制器的功能,必须在类
上
处理方法
这个就是处理异常发生的方法,也就是如果发生
AgeException
异常,那么就会执行@ExceptionHandler(value = AgeException.class) public ModelAndView ageEx(Exception e) {}
1
2中的代码,在这个方法中,我们可以进行的操作,比如,将异常记录下来,以日志的形式,将这个异常发送个管理员,以邮件短信等形式
在程序执行的过程中,可能还有很多的方法,并没有在我们控制器增强类中,进行异常的处理,那么如果对这些异常进行处理呢?
@ExceptionHandler()
public ModelAndView otherEx(Exception e) {}
2
@ExceptionHandler()
没有值,就会执行这个方法中的代码
# 执行过程
如果没有发生异常的话,那么返回的视图为show.jsp
,但是如果发生异常,那么返回的视图就是exce.jsp
,通过这个方法,可以实现程序的解耦合操作,这样所有的异常处理,都是在这个控制器增强类中进行。
但是经过测试发现,如果异常的处理方法是定义在springboot的controller中中,也可以不用加上
@ControllerAdvice
注解如果我们的异常处理方法是单独定义在一个类中的,那么我们想要在controller中抛出的异常被处理,我们就必须在此异常处理类上加入
@Component,@ControllerAdvice
两个注解,在异常处理方法上加入@ResponseBody,@ExceptionHandler
注解
@Slf4j
@Component
@ControllerAdvice
public class CustomExceptionHandler {
@ResponseBody
@ExceptionHandler(value = {RuntimeException.class})
public Result aopException(RuntimeException exception) {
log.error("异常信息{}",exception.getMessage());
return Result.builder()
.code(ResultCode.SUCCESS.getCode())
.message(ResultCode.SUCCESS.getMessage())
.data("出现异常了").build();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 拦截器
SpringMVC 中的 Interceptor 拦截器是非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器,在处理器适配器执行处理器之前”。当然,在处理器映射器映射出所要执行的处理器类时,已经将拦截器与处理器组合为了一个处理器执行链,并返回给了中央调度器。
# 使用步骤
- 定义一个类,实现
HandlerInterceptor
接口,并且重写里面的方法 - 在springmvc配置文件中,注册拦截器
- 定义拦截器类
public class Interceptor implements HandlerInterceptor {
@Override
public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("执行preHandle·········");
return true;
}
@Override
public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("执行postHandle·········");
}
@Override
public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("执行postHandle·········");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
自定义拦截器,需要实现 HandlerInterceptor 接口。而该接口中含有三个方法:
➢
preHandle(request,response, Object handler)
:
该方法在处理器方法执行之前执行。其返回值为 boolean,若为 true,则紧接着会执行处理器方法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。
➢ postHandle(request,response, Object handler,modelAndView)
:
该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。
由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修
改处理器方法的处理结果数据,且可以修改跳转方向。
➢
afterCompletion(request,response, Object handler, Exception ex)
:
当 preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有
工作完成之后才执行该方法。即该方法是在中央调度器渲染(数据填充)了响应页面之后执行的,此
时对 ModelAndView 再操作也对响应无济于事。
afterCompletion 最后执行的方法,清除资源,例如在 Controller 方法中加入数据
- 注册拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="vin.cco.interceptor.MyInterceptor" />
</mvc:interceptor>
</mvc:interceptors>
2
3
4
5
6
<mvc:mapping path="/**"/>
中的path就是用户请求资源的url地址,可以使用通配符,**
表示任意的资源,那么这样配置之后,所有的资源,都将被拦截
需要注意的一个点就是
private int chu = 1;
1如果其中一个方法,对这个chu变量进行加减等操作,这个变量是共享的,相当于是保存在
ServletContext
中,无论用户是在哪个设置上进行访问,只要将拦截器中设置变量,那么所有用户拿到的此对象都是同一个,通过这个特性,可以使用这个来进行多种操作
# 多个拦截器执行顺序
一个请求地址,可以对应一个或者多个拦截器,只需要在springmvc中,对这些拦截器进行注册即可,但是他们之间,存在一个先后顺序
- 注册多个拦截器
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="vin.cco.interceptor.MyInterceptor1" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="vin.cco.interceptor.MyInterceptor2" />
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="vin.cco.interceptor.MyInterceptor3" />
</mvc:interceptor>
</mvc:interceptors>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
可以将拦截器的存储想象成,被框架使用list集合进行保存的,所以最先注册的那个拦截器是最先执行的
当有多个拦截器时,形成拦截器链。拦截器链的执行顺序,与其注册顺序一致。需要再次强调一点的是,当某一个拦截器的 preHandle()方法返回 true 并被执行到时,会向一个专门的方法栈中放入该拦截器的 afterCompletion()方法。
多个拦截器中方法与处理器方法的执行顺序如下图:
从图中可以看出,只要有一个 preHandle()方法返回 false,则上部的执行链将被断开,其后续的处理器方法与 postHandle()方法将无法执行。但,无论执行链执行情况怎样,只要方法栈中有方法,即执行链中只要有 preHandle()方法返回 true,就会执行方法栈中的afterCompletion()方法。最终都会给出响应。
换一种表现方式,也可以这样理解:
# 拦截器和过滤器比较
- 过滤器是servlet中的对象, 拦截器是框架中的对象
- 过滤器实现Filter接口的对象, 拦截器是实现HandlerInterceptor
- 过滤器是用来设置request,response的参数,属性的,侧重对数据过滤的。 拦截器是用来验证请求的,能截断请求。
- 过滤器是在拦截器之前先执行的。
- 过滤器是tomcat服务器创建的对象 拦截器是springmvc容器中创建的对象
- 过滤器是一个执行时间点。 拦截器有三个执行时间点
- 过滤器可以处理jsp,js,html等等 拦截器是侧重拦截对Controller的对象。 如果你的请求不能被DispatcherServlet接收, 这个请求不会执行拦截器内容
- 拦截器拦截普通类方法执行,过滤器过滤servlet请求响应
如果存在多个拦截器,那么只要存在一个拦截器的preHandle()方法返回false,那么之后的拦截器不会再执行,并且请求也不会被发送成功
← 嵌入式servlet服务器 执行流程图 →