1. @ControllerAdvice(basePackageClasses = AcmeController.class)
  2. public class AcmeControllerAdvice extends ResponseEntityExceptionHandler {
  3.  
  4. @ExceptionHandler(YourException.class)
  5. @ResponseBody
  6. ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
  7. HttpStatus status = getStatus(request);
  8. return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
  9. }
  10.  
  11. private HttpStatus getStatus(HttpServletRequest request) {
  12. Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
  13. if (statusCode == null) {
  14. return HttpStatus.INTERNAL_SERVER_ERROR;
  15. }
  16. return HttpStatus.valueOf(statusCode);
  17. }
  18.  
  19. }

https://docs.spring.io/spring-boot/docs/2.0.0.RELEASE/reference/htmlsingle/

下图中,我画出了Spring MVC中,跟异常处理相关的主要类和接口。

1.包含文件

  • spring.xml
  • messages_zh_CN.properties
  • messages_en_US.properties
  • ExceptionHandle.java
  • XXController.java

2.文件内容

  • spring.xml

    1. <mvc:annotation-driven validator="validator" >
    2. <mvc:message-converters>
    3. <ref bean="stringHttpMessageConverter" />
    4. </mvc:message-converters>
    5. </mvc:annotation-driven>
    6. <!--避免错误信息是乱码-->
    7. <util:list id="messageConverters">
    8. <ref bean="stringHttpMessageConverter" />
    9. </util:list>
    10. <bean id="stringHttpMessageConverter"
    11. class="org.springframework.http.converter.StringHttpMessageConverter">
    12. <constructor-arg value="UTF-8" index="0"/>
    13. <property name="supportedMediaTypes">
    14. <list>
    15. <!--项目中返回都是转成json格式的string,所以是text类型-->
    16. <value>text/plain;charset=UTF-8</value>
    17. <value>text/html;charset=UTF-8</value>
    18. </list>
    19. </property>
    20. </bean>
    21. <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
    22. <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
    23. <property name="validationMessageSource" ref="messageSource"/>
    24. </bean>
    25. <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    26. <property name="basenames">
    27. <list>
    28. <value>classpath:messages</value> <value>classpath:org/hibernate/validator/ValidationMessages</value>
    29. </list>
    30. </property>
    31. <property name="useCodeAsDefaultMessage" value="false"/>
    32. <property name="defaultEncoding" value="UTF-8"/>
    33. <property name="cacheSeconds" value="60"/>
    34. </bean>
  • messages_zh_CN.properties

    1. #注意了,如果你的启动jvm是local是US他会去读取messages_en_US.properties文件
    2. #如果不存在,就不会解析{uname.null}
    3. uname.null=用户名不能为空
  • ExceptionHandle.java

    1. import java.util.stream.Collectors;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.validation.BindException;
    4. import org.springframework.validation.ObjectError;
    5. import org.springframework.web.bind.annotation.ControllerAdvice;
    6. import org.springframework.web.bind.annotation.ExceptionHandler;
    7. import org.springframework.web.bind.annotation.ResponseBody;
    8. import
    9. org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
    10. import com.xx.customquery.exception.BusinessException;
    11. import com.xx.customquery.exception.CommonMessageCode.SystemMessageCode;
    12. import com.xx.customquery.util.Result;
    13. @ControllerAdvice
    14. @Slf4j
    15. public class ExceptionHandle {
    16. @ExceptionHandler(BindException.class)
    17. @ResponseBody
    18. public String processValidationError(BindException ex) {
    19. log.error(ex.getMessage(), ex);
    20. String result = ex
    21. .getBindingResult()
    22. .getAllErrors()
    23. .stream()
    24. .map(ObjectError::getDefaultMessage)
    25. .collect(Collectors.joining(","));
    26. return Result.returnParamFailResult(result);
    27. }
    28. @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    29. @ResponseBody
    30. public String processArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
    31. log.error(ex.getMessage(), ex);
    32. return Result.returnParamFailResult(ex.getMessage());
    33. }
    34. @ExceptionHandler(BusinessException.class)
    35. @ResponseBody
    36. public String processBusinessException(BusinessException ex) {
    37. log.error(ex.getMessage(), ex);
    38. return Result.returnFailResult(ex);
    39. }
    40. @ExceptionHandler(Throwable.class)
    41. @ResponseBody
    42. public String processException(Throwable ex) {
    43. log.error(ex.getMessage(), ex);
    44. return Result.returnFailResult(new BusinessException(SystemMessageCode.ERROR_SYSTEM));
    45. }
    46. }
  • Result.java

    1. import com.google.gson.Gson;
    2. import com.xx.customquery.exception.BusinessException;
    3. public class Result<E> {
    4. public final static int SUCCESS = 0;
    5. public final static int FAIL = 999;
    6. public final static int PARAM_FAIL = -1;
    7. private final static Gson GSON = new GsonBuilder().disableHtmlEscaping().create();
    8. public static String returnSuccResult(){
    9. return GSON.toJson(new Result<String>(SUCCESS, "", ""));
    10. }
    11. public static String returnFailResult(String message){
    12. return GSON.toJson(new Result<String>(FAIL, message, ""));
    13. }
    14. public static String returnParamFailResult(String message){
    15. return GSON.toJson(new Result<String>(PARAM_FAIL, message, ""));
    16. }
    17. public static String returnFailResult(BusinessException exception){
    18. return GSON.toJson(new Result<String>(exception.getCode(), exception.getMessage(), ""));
    19. }
    20. public static <T> String returnDataResult(T data){
    21. return GSON.toJson(new Result<T>(SUCCESS, "", data));
    22. }
    23. private int code;
    24. private String message;
    25. private E data;
    26. public Result(){}
    27. public Result(int code,String message, E data){
    28. this.code = code;
    29. this.message = message;
    30. this.data = data;
    31. }
    32. public int getCode() {
    33. return code;
    34. }
    35. public void setCode(int code) {
    36. this.code = code;
    37. }
    38. public String getMessage() {
    39. return message;
    40. }
    41. public void setMessage(String message) {
    42. this.message = message;
    43. }
    44. public E getData() {
    45. return data;
    46. }
    47. public void setData(E data) {
    48. this.data = data;
    49. }
    50. }
  • XXController.java
    1. @ResponseBody
    2. @RequestMapping(value = "save", method = RequestMethod.POST)
    3. public String saveQuery(@Valid Query querys) {
    4. }
  • Query
    1. import lombok.Data;
    2. import org.hibernate.validator.constraints.NotBlank;
    3. @Data
    4. public class Queries {
    5. private long id;
    6. @NotBlank(message="{uname.null}")
    7. private String user;
    8. }

