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

xiaoxiao2021-02-27  401

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

异常处理顺序:

如果是特定异常使用DefaultHandlerExceptionResolver;

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

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

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

最后使用SimpleMappingExceptionResolver。

【HandlerExceptionResolver接口如下:】

/* * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.portlet; import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import javax.portlet.ResourceRequest; import javax.portlet.ResourceResponse; /** * Interface to be implemented by objects than can resolve exceptions thrown * during handler mapping or execution, in the typical case to error views. * Implementors are typically registered as beans in the application context. * * <p>Error views are analogous to the error page JSPs, but can be used with * any kind of exception including any checked exception, with potentially * fine-granular mappings for specific handlers. * * @author Juergen Hoeller * @author John A. Lewis * @since 2.0 */ public interface HandlerExceptionResolver { /** * Try to resolve the given exception that got thrown during on handler execution, * returning a ModelAndView that represents a specific error page if appropriate. * @param request current portlet request * @param response current portlet response * @param handler the executed handler, or null if none chosen at the time of * the exception (for example, if multipart resolution failed) * @param ex the exception that got thrown during handler execution * @return a corresponding ModelAndView to forward to, * or {@code null} for default processing */ ModelAndView resolveException( RenderRequest request, RenderResponse response, Object handler, Exception ex); /** * Try to resolve the given exception that got thrown during on handler execution, * returning a ModelAndView that represents a specific error page if appropriate. * @param request current portlet request * @param response current portlet response * @param handler the executed handler, or null if none chosen at the time of * the exception (for example, if multipart resolution failed) * @param ex the exception that got thrown during handler execution * @return a corresponding ModelAndView to forward to, * or {@code null} for default processing */ ModelAndView resolveException( ResourceRequest request, ResourceResponse response, Object handler, Exception ex); }

其实现类如下图所示:


DispatcherServlet默认装配的ExceptionResolver:

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

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


【1】ExceptionHandlerExceptionResolver

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

JavaDoc:

An AbstractHandlerMethodExceptionResolver that resolves exceptions through @ExceptionHandler methods. Support for custom argument and return value types can be added via setCustomArgumentResolvers and setCustomReturnValueHandlers. Or alternatively to re-configure all argument and return value types use setArgumentResolvers and setReturnValueHandlers(List).

在 @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方法)。


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

@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; } }

【2】ResponseStatusExceptionResolver

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

Javadoc:

A HandlerExceptionResolver that uses the @ResponseStatus annotation to map exceptions to HTTP status codes. This exception resolver is enabled by default in the DispatcherServlet and the MVC Java config and the MVC namespace.

ResponseStatus

@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ResponseStatus { /** * Alias for {@link #code}. */ @AliasFor("code") HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR; /** * The status <em>code</em> to use for the response. * <p>Default is {@link HttpStatus#INTERNAL_SERVER_ERROR}, which should * typically be changed to something more appropriate. * @since 4.2 * @see javax.servlet.http.HttpServletResponse#setStatus(int) * @see javax.servlet.http.HttpServletResponse#sendError(int) */ @AliasFor("value") HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR; /** * The <em>reason</em> to be used for the response. * @see javax.servlet.http.HttpServletResponse#sendError(int, String) */ String reason() default ""; }

API说明如下:

/** * Marks a method or exception class with the status {@link #code} and * {@link #reason} that should be returned. * * <p>The status code is applied to the HTTP response when the handler * method is invoked and overrides status information set by other means, * like {@code ResponseEntity} or {@code "redirect:"}. * * <p><strong>Warning</strong>: when using this annotation on an exception * class, or when setting the {@code reason} attribute of this annotation, * the {@code HttpServletResponse.sendError} method will be used. * * <p>With {@code HttpServletResponse.sendError}, the response is considered * complete and should not be written to any further. Furthermore, the Servlet * container will typically write an HTML error page therefore making the * use of a {@code reason} unsuitable for REST APIs. For such cases it is * preferable to use a {@link org.springframework.http.ResponseEntity} as * a return type and avoid the use of {@code @ResponseStatus} altogether. * * <p>Note that a controller class may also be annotated with * {@code @ResponseStatus} and is then inherited by all {@code @RequestMapping} * methods.

自定义异常类示例如下:

注意value和reason值与下面网页对比。 @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(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:

[FirstInterceptor] preHandle testResponseStatusExceptionResolver... [FirstInterceptor] postHandle [FirstInterceptor] afterCompletion


如果在当前 Handler 中找到 @ExceptionHandler 方法来处理当前异常, 或@ControllerAdvice 标记的类中找到 @ExceptionHandler 标记的方法处理当前异常。

则将会调用对应方法处理该异常,不会使用ResponseStatusExceptionResolver。

result as follows :

[FirstInterceptor] preHandle [出异常了-运行时异常]: com.web.springmvc.test.UserNameNotMatchPasswordException [FirstInterceptor] afterCompletion

【3】DefaultHandlerExceptionResolver

Javadoc:

Default implementation of the HandlerExceptionResolver interface that resolves standard Spring exceptions and translates them to corresponding HTTP status codes. This exception resolver is enabled by default in the common Spring org.springframework.web.servlet.DispatcherServlet.

对一些特殊的、特定的异常进行处理,主要有:

* @see org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler * @see #handleNoSuchRequestHandlingMethod * @see #handleHttpRequestMethodNotSupported * @see #handleHttpMediaTypeNotSupported * @see #handleMissingServletRequestParameter * @see #handleServletRequestBindingException * @see #handleTypeMismatch * @see #handleHttpMessageNotReadable * @see #handleHttpMessageNotWritable * @see #handleMethodArgumentNotValidException * @see #handleMissingServletRequestParameter * @see #handleMissingServletRequestPartException * @see #handleBindException

测试方法如下:

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


【4】SimpleMappingExceptionResolver

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

Javadoc:

org.springframework.web.servlet.HandlerExceptionResolver implementation that allows for mapping exception class names to view names, either for a set of given handlers or for all handlers in the DispatcherServlet. Error views are analogous to error page JSPs, but can be used with any kind of exception including any checked one, with fine-granular mappings for specific handlers.

springmvc.xml配置如下:

<!-- ******************配置使用 SimpleMappingExceptionResolver 来映射异常 *******************--> <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。

result as follows :

[FirstInterceptor] preHandle [出异常了-运行时异常]: java.lang.ArrayIndexOutOfBoundsException: 21 [FirstInterceptor] afterCompletion

【5】捕捉异常直接返回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(); } .... }
转载请注明原文地址: https://www.6miu.com/read-2801.html

最新回复(0)