SpringMVC中异常处理与ControllerAdvice捕捉全局异常

在dispatchServlet处理过程中,有时会抛出异常。SpringMVC对此进行了处理,如下图所示在processDispatchResult中提供了入口processHandlerException(request, response, handler, exception);来处理异常。
在这里插入图片描述

具体如何处理呢?SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver

HandlerExceptionResolver接口的实现类
在这里插入图片描述

  • 蓝色实线表示的是继承关系
  • 绿色虚线表示的是接口实现关系
  • 绿色实线表示的是接口与接口的关系

每个类主要方法示意图
在这里插入图片描述

【1】概述

SpringMVC通过HandlerExceptionResolver处理程序的异常,包括handler映射、数据绑定以及目标方法的执行时发生的异常。

异常处理顺序

  • 如果是特定异常使用DefaultHandlerExceptionResolver

  • 如果存在@ExceptionHandler({RuntimeException.class})注解的方法,将执行。

  • 如果同时存在@ExceptionHandler({RuntimeException.class})和类似于@ExceptionHandler({ArithmeticException.class}),则近者优先!

  • 如果不存在@ExceptionHandler,则异常尝试使用ResponseStatusExceptionResolver

  • 最后使用SimpleMappingExceptionResolver

HandlerExceptionResolver接口

由对象实现的接口,可以解决在处理程序映射或执行期间抛出的异常,在典型情况下是会跳到一个错误视图。实现者通常在应用程序上下文中注册为bean。

错误视图类似于错误页面JSP,但可以用于任何类型的异常,包括任何已检查的异常,以及特定处理程序的潜在细粒度映射(某个controller的某个方法)。

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.lang.Nullable;

public interface HandlerExceptionResolver {
   //尝试解析给定的异常并返回一个ModelAndView (一个合适的错误页面)
   //返回的ModelAndView 可能为空,表示异常已经成功解决但是不需一个错误页面,比如设置STATUS CODE
@Nullable
ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

其实现类如下图所示(可以通过Navigate -> Type Hierarchy 打开,快捷键是F4):
这里写图片描述


DispatcherServlet默认装配的ExceptionResolver

① 没有配置<mvc:annotation-driven/>

这里写图片描述

② 配置了<mvc:annotation-driven/>

这里写图片描述


【2】ExceptionHandlerExceptionResolver

主要处理handler中用@ExceptionHandler注解定义的方法。

AbstractHandlerMethodExceptionResolver可以通过@ExceptionHandler注解的方法解决异常。可以通过setCustomArgumentResolversetCustomReturnValueHandlers添加对自定义参数和返回值类型的支持。或者使用setArgumentResolverssetReturnValueHandlers重新配置所有参数和返回值类型。

相关说明如下

  • @ExceptionHandler 方法的入参中可以加入 Exception 类型的参数, 该参数即对应发生的异常对象;

  • @ExceptionHandler 方法的入参中不能传入 Map。若希望把异常信息传到页面上,需要使用 ModelAndView 作为返回值;若想直接返回JSON可以使用@ResponseBody注解;

  • @ExceptionHandler 方法标记的异常有优先级的问题.

  • @ControllerAdvice: 如果在当前 Handler 中找不到 @ExceptionHandler 方法来处理当前方法出现的异常, 则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常.

测试代码如下:

@ExceptionHandler({ArithmeticException.class})
public ModelAndView handleArithmeticException(Exception ex){
	System.out.println("出异常了: " + ex);
	ModelAndView mv = new ModelAndView("error");
	mv.addObject("exception", ex);
	return mv;
}

@RequestMapping("/testExceptionHandlerExceptionResolver")
public String testExceptionHandlerExceptionResolver(@RequestParam("i") int i){
	System.out.println("result: " + (10 / i));
	return "success";
}

当 i = 0时,抛出异常:

[FirstInterceptor] preHandle
出异常了: java.lang.ArithmeticException: / by zero
[FirstInterceptor] afterCompletion

handleArithmeticException方法捕捉该异常,并跳到error.jsp页面(同时可注意到,因为目标方法抛出异常,故拦截器不执行postHandle方法)。
这里写图片描述


如果想方法捕捉的范围更广一点,可以使用如下配置

ArithmeticException改为RuntimeException