http://www.jianshu.com/p/1390dc477d92

@ControllerAdvice源码

  1. package org.springframework.web.bind.annotation;
  2. import java.lang.annotation.Annotation;
  3. import java.lang.annotation.Documented;
  4. import java.lang.annotation.ElementType;
  5. import java.lang.annotation.Retention;
  6. import java.lang.annotation.RetentionPolicy;
  7. import java.lang.annotation.Target;
  8. import org.springframework.core.annotation.AliasFor;
  9. import org.springframework.stereotype.Component;
  10. @Target(ElementType.TYPE)
  11. @Retention(RetentionPolicy.RUNTIME)
  12. @Documented
  13. @Component
  14. public @interface ControllerAdvice {
  15. @AliasFor("basePackages")
  16. String[] value() default {};
  17. @AliasFor("value")
  18. String[] basePackages() default {};
  19. Class<?>[] basePackageClasses() default {};
  20. Class<?>[] assignableTypes() default {};
  21. Class<? extends Annotation>[] annotations() default {};
  22. }

源码分析

@ ControllerAdvice是一个@ Component,
用于定义@ ExceptionHandler的,@InitBinder和@ModelAttribute方法,适用于所有使用@ RequestMapping方法,并处理所有@ RequestMapping标注方法出现异常的统一处理。

项目图片

这里写图片描述

