拦截器

基础使用

加强版本的 Filter,MVC 中的接口为 HandlerInterceptor 处理拦截器,一共有三个方法:

1
2
3
public boolean preHandle(); //执行请求处理方法之前执行
public void postHandle(); //执行请求处理方法之后执行
public void afterCompletion(); //视图渲染完成之后执行

自己实现 HandlerInterceptor 之后,需要在 SpringMVC 中配置拦截器的信息才可以使用:

1
2
3
4
5
6
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/Interceptor"/>
<bean class="space.xorex.SpringMVC_01.Interceptor.MyInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>

多重拦截器

当有多个拦截器匹配到某个方法的时候,整体拦截器的工作顺序是按照在 SpringMVC 配置的顺序执行的。

而对于 preHandle() 和 postHandle() 则是按照一层一层套娃执行的,拦截器执行顺序靠前的在最外层,拦截器配置 01 在 02 前面:

1
2
3
4
5
6
7
01 preHandle() is invoked!
02 preHandle() is invoked!
testInterceptor() is invoked!
02 postHandle() is invoked!
01 postHandle() is invoked!
02 afterCompletion() is invoked!
01 afterCompletion() is invoked!

而 afterCompletion() 作为最后执行的擦屁股方法,和 try-catch-finally 里面的 finally 代码块类似,等待前面的套娃执行完毕,再单独按照顺序套娃从内到外执行最后的处理。可以记忆为,prehandle 顺序执行,postHandle() 和 afterCompletion() 逆序执行。

拦截

对于配置了一个拦截器的执行方法被 preHandler() return 了 false,那么后面的请求处理方法、postHandle() 和 afterCompletion() 都停止执行。

如果多个拦截器的执行方法被某一个 preHandler() return 了 false,那么这个 preHandler() 所在的截断了的拦截器的后面拦截器的所有方法(preHandler()、postHandle() 和 afterCompletion())都不会再执行。而前面所有放行了的拦截器的 afterCompletion() 方法作为拦截器的结尾处理还是会被执行的!

举个例子,01、02 和 03 三个拦截器,02 的 preHandler() return false,进行截断,那么方法的执行结果为:

1
2
3
01 preHandle() is invoked!
02 preHandle() is invoked!
01 afterCompletion() is invoked! //因为 01 的 preHandle() 放行了,所以需要执行收尾方法、

所以如果一旦被拦截,整个请求就不会被处理了,也不会有有效的响应。

异常处理

异常处理流程

SpringMVC 的异常处理是通过 List<HandlerExceptionResolver> 来解决的,如果在调用请求处理方法的时候抛出了异常,那么会依次枚举实现了 HandlerExceptionResolver 接口的类,如果抛出来的这些异常能被这些异常处理解析器解决,那么就交给他们,如果不能解决,那么会直接抛出异常到 Tomcat,最终在页面上显示一个丑丑的 500 服务器内部错误的页面。

在 SpringMVC 中,有三个默认加载的 HandlerExceptionResolver 实现:ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver 和 DefaultHandlerExceptionResolver。

  1. ExceptionHandlerExceptionResolver

第一个处理类可以处理的异常为所有使用了注解 @ExcpetionHandler(Class) 中参数输入的异常。也是我们用来自定义异常处理的方式,详情看下面介绍。

  1. ResponseStatusExceptionResolver

用于设置当我们出现了自定义异常的时候,应该返回什么状态码和错误信息,而使用核心就是注解 @ResponseStatus

我们首先自定义一个异常,并使用注解 @ResponseStatus 来定义

1
2
3
@ResponseStatus(value = HttpStatus.NOT_ACCEPTABLE,reason = "拒绝访问!")
public class CanNotAccsessException extends RuntimeException {
}
1
2
3
4
@RequestMapping("/exception")
public void exception() {
throw new CanNotAccsessException();
}

然后当我们抛出我们自定义的异常 CanNotAccessException 无法被 ExceptionHandlerExceptionResolver 里面的异常处理方法处理之后,就会交给 ResponseStatusExceptionResolver ,发现异常拥有注解 @ResponseStatus 之后,会读取注解信息,然后返回一个自定义的错误页面, 406 拒绝访问。

306.jpg

  1. DefaulthandlerExceptionResolver

是用来处理和 SpringMVC 相关的异常,我们可以忽略它,比如这些异常:

1
2
3
4
5
6
7
8
9
10
11
12
handleNoSuchRequestHandlingMethod
handleHttpRequestMethodNotSupported
handleHttpMediaTypeNotSupported
handleMissingServletRequestParameter
handleServletRequestBindingException
handleTypeMismatch
handleHttpMessageNotReadable
handleHttpMessageNotWritable
handleMethodArgumentNotValidException
handleMissingServletRequestParameter
handleMissingServletRequestPartException
handleBindException

@ExceptionHandler

只需要在某个方法前面加上 @ExceptionHandler 这个注解,就可以让这个方法变为异常处理器。处理的异常取决于注解里面的参数填写的异常种类。可以自己在异常处理器中返回 ModelAndView 来自定义视图,不写也没关系。获取异常实例通过异常处理器的参数处获取,参数类型一定要匹配被 catch 异常的类型。

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping("/exception")
public void exception() {
throw new CanNotAccsessException(); //抛出异常供下面处理器处理
}

@ExceptionHandler(CanNotAccsessException.class) //指明这个方法是处理 CanNotAccessException 异常的处理器
public ModelAndView mathException(Exception exception) { //获取被抛出来的实例
ModelAndView mv = new ModelAndView("Hello/error");
mv.addObject("error", exception);
return mv; //当然如果不想跳转页面的话也可以不返回 ModelAndView
}

@ControllerAdvice

可以将多个 ExceptionHandler 异常处理器方法集中到一个类中去,然后使用注解 @ControllerAdvice 告诉 SpringMVC 这个类里面的东西都是控制层的建议方法。这个属于全局的异常处理器,而上面在 @Controller 里面定义的处理器则是局部处理器,只能处理本类里面抛出的异常。

@Controller@ControllerAdvice 都拥有能处理异常的方法的时候,本类处理器优先级高于全局处理器。而对于两个在同一个地方的异常处理器,则是优先选择匹配精度最高的那一个。

SimpleMappingExceptionHandlerResovler

这个异常处理解析器是通过配置的方式来对某些异常进行指定页面处理。

1
2
3
4
5
6
7
8
9
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props> <!--设置捕捉异常之后前往的页面-->
<prop key="java.lang.NullPointerException">Hello/error</prop>
</props>
</property> <!-- 设置将异常实例放入请求域的 Key -->
<property name="exceptionAttribute" value="exception">
</property>
</bean>