 @ExceptionHandler({RuntimeException.class})
public ModelAndView handleArithmeticException2(Exception ex){
	System.out.println("[出异常了-运行时异常]: " + ex);
	ModelAndView mv = new ModelAndView("error");
	mv.addObject("exception", ex);
	return mv;
}
	

如果两个方法同时存在,则不会执行第二个方法(@ExceptionHandler({RuntimeException.class}))–近者优先;

如果两个方法都不存在,则将去 @ControllerAdvice 标记的类中查找 @ExceptionHandler 标记的方法来处理异常.

@ControllerAdvice
public class SpringMVCTestExceptionHandler {

	@ExceptionHandler({ArithmeticException.class})
	public ModelAndView handleArithmeticException(Exception ex){
		System.out.println("----> 出异常了: " + ex);
		ModelAndView mv = new ModelAndView("error");
		mv.addObject("exception", ex);
		return mv;
	}
	
}

【3】ResponseStatusExceptionResolver

① 响应状态码异常解析器

在异常或异常父类中找到@ResponseStatus注解,然后使用这个注解的属性进行处理。

HandlerExceptionResolver使用@ResponseStatus注解将异常映射到HTTP STATUS CODE。默认在DispatcherServlet、MVC java配置以及MVCnamespace,都可以使用该异常解析器。从4.2开始,该解析器还递归查找原因异常中存在的@ResponseStatus,从4.2.2开始,该解析器支持自定义组合注解中@ResponseStatus的属性重写。从5.0开始,该解析器支持ResponseStatusException。

ResponseStatus注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseStatus {

	@AliasFor("code")
	HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR;

	@AliasFor("value")
	HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR;

	String reason() default "";

}

该注解使用status code和异常原因reason来标记一个方法或者异常类。当调用处理程序方法时,状态代码将应用于HTTP响应,并通过其他方式覆盖状态信息,如 ResponseEntity或请求重定向redirect:。当在异常类上使用该注解或者设置注解属性reason时,将会使用HttpServletResponse.sendError方法。对于HttpServletResponse.sendError,响应被视为已完成,不应写入任何其他文件。此外,Servlet容器通常会编写一个HTML错误页面,因此使用reason不适合REST API。对于这种情况,最好使用org.springframework.http.ResponseEntity作为返回类型,并避免使用@ResponseStatus

注意,控制器类也可以用@ResponseStatus注解,然后由所有@RequestMapping注解的方法继承。


自定义异常类

① 异常类上使用注解@ResponseStatus

注意valuereason值与下面网页对比。

@ResponseStatus(value=HttpStatus.FORBIDDEN, reason="用户名和密码不匹配!")
public class UserNameNotMatchPasswordException extends RuntimeException{

	private static final long serialVersionUID = 1L;
	//...
}

在当前 Handler 中找不到 @ExceptionHandler 方法来处理当前异常, 且@ControllerAdvice 标记的类中找不到 @ExceptionHandler 标记的方法处理当前异常.

测试方法

@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i){
	if(i == 13){
		throw new UserNameNotMatchPasswordException();
	}
	System.out.println("testResponseStatusExceptionResolver...");
	
	return "success";
}
	

测试结果
这里写图片描述


在方法上使用注解@ResponseStatus

@ResponseStatus(reason="测试",value=HttpStatus.NOT_FOUND)
@RequestMapping("/testResponseStatusExceptionResolver")
public String testResponseStatusExceptionResolver(@RequestParam("i") int i){
	if(i == 13){
		throw new UserNameNotMatchPasswordException();
	}
	System.out.println("testResponseStatusExceptionResolver...");
	
	return "success";
}

方法正常调用,但是页面显示404
这里写图片描述

如果在当前 Handler 中找到 @ExceptionHandler 方法来处理当前异常, 或@ControllerAdvice 标记的类中找到 @ExceptionHandler 标记的方法处理当前异常。则将会调用对应方法处理该异常,不会使用ResponseStatusExceptionResolver

【4】DefaultHandlerExceptionResolver

HandlerExceptionResolver的默认实现,解析标准Spring MVC异常并将其转换为相应的HTTP状态代码。