pom.xml

  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  2. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  3. <modelVersion>4.0.0</modelVersion>
  4. <groupId>com.jege.spring.boot</groupId>
  5. <artifactId>spring-boot-controller-advice</artifactId>
  6. <version>0.0.1-SNAPSHOT</version>
  7. <packaging>jar</packaging>
  8. <name>spring-boot-controller-advice</name>
  9. <url>http://maven.apache.org</url>
  10. <!-- 公共spring-boot配置,下面依赖jar文件不用在写版本号 -->
  11. <parent>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-parent</artifactId>
  14. <version>1.4.1.RELEASE</version>
  15. <relativePath />
  16. </parent>
  17. <properties>
  18. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  19. <java.version>1.8</java.version>
  20. </properties>
  21. <dependencies>
  22. <!-- web -->
  23. <dependency>
  24. <groupId>org.springframework.boot</groupId>
  25. <artifactId>spring-boot-starter-web</artifactId>
  26. </dependency>
  27. <!-- 持久层 -->
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-data-jpa</artifactId>
  31. </dependency>
  32. <!-- h2内存数据库 -->
  33. <dependency>
  34. <groupId>com.h2database</groupId>
  35. <artifactId>h2</artifactId>
  36. <scope>runtime</scope>
  37. </dependency>
  38. <!-- 测试 -->
  39. <dependency>
  40. <groupId>org.springframework.boot</groupId>
  41. <artifactId>spring-boot-starter-test</artifactId>
  42. <!-- 只在test测试里面运行 -->
  43. <scope>test</scope>
  44. </dependency>
  45. </dependencies>
  46. <build>
  47. <finalName>spring-boot-controller-advice</finalName>
  48. <plugins>
  49. <plugin>
  50. <groupId>org.apache.maven.plugins</groupId>
  51. <artifactId>maven-compiler-plugin</artifactId>
  52. <configuration>
  53. <source>${java.version}</source>
  54. <target>${java.version}</target>
  55. </configuration>
  56. </plugin>
  57. </plugins>
  58. </build>
  59. </project>

全局异常处理类CommonExceptionAdvice

  1. package com.jege.spring.boot.exception;
  2. import java.util.Set;
  3. import javax.validation.ConstraintViolation;
  4. import javax.validation.ConstraintViolationException;
  5. import javax.validation.ValidationException;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import org.springframework.dao.DataIntegrityViolationException;
  9. import org.springframework.http.HttpStatus;
  10. import org.springframework.http.converter.HttpMessageNotReadableException;
  11. import org.springframework.validation.BindException;
  12. import org.springframework.validation.BindingResult;
  13. import org.springframework.validation.FieldError;
  14. import org.springframework.web.HttpMediaTypeNotSupportedException;
  15. import org.springframework.web.HttpRequestMethodNotSupportedException;
  16. import org.springframework.web.bind.MethodArgumentNotValidException;
  17. import org.springframework.web.bind.MissingServletRequestParameterException;
  18. import org.springframework.web.bind.annotation.ControllerAdvice;
  19. import org.springframework.web.bind.annotation.ExceptionHandler;
  20. import org.springframework.web.bind.annotation.ResponseBody;
  21. import org.springframework.web.bind.annotation.ResponseStatus;
  22. import com.jege.spring.boot.json.AjaxResult;
  23. /**
  24. * @author JE哥
  25. * @email 1272434821@qq.com
  26. * @description:全局异常处理
  27. */
  28. @ControllerAdvice
  29. @ResponseBody
  30. public class CommonExceptionAdvice {
  31. private static Logger logger = LoggerFactory.getLogger(CommonExceptionAdvice.class);
  32. /**
  33. * 400 - Bad Request
  34. */
  35. @ResponseStatus(HttpStatus.BAD_REQUEST)
  36. @ExceptionHandler(MissingServletRequestParameterException.class)
  37. public AjaxResult handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
  38. logger.error("缺少请求参数", e);
  39. return new AjaxResult().failure("required_parameter_is_not_present");
  40. }
  41. /**
  42. * 400 - Bad Request
  43. */
  44. @ResponseStatus(HttpStatus.BAD_REQUEST)
  45. @ExceptionHandler(HttpMessageNotReadableException.class)
  46. public AjaxResult handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
  47. logger.error("参数解析失败", e);
  48. return new AjaxResult().failure("could_not_read_json");
  49. }
  50. /**
  51. * 400 - Bad Request
  52. */
  53. @ResponseStatus(HttpStatus.BAD_REQUEST)
  54. @ExceptionHandler(MethodArgumentNotValidException.class)
  55. public AjaxResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
  56. logger.error("参数验证失败", e);
  57. BindingResult result = e.getBindingResult();
  58. FieldError error = result.getFieldError();
  59. String field = error.getField();
  60. String code = error.getDefaultMessage();
  61. String message = String.format("%s:%s", field, code);
  62. return new AjaxResult().failure(message);
  63. }
  64. /**
  65. * 400 - Bad Request
  66. */
  67. @ResponseStatus(HttpStatus.BAD_REQUEST)
  68. @ExceptionHandler(BindException.class)
  69. public AjaxResult handleBindException(BindException e) {
  70. logger.error("参数绑定失败", e);
  71. BindingResult result = e.getBindingResult();
  72. FieldError error = result.getFieldError();
  73. String field = error.getField();
  74. String code = error.getDefaultMessage();
  75. String message = String.format("%s:%s", field, code);
  76. return new AjaxResult().failure(message);
  77. }
  78. /**
  79. * 400 - Bad Request
  80. */
  81. @ResponseStatus(HttpStatus.BAD_REQUEST)
  82. @ExceptionHandler(ConstraintViolationException.class)
  83. public AjaxResult handleServiceException(ConstraintViolationException e) {
  84. logger.error("参数验证失败", e);
  85. Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
  86. ConstraintViolation<?> violation = violations.iterator().next();
  87. String message = violation.getMessage();
  88. return new AjaxResult().failure("parameter:" + message);
  89. }
  90. /**
  91. * 400 - Bad Request
  92. */
  93. @ResponseStatus(HttpStatus.BAD_REQUEST)
  94. @ExceptionHandler(ValidationException.class)
  95. public AjaxResult handleValidationException(ValidationException e) {
  96. logger.error("参数验证失败", e);
  97. return new AjaxResult().failure("validation_exception");
  98. }
  99. /**
  100. * 405 - Method Not Allowed
  101. */
  102. @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
  103. @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  104. public AjaxResult handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
  105. logger.error("不支持当前请求方法", e);
  106. return new AjaxResult().failure("request_method_not_supported");
  107. }
  108. /**
  109. * 415 - Unsupported Media Type
  110. */
  111. @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
  112. @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
  113. public AjaxResult handleHttpMediaTypeNotSupportedException(Exception e) {
  114. logger.error("不支持当前媒体类型", e);
  115. return new AjaxResult().failure("content_type_not_supported");
  116. }
  117. /**
  118. * 500 - Internal Server Error
  119. */
  120. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  121. @ExceptionHandler(ServiceException.class)
  122. public AjaxResult handleServiceException(ServiceException e) {
  123. logger.error("业务逻辑异常", e);
  124. return new AjaxResult().failure("业务逻辑异常:" + e.getMessage());
  125. }
  126. /**
  127. * 500 - Internal Server Error
  128. */
  129. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  130. @ExceptionHandler(Exception.class)
  131. public AjaxResult handleException(Exception e) {
  132. logger.error("通用异常", e);
  133. return new AjaxResult().failure("通用异常:" + e.getMessage());
  134. }
  135. /**
  136. * 操作数据库出现异常:名称重复,外键关联
  137. */
  138. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  139. @ExceptionHandler(DataIntegrityViolationException.class)
  140. public AjaxResult handleException(DataIntegrityViolationException e) {
  141. logger.error("操作数据库出现异常:", e);
  142. return new AjaxResult().failure("操作数据库出现异常:字段重复、有外键关联等");
  143. }
  144. }

