SpringMVC 全局异常处理

SpringMVC 全局异常处理

1. SimpleMappingExceptionResolver

  1. 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.csdn.net/liujia120103/article/details/75126124

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页