ExceptionHTTP Status Code
HttpRequestMethodNotSupportedException405 (SC_METHOD_NOT_ALLOWED)
HttpMediaTypeNotSupportedException415 (SC_UNSUPPORTED_MEDIA_TYPE)
HttpMediaTypeNotAcceptableException406 (SC_NOT_ACCEPTABLE)
MissingPathVariableException500 (SC_INTERNAL_SERVER_ERROR)
MissingServletRequestParameterException400 (SC_BAD_REQUEST)
ServletRequestBindingException400 (SC_BAD_REQUEST)
ConversionNotSupportedException500 (SC_INTERNAL_SERVER_ERROR)
TypeMismatchException400 (SC_BAD_REQUEST)
HttpMessageNotReadableException400 (SC_BAD_REQUEST)
HttpMessageNotWritableException500 (SC_INTERNAL_SERVER_ERROR)
MethodArgumentNotValidException400 (SC_BAD_REQUEST)
MissingServletRequestPartException400 (SC_BAD_REQUEST)
BindException400 (SC_BAD_REQUEST)
NoHandlerFoundException404 (SC_NOT_FOUND)
AsyncRequestTimeoutException503 (SC_SERVICE_UNAVAILABLE)

测试方法

如这里指定post方法。

@RequestMapping(value="/testDefaultHandlerExceptionResolver",method=RequestMethod.POST)
public String testDefaultHandlerExceptionResolver(){
	System.out.println("testDefaultHandlerExceptionResolver...");
	return "success";
}

测试结果
这里写图片描述


【5】SimpleMappingExceptionResolver

如果想统一管理异常,就使用SimpleMappingExceptionResolver,它将异常类名映射到对应视图名。发送异常时 ,会跳转对应页面。

SimpleMappingExceptionResolverorg.springframework.web.servlet.HandlerExceptionResolver实现,该实现允许将异常类名映射到视图名,无论是对于一组给定的处理程序还是DispatcherServlet中的所有处理程序。错误视图类似于错误页面JSP,但可以用于任何类型的异常,包括任何选中的异常,以及特定处理程序的细粒度映射。

springmvc.xml配置

<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	 <!-- 定义默认的异常处理页面 -->
    <property name="defaultErrorView" value="default/error"/>
	<property name="exceptionAttribute" value="exception"></property>
	<!--特殊异常的处理方法-->
	<property name="exceptionMappings">
		<props>
		<!--数组越界异常,视图--error.jsp -->
			<prop key="java.lang.ArrayIndexOutOfBoundsException">error</prop>
		</props>
	</property>
</bean>	

后台测试代码

@RequestMapping("/testSimpleMappingExceptionResolver")
public String testSimpleMappingExceptionResolver(@RequestParam("i") int i){
	String [] vals = new String[10];
	System.out.println(vals[i]);
	return "success";
}
	

测试结果
这里写图片描述

如果在当前 Handler 中找到 @ExceptionHandler 方法来处理当前异常, 或@ControllerAdvice 标记的类中找到 @ExceptionHandler 标记的方法处理当前异常。则将会调用对应方法处理该异常,不会使用SimpleMappingExceptionResolver

【6】ControllerAdvice捕捉异常直接返回JSON

@ControllerAdvice
public class ControllerExceptionHandler {
    private final static  Logger log = LoggerFactory.getLogger(ControllerExceptionHandler.class);
 
    @ExceptionHandler(value = {BindException.class})  
	@ResponseBody
    public String bindExceptionHandler(HttpServletRequest request, Exception e) {  
		log.error(e.getMessage());
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("code", "false");
		jsonObject.put("message","非法的日期格式!");
		return jsonObject.toJSONString(); 
    }
    ....
}

【7】EmbeddedServletContainerCustomizer注入自定义错误页面

在SpringBoot中,可以通过如下配置注入自定义的errorPage。这样当出现对应的HttpStatus时,就会跳到指定页面。

@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
    return new EmbeddedServletContainerCustomizer() {
        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            ErrorPage error400Page = new ErrorPage(HttpStatus.BAD_REQUEST, "/error/400");
            ErrorPage error401Page = new ErrorPage(HttpStatus.UNAUTHORIZED, "/error/401");
            ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
            ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");

            container.addErrorPages(error400Page,error401Page, error404Page, error500Page);
        }
    };
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值