自定义异常ServiceException

  1. package com.jege.spring.boot.exception;
  2. /**
  3. * @author JE哥
  4. * @email 1272434821@qq.com
  5. * @description:自定义异常类
  6. */
  7. public class ServiceException extends RuntimeException {
  8. public ServiceException(String msg) {
  9. super(msg);
  10. }
  11. }

不需要application.properties

控制器AdviceController

  1. package com.jege.spring.boot.controller;
  2. import java.util.List;
  3. import org.springframework.web.bind.annotation.RequestMapping;
  4. import org.springframework.web.bind.annotation.RestController;
  5. import com.jege.spring.boot.exception.ServiceException;
  6. /**
  7. * @author JE哥
  8. * @email 1272434821@qq.com
  9. * @description:全局异常处理演示入口
  10. */
  11. @RestController
  12. public class AdviceController {
  13. @RequestMapping("/hello1")
  14. public String hello1() {
  15. int i = 1 / 0;
  16. return "hello";
  17. }
  18. @RequestMapping("/hello2")
  19. public String hello2(Long id) {
  20. String string = null;
  21. string.length();
  22. return "hello";
  23. }
  24. @RequestMapping("/hello3")
  25. public List<String> hello3() {
  26. throw new ServiceException("test");
  27. }
  28. }

访问

源码地址

https://github.com/je-ge/spring-boot

http://www.jianshu.com/p/5c5601789626

