SpringMVC 全局异常处理
1. SimpleMappingExceptionResolver
- SimpleMappingExceptionResolver继承树
我们可以看到AbstractHandlerExceptionResolver实现了HandlerExceptionResolver中的resolveException, 其具体实现如下:
public ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
//判断该resolver是否支持handler
if (shouldApplyTo(request, handler)) {
//清除response缓存
prepareResponse(ex, response);
//调用doResolverException, 该方法是抽象方法, 由SimpleMappingExceptionResolver实现
ModelAndView result = doResolveException(request, response, handler, ex);
//省略部分代码
return result;
}
else {
return null;
}
}
SimpleMappingExceptionResolver实现了上面AbstractHandlerExceptionResolver留下来的抽象方法doResolveException来处理异常的方法, 实现如下:
protected ModelAndView doResolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
//根据异常类型决定viewName
String viewName = determineViewName(ex, request);
if (viewName != null) {
//根据viewName决定statusCode
Integer statusCode = determineStatusCode(request, viewName);
if (statusCode != null) {
applyStatusCodeIfPossible(request, response, statusCode);
}
//根据viewName, ex, request生成ModelAndView
return getModelAndView(viewName, ex, request);
}
else {
return null;
}
}
上面我们可以看到通过determineViewName来决定什么类型的异常对应什么样的viewName, 调用determineStatusCode根据viewName来确定statusCode, 我们看看这两个方法是怎么决定的:
protected String determineViewName(Exception ex, HttpServletRequest request) {
String viewName = null;
//如果该ex的类型是我们排除掉的异常, 直接return null;
if (this.excludedExceptions != null) {
for (Class<?> excludedEx : this.excludedExceptions) {
if (excludedEx.equals(ex.getClass())) {
return null;
}
}
}
// 匹配该异常对应的viewName
if (this.exceptionMappings != null) {
//传入了this.exceptionMappings
//类型为private Properties exceptionMappings;
//所以我们只需要通过该properties来设置viewName和ex类型的对应关系
viewName = findMatchingViewName(this.exceptionMappings, ex);
}
// 如果viewName为null,并且设置了默认的错误页面
if (viewName == null && this.defaultErrorView != null) {
//省略部分代码
//返回默认错误页面
viewName = this.defaultErrorView;
}
return viewName;
}
//根据viewName 获取 statusCode
protected Integer determineStatusCode(HttpServletRequest request, String viewName) {
if (this.statusCodes.containsKey(viewName)) {
return this.statusCodes.get(viewName);
}
return this.defaultStatusCode;
}
上述代码我们发现, 只需要通过一个Properties类型来设置异常类型和viewName的对应关系
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver(){
SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
//表示这个处理器不处理空指针异常和数组越界
resolver.setExcludedExceptions(NullPointerException.class, ArrayIndexOutOfBoundsException.class);
//设置我们想要处理的异常和对应的异常页面viewName
Properties properties = new Properties();
properties.setProperty("org.apache.shiro.authz.AuthorizationException", "unauthorized");
resolver.setExceptionMappings(properties);
//设置默认的异常处理页面
resolver.setDefaultErrorView("error");
//设置默认的异常状态码
resolver.setDefaultStatusCode(503);
//设置不同错误页面对应的状态码
Properties properties1 = new Properties();
properties1.setProperty("unauthorized", "300");
resolver.setStatusCodes(properties1);
return resolver;
}
2. 实现HandlerExceptionResolver
实现该方法
ModelAndView resolveException(
HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
3. 使用@ExceptionHandler
@RestController
public class UserInfoController {
@Autowired
UserInfoService service;
@RequestMapping("test")
public String test(){
throw new RuntimeException("baocuola");
}
@ExceptionHandler(RuntimeException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String exception(RuntimeException ex){
Map<String, String> map = new HashMap<>();
map.put("code", "400");
map.put("message", ex.getMessage());
return new Gson().toJson(map);
}
}
当我们访问"/test"的时候, test方法抛出RuntimeException, 因为exception方法声明拦截RuntimeException, 所以将会调用exception方法.
你也可以指定多个需要处理的异常类型,比如这样@ExceptionHandler(value = {MissingServletRequestParameterException.class,BindException.class}),这样就会处理多个异常了。
因为UserInfoController被@RestController修饰, 所以返回的是json数据, 当只是使用普通的@Controller修饰的时候, 返回值会被解析成错误页面, 这就跟普通的Controller中的方法一样.
但是这样只是在当前Controller里面起作用, 对于其他Controller抛出的异常是不会进行处理的.
如果想在所有的Controller里面统一处理异常的话,可以用@ControllerAdvice来创建一个专门处理的类
@ControllerAdvice
public class AdviceController {
@ResponseBody
@@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(RuntimeException.class)
public String exception(RuntimeException ex){
Map<String, String> map = new HashMap<>();
map.put("code", "300");
map.put("message", ex.getMessage());
return new Gson().toJson(map);
}
}
这样不管是哪个Controller抛出异常都可以被该exception所拦截
参考: https://blog.youkuaiyun.com/liujia120103/article/details/75126124