在Spring MVC中,所有用于处理在请求映射和请求处理过程中抛出的异常的类,都要实现HandlerExceptionResolver接口。AbstractHandlerExceptionResolver实现该接口和Orderd接口,是HandlerExceptionResolver类的实现的基类。ResponseStatusExceptionResolver等具体的异常处理类均在AbstractHandlerExceptionResolver之上,实现了具体的异常处理方式。一个基于Spring MVC的Web应用程序中,可以存在多个实现了HandlerExceptionResolver的异常处理类,他们的执行顺序,由其order属性决定, order值越小,越是优先执行, 在执行到第一个返回不是null的ModelAndView的Resolver时,不再执行后续的尚未执行的Resolver的异常处理方法。。

下面我逐个介绍一下SpringMVC提供的这些异常处理类的功能。

DefaultHandlerExceptionResolver

HandlerExceptionResolver接口的默认实现,基本上是Spring MVC内部使用,用来处理Spring定义的各种标准异常,将其转化为相对应的HTTP Status Code。其处理的异常类型有:

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

ResponseStatusExceptionResolver

用来支持ResponseStatus的使用,处理使用了ResponseStatus注解的异常,根据注解的内容,返回相应的HTTP Status Code和内容给客户端。如果Web应用程序中配置了ResponseStatusExceptionResolver,那么我们就可以使用ResponseStatus注解来注解我们自己编写的异常类,并在Controller中抛出该异常类,之后ResponseStatusExceptionResolver就会自动帮我们处理剩下的工作。

这是一个自己编写的异常,用来表示订单不存在:

  1. @ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order") // 404
  2. public class OrderNotFoundException extends RuntimeException {
  3. // ...
  4. }

这是一个使用该异常的Controller方法:

  1. @RequestMapping(value="/orders/{id}", method=GET)
  2. public String showOrder(@PathVariable("id") long id, Model model) {
  3. Order order = orderRepository.findOrderById(id);
  4. if (order == null) throw new OrderNotFoundException(id);
  5. model.addAttribute(order);
  6. return "orderDetail";
  7. }

这样,当OrderNotFoundException被抛出时,ResponseStatusExceptionResolver会返回给客户端一个HTTP Status Code为404的响应。

AnnotationMethodHandlerExceptionResolver和ExceptionHandlerExceptionResolver

用来支持ExceptionHandler注解,使用被ExceptionHandler注解所标记的方法来处理异常。其中AnnotationMethodHandlerExceptionResolver在3.0版本中开始提供,ExceptionHandlerExceptionResolver在3.1版本中开始提供,从3.2版本开始,Spring推荐使用ExceptionHandlerExceptionResolver。
如果配置了AnnotationMethodHandlerExceptionResolver和ExceptionHandlerExceptionResolver这两个异常处理bean之一,那么我们就可以使用ExceptionHandler注解来处理异常。

下面是几个ExceptionHandler注解的使用例子:

  1. @Controller
  2. public class ExceptionHandlingController {
  3. // @RequestHandler methods
  4. ...
  5. // 以下是异常处理方法
  6. // 将DataIntegrityViolationException转化为Http Status Code为409的响应
  7. @ResponseStatus(value=HttpStatus.CONFLICT, reason="Data integrity violation") // 409
  8. @ExceptionHandler(DataIntegrityViolationException.class)
  9. public void conflict() {
  10. // Nothing to do
  11. }
  12. // 针对SQLException和DataAccessException返回视图databaseError
  13. @ExceptionHandler({SQLException.class,DataAccessException.class})
  14. public String databaseError() {
  15. // Nothing to do. Returns the logical view name of an error page, passed to
  16. // the view-resolver(s) in usual way.
  17. // Note that the exception is _not_ available to this view (it is not added to
  18. // the model) but see "Extending ExceptionHandlerExceptionResolver" below.
  19. return "databaseError";
  20. }
  21. // 创建ModleAndView,将异常和请求的信息放入到Model中,指定视图名字,并返回该ModleAndView
  22. @ExceptionHandler(Exception.class)
  23. public ModelAndView handleError(HttpServletRequest req, Exception exception) {
  24. logger.error("Request: " + req.getRequestURL() + " raised " + exception);
  25. ModelAndView mav = new ModelAndView();
  26. mav.addObject("exception", exception);
  27. mav.addObject("url", req.getRequestURL());
  28. mav.setViewName("error");
  29. return mav;
  30. }
  31. }

需要注意的是,上面例子中的ExceptionHandler方法的作用域,只是在本Controller类中。如果需要使用ExceptionHandler来处理全局的Exception,则需要使用ControllerAdvice注解。

  1. @ControllerAdvice
  2. class GlobalDefaultExceptionHandler {
  3. public static final String DEFAULT_ERROR_VIEW = "error";
  4. @ExceptionHandler(value = Exception.class)
  5. public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
  6. // 如果异常使用了ResponseStatus注解,那么重新抛出该异常,Spring框架会处理该异常。
  7. if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
  8. throw e;
  9. // 否则创建ModleAndView,处理该异常。
  10. ModelAndView mav = new ModelAndView();
  11. mav.addObject("exception", e);
  12. mav.addObject("url", req.getRequestURL());
  13. mav.setViewName(DEFAULT_ERROR_VIEW);
  14. return mav;
  15. }
  16. }

SimpleMappingExceptionResolver

提供了将异常映射为视图的能力,高度可定制化。其提供的能力有:

  1. 根据异常的类型,将异常映射到视图;
  2. 可以为不符合处理条件没有被处理的异常,指定一个默认的错误返回;
  3. 处理异常时,记录log信息;
  4. 指定需要添加到Modle中的Exception属性,从而在视图中展示该属性。
  1. @Configuration
  2. @EnableWebMvc
  3. public class MvcConfiguration extends WebMvcConfigurerAdapter {
  4. @Bean(name="simpleMappingExceptionResolver")
  5. public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
  6. SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
  7. Properties mappings = new Properties();
  8. mappings.setProperty("DatabaseException", "databaseError");
  9. mappings.setProperty("InvalidCreditCardException", "creditCardError");
  10. r.setExceptionMappings(mappings); // 默认为空
  11. r.setDefaultErrorView("error"); // 默认没有
  12. r.setExceptionAttribute("ex");
  13. r.setWarnLogCategory("example.MvcLogger");
  14. return r;
  15. }
  16. ...
  17. }

自定义ExceptionResolver

Spring MVC的异常处理非常的灵活,如果提供的ExceptionResolver类不能满足使用,我们可以实现自己的异常处理类。可以通过继承SimpleMappingExceptionResolver来定制Mapping的方式和能力,也可以直接继承AbstractHandlerExceptionResolver来实现其它类型的异常处理类。

Spring MVC是如何创建和使用这些Resolver的?

首先看Spring MVC是怎么加载异常处理bean的。

  1. Spring MVC有两种加载异常处理类的方式,一种是根据类型,这种情况下,会加载ApplicationContext下所有实现了ExceptionResolver接口的bean,并根据其order属性排序,依次调用;一种是根据名字,这种情况下会加载ApplicationContext下,名字为handlerExceptionResolver的bean。
  2. 不管使用那种加载方式,如果在ApplicationContext中没有找到异常处理bean,那么Spring MVC会加载默认的异常处理bean。
  3. 默认的异常处理bean定义在DispatcherServlet.properties中。
  1. org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
  2. org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
  3. org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

以下代码摘自ispatcherServlet,描述了异常处理类的加载过程:

  1. /**
  2. * Initialize the HandlerMappings used by this class.
  3. * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
  4. * we default to BeanNameUrlHandlerMapping.
  5. */
  6. private void initHandlerMappings(ApplicationContext context) {
  7. this.handlerMappings = null;
  8. if (this.detectAllHandlerMappings) {
  9. // Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
  10. Map<String, HandlerMapping> matchingBeans =
  11. BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
  12. if (!matchingBeans.isEmpty()) {
  13. this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
  14. // We keep HandlerMappings in sorted order.
  15. OrderComparator.sort(this.handlerMappings);
  16. }
  17. }
  18. else {
  19. try {
  20. HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
  21. this.handlerMappings = Collections.singletonList(hm);
  22. }
  23. catch (NoSuchBeanDefinitionException ex) {
  24. // Ignore, we'll add a default HandlerMapping later.
  25. }
  26. }
  27. // Ensure we have at least one HandlerMapping, by registering
  28. // a default HandlerMapping if no other mappings are found.
  29. if (this.handlerMappings == null) {
  30. this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
  31. if (logger.isDebugEnabled()) {
  32. logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
  33. }
  34. }
  35. }

然后看Spring MVC是怎么使用异常处理bean的。

  1. Spring MVC把请求映射和处理过程放到try catch中,捕获到异常后,使用异常处理bean进行处理。
  2. 所有异常处理bean按照order属性排序,在处理过程中,遇到第一个成功处理异常的异常处理bean之后,不再调用后续的异常处理bean。

以下代码摘自DispatcherServlet,描述了处理异常的过程。

  1. /**
  2. * Process the actual dispatching to the handler.
  3. * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
  4. * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
  5. * to find the first that supports the handler class.
  6. * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
  7. * themselves to decide which methods are acceptable.
  8. * @param request current HTTP request
  9. * @param response current HTTP response
  10. * @throws Exception in case of any kind of processing failure
  11. */
  12. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  13. HttpServletRequest processedRequest = request;
  14. HandlerExecutionChain mappedHandler = null;
  15. boolean multipartRequestParsed = false;
  16. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  17. try {
  18. ModelAndView mv = null;
  19. Exception dispatchException = null;
  20. try {
  21. processedRequest = checkMultipart(request);
  22. multipartRequestParsed = (processedRequest != request);
  23. // Determine handler for the current request.
  24. mappedHandler = getHandler(processedRequest);
  25. if (mappedHandler == null || mappedHandler.getHandler() == null) {
  26. noHandlerFound(processedRequest, response);
  27. return;
  28. }
  29. // Determine handler adapter for the current request.
  30. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  31. // Process last-modified header, if supported by the handler.
  32. String method = request.getMethod();
  33. boolean isGet = "GET".equals(method);
  34. if (isGet || "HEAD".equals(method)) {
  35. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  36. if (logger.isDebugEnabled()) {
  37. logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
  38. }
  39. if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
  40. return;
  41. }
  42. }
  43. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  44. return;
  45. }
  46. // Actually invoke the handler.
  47. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  48. if (asyncManager.isConcurrentHandlingStarted()) {
  49. return;
  50. }
  51. applyDefaultViewName(request, mv);
  52. mappedHandler.applyPostHandle(processedRequest, response, mv);
  53. }
  54. catch (Exception ex) {
  55. dispatchException = ex;
  56. }
  57. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  58. }
  59. catch (Exception ex) {
  60. triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  61. }
  62. catch (Error err) {
  63. triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
  64. }
  65. finally {
  66. if (asyncManager.isConcurrentHandlingStarted()) {
  67. // Instead of postHandle and afterCompletion
  68. if (mappedHandler != null) {
  69. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  70. }
  71. }
  72. else {
  73. // Clean up any resources used by a multipart request.
  74. if (multipartRequestParsed) {
  75. cleanupMultipart(processedRequest);
  76. }
  77. }
  78. }
  79. }
  80. /**
  81. * Determine an error ModelAndView via the registered HandlerExceptionResolvers.
  82. * @param request current HTTP request
  83. * @param response current HTTP response
  84. * @param handler the executed handler, or {@code null} if none chosen at the time of the exception
  85. * (for example, if multipart resolution failed)
  86. * @param ex the exception that got thrown during handler execution
  87. * @return a corresponding ModelAndView to forward to
  88. * @throws Exception if no error ModelAndView found
  89. */
  90. protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
  91. Object handler, Exception ex) throws Exception {
  92. // Check registered HandlerExceptionResolvers...
  93. ModelAndView exMv = null;
  94. for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
  95. exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
  96. if (exMv != null) {
  97. break;
  98. }
  99. }
  100. if (exMv != null) {
  101. if (exMv.isEmpty()) {
  102. request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
  103. return null;
  104. }
  105. // We might still need view name translation for a plain error model...
  106. if (!exMv.hasView()) {
  107. exMv.setViewName(getDefaultViewName(request));
  108. }
  109. if (logger.isDebugEnabled()) {
  110. logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
  111. }
  112. WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
  113. return exMv;
  114. }
  115. throw ex;
  116. }

何时该使用何种ExceptionResolver?

Spring提供了很多选择和非常灵活的使用方式,下面是一些使用建议:

  1. 如果自定义异常类,考虑加上ResponseStatus注解;
  2. 对于没有ResponseStatus注解的异常,可以通过使用ExceptionHandler+ControllerAdvice注解,或者通过配置SimpleMappingExceptionResolver,来为整个Web应用提供统一的异常处理。
  3. 如果应用中有些异常处理方式,只针对特定的Controller使用,那么在这个Controller中使用ExceptionHandler注解。
  4. 不要使用过多的异常处理方式,不然的话,维护起来会很苦恼,因为异常的处理分散在很多不同的地方。

http://www.cnblogs.com/xinzhao/p/4902295.html

Spring MVC异常处理详解(转)的更多相关文章

  1. Spring MVC异常处理详解

    Spring MVC中异常处理的类体系结构 下图中,我画出了Spring MVC中,跟异常处理相关的主要类和接口. 在Spring MVC中,所有用于处理在请求映射和请求处理过程中抛出的异常的类,都要 ...

  2. Spring MVC异常处理详解 ExceptionHandler good

    @ControllerAdvice(basePackageClasses = AcmeController.class) public class AcmeControllerAdvice exten ...

  3. Spring Boot异常处理详解

    在Spring MVC异常处理详解中,介绍了Spring MVC的异常处理体系,本文将讲解在此基础上Spring Boot为我们做了哪些工作.下图列出了Spring Boot中跟MVC异常处理相关的类 ...

  4. Spring MVC配置详解(3)

    一.Spring MVC环境搭建:(Spring 2.5.6 + Hibernate 3.2.0) 1. jar包引入 Spring 2.5.6:spring.jar.spring-webmvc.ja ...

  5. spring mvc 注解详解

    1.@Controller 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ...

  6. spring mvc DispatcherServlet详解之前传---FrameworkServlet

    做项目时碰到Controller不能使用aop进行拦截,从网上搜索得知:使用spring mvc 启动了两个context:applicationContext 和WebapplicationCont ...

  7. (转载)spring mvc DispatcherServlet详解之一---处理请求深入解析

    要深入理解spring mvc的工作流程,就需要先了解spring mvc的架构: 从上图可以看到 前端控制器DispatcherServlet在其中起着主导作用,理解了DispatcherServl ...

  8. spring MVC配置详解

    现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了.不过 ...

  9. Spring mvc 配置详解

    现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了.不过 ...

随机推荐

  1. 足球和oracle列(4):巴西惨败于德国,认为,差额RAC拓扑控制!

    足球与oracle系列(4):从巴西慘败于德国,想到,差异的RAC拓扑对照! 前期回想: 本来想说今晚,回头一想,应该是今早第二场半决赛就要开战了!先来回味一下之前的比赛,本届8支小组赛第一名已经所有 ...

  2. MySQL 最经常使用的一千行

    /* 启动MySQL */ net start mysql /* 连接和断开server */ mysql -h 住址 -P port -u username -p password /* 跳过许可认 ...

  3. 191. Number of 1 Bits Leetcode Python

    Write a function that takes an unsigned integer and returns the number of '1' bits it has (also know ...

  4. FTP文件操作之上传文件

    上传文件是一个比较常用的功能,前段时间就做了一个上传图片的模块.开始采用的是共享文件夹的方式,后来发现这种方法不太好.于是果断将其毙掉,后来选择采用FTP的方式进行上传.个人感觉FTP的方式还是比较好 ...

  5. unity简易小地图的实现(NGUI)

    首先,我们在场景中添加一个摄像机叫做minimapCamera, 把上面的Audio Listener组件去掉,调整摄像机位置,将其置于角色正上方,如图 新建一个Texture我们叫做minimapT ...

  6. 从&quot;分层二进制输出&quot;至&quot;解决二进制树深度&quot;总结

    本文研究的摘要,欢迎转载,但请注明出处:http://write.blog.csdn.net/postedit/41964669 近期在刷LettCode上的算法题,发现好多题目的解题思路大体是一致的 ...

  7. 不一样的味道--Html和Xml解析、格式、遍历

    很多其它内容查看官网:http://www.tinygroup.org TinyXmlParser一切以简单.有用.高速为主. 演示样例1:Xml字符串解析 比方,我们要解析一段Xml字符串,简单例如 ...

  8. localStorge它storage事件

    随着h5患病率和mobile发展.localStorage它不再是一个陌生的词汇.我相信大多数童鞋进行了联系,并用它.但storage事件相信有很多童鞋不清晰甚至没有接触.今天我们主要谈storage ...

  9. SDUT 2498-AOE网上的关键路径(spfa+字典序路径)

    AOE网上的关键路径 Time Limit: 1000ms   Memory limit: 65536K  有疑问?点这里^_^ 题目描写叙述 一个无环的有向图称为无环图(Directed Acycl ...

  10. 交互式命令 expect

    shell尽管很强大.但是貌似无法完成交互式命令的操作,实例 ssh host 如果host而且该机没有加入信任.手动输入的时间需要password. 这样的情况下可以使用expect支持. 下面举个 ...