Spring MVC异常处理详解 ExceptionHandler good
@ControllerAdvice(basePackageClasses = AcmeController.class)
public class AcmeControllerAdvice extends ResponseEntityExceptionHandler { @ExceptionHandler(YourException.class)
@ResponseBody
ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
} private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
} }

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 - <mvc:annotation-driven validator="validator" >
 <mvc:message-converters>
 <ref bean="stringHttpMessageConverter" />
 </mvc:message-converters>
 </mvc:annotation-driven> <!--避免错误信息是乱码-->
 <util:list id="messageConverters">
 <ref bean="stringHttpMessageConverter" />
 </util:list> <bean id="stringHttpMessageConverter"
 class="org.springframework.http.converter.StringHttpMessageConverter">
 <constructor-arg value="UTF-8" index="0"/>
 <property name="supportedMediaTypes">
 <list>
 <!--项目中返回都是转成json格式的string,所以是text类型-->
 <value>text/plain;charset=UTF-8</value>
 <value>text/html;charset=UTF-8</value>
 </list>
 </property>
 </bean> <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
 <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
 <property name="validationMessageSource" ref="messageSource"/>
 </bean> <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
 <property name="basenames">
 <list>
 <value>classpath:messages</value> <value>classpath:org/hibernate/validator/ValidationMessages</value>
 </list>
 </property>
 <property name="useCodeAsDefaultMessage" value="false"/>
 <property name="defaultEncoding" value="UTF-8"/>
 <property name="cacheSeconds" value="60"/>
 </bean>
- messages_zh_CN.properties - #注意了,如果你的启动jvm是local是US他会去读取messages_en_US.properties文件
 #如果不存在,就不会解析{uname.null}
 uname.null=用户名不能为空
- ExceptionHandle.java - import java.util.stream.Collectors;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.BindException;
 import org.springframework.validation.ObjectError;
 import org.springframework.web.bind.annotation.ControllerAdvice;
 import org.springframework.web.bind.annotation.ExceptionHandler;
 import org.springframework.web.bind.annotation.ResponseBody;
 import
 org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
 import com.xx.customquery.exception.BusinessException;
 import com.xx.customquery.exception.CommonMessageCode.SystemMessageCode;
 import com.xx.customquery.util.Result;
 @ControllerAdvice
 @Slf4j
 public class ExceptionHandle {
 @ExceptionHandler(BindException.class)
 @ResponseBody
 public String processValidationError(BindException ex) {
 log.error(ex.getMessage(), ex);
 String result = ex
 .getBindingResult()
 .getAllErrors()
 .stream()
 .map(ObjectError::getDefaultMessage)
 .collect(Collectors.joining(",")); return Result.returnParamFailResult(result);
 } @ExceptionHandler(MethodArgumentTypeMismatchException.class)
 @ResponseBody
 public String processArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
 log.error(ex.getMessage(), ex);
 return Result.returnParamFailResult(ex.getMessage());
 } @ExceptionHandler(BusinessException.class)
 @ResponseBody
 public String processBusinessException(BusinessException ex) {
 log.error(ex.getMessage(), ex);
 return Result.returnFailResult(ex);
 } @ExceptionHandler(Throwable.class)
 @ResponseBody
 public String processException(Throwable ex) {
 log.error(ex.getMessage(), ex);
 return Result.returnFailResult(new BusinessException(SystemMessageCode.ERROR_SYSTEM));
 }
 }
- Result.java - import com.google.gson.Gson;
 import com.xx.customquery.exception.BusinessException;
 public class Result<E> {
 public final static int SUCCESS = 0;
 public final static int FAIL = 999;
 public final static int PARAM_FAIL = -1;
 private final static Gson GSON = new GsonBuilder().disableHtmlEscaping().create(); public static String returnSuccResult(){
 return GSON.toJson(new Result<String>(SUCCESS, "", ""));
 }
 public static String returnFailResult(String message){
 return GSON.toJson(new Result<String>(FAIL, message, ""));
 }
 public static String returnParamFailResult(String message){
 return GSON.toJson(new Result<String>(PARAM_FAIL, message, ""));
 }
 public static String returnFailResult(BusinessException exception){
 return GSON.toJson(new Result<String>(exception.getCode(), exception.getMessage(), ""));
 }
 public static <T> String returnDataResult(T data){
 return GSON.toJson(new Result<T>(SUCCESS, "", data));
 } private int code;
 private String message;
 private E data;
 public Result(){}
 public Result(int code,String message, E data){
 this.code = code;
 this.message = message;
 this.data = data;
 }
 public int getCode() {
 return code;
 }
 public void setCode(int code) {
 this.code = code;
 }
 public String getMessage() {
 return message;
 }
 public void setMessage(String message) {
 this.message = message;
 }
 public E getData() {
 return data;
 }
 public void setData(E data) {
 this.data = data;
 }
 }
- XXController.java
@ResponseBody
 @RequestMapping(value = "save", method = RequestMethod.POST)
 public String saveQuery(@Valid Query querys) {
 }
- Query
import lombok.Data;
 import org.hibernate.validator.constraints.NotBlank;
 @Data
 public class Queries {
 private long id;
 @NotBlank(message="{uname.null}")
 private String user;
 }
http://www.jianshu.com/p/1390dc477d92
@ControllerAdvice源码
package org.springframework.web.bind.annotation;
import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface ControllerAdvice {
    @AliasFor("basePackages")
    String[] value() default {};
    @AliasFor("value")
    String[] basePackages() default {};
    Class<?>[] basePackageClasses() default {};
    Class<?>[] assignableTypes() default {};
    Class<? extends Annotation>[] annotations() default {};
}源码分析
@ ControllerAdvice是一个@ Component,
用于定义@ ExceptionHandler的,@InitBinder和@ModelAttribute方法,适用于所有使用@ RequestMapping方法,并处理所有@ RequestMapping标注方法出现异常的统一处理。
项目图片
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.jege.spring.boot</groupId>
    <artifactId>spring-boot-controller-advice</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>spring-boot-controller-advice</name>
    <url>http://maven.apache.org</url>
    <!-- 公共spring-boot配置,下面依赖jar文件不用在写版本号 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.1.RELEASE</version>
        <relativePath />
    </parent>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 持久层 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!-- h2内存数据库 -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <!-- 只在test测试里面运行 -->
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>spring-boot-controller-advice</finalName>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>全局异常处理类CommonExceptionAdvice
package com.jege.spring.boot.exception;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.jege.spring.boot.json.AjaxResult;
/**
 * @author JE哥
 * @email 1272434821@qq.com
 * @description:全局异常处理
 */
@ControllerAdvice
@ResponseBody
public class CommonExceptionAdvice {
  private static Logger logger = LoggerFactory.getLogger(CommonExceptionAdvice.class);
  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(MissingServletRequestParameterException.class)
  public AjaxResult handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
    logger.error("缺少请求参数", e);
    return new AjaxResult().failure("required_parameter_is_not_present");
  }
  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(HttpMessageNotReadableException.class)
  public AjaxResult handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
    logger.error("参数解析失败", e);
    return new AjaxResult().failure("could_not_read_json");
  }
  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(MethodArgumentNotValidException.class)
  public AjaxResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    logger.error("参数验证失败", e);
    BindingResult result = e.getBindingResult();
    FieldError error = result.getFieldError();
    String field = error.getField();
    String code = error.getDefaultMessage();
    String message = String.format("%s:%s", field, code);
    return new AjaxResult().failure(message);
  }
  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(BindException.class)
  public AjaxResult handleBindException(BindException e) {
    logger.error("参数绑定失败", e);
    BindingResult result = e.getBindingResult();
    FieldError error = result.getFieldError();
    String field = error.getField();
    String code = error.getDefaultMessage();
    String message = String.format("%s:%s", field, code);
    return new AjaxResult().failure(message);
  }
  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(ConstraintViolationException.class)
  public AjaxResult handleServiceException(ConstraintViolationException e) {
    logger.error("参数验证失败", e);
    Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
    ConstraintViolation<?> violation = violations.iterator().next();
    String message = violation.getMessage();
    return new AjaxResult().failure("parameter:" + message);
  }
  /**
   * 400 - Bad Request
   */
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(ValidationException.class)
  public AjaxResult handleValidationException(ValidationException e) {
    logger.error("参数验证失败", e);
    return new AjaxResult().failure("validation_exception");
  }
  /**
   * 405 - Method Not Allowed
   */
  @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
  @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
  public AjaxResult handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
    logger.error("不支持当前请求方法", e);
    return new AjaxResult().failure("request_method_not_supported");
  }
  /**
   * 415 - Unsupported Media Type
   */
  @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
  @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
  public AjaxResult handleHttpMediaTypeNotSupportedException(Exception e) {
    logger.error("不支持当前媒体类型", e);
    return new AjaxResult().failure("content_type_not_supported");
  }
  /**
   * 500 - Internal Server Error
   */
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(ServiceException.class)
  public AjaxResult handleServiceException(ServiceException e) {
    logger.error("业务逻辑异常", e);
    return new AjaxResult().failure("业务逻辑异常:" + e.getMessage());
  }
  /**
   * 500 - Internal Server Error
   */
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(Exception.class)
  public AjaxResult handleException(Exception e) {
    logger.error("通用异常", e);
    return new AjaxResult().failure("通用异常:" + e.getMessage());
  }
  /**
   * 操作数据库出现异常:名称重复,外键关联
   */
  @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  @ExceptionHandler(DataIntegrityViolationException.class)
  public AjaxResult handleException(DataIntegrityViolationException e) {
    logger.error("操作数据库出现异常:", e);
    return new AjaxResult().failure("操作数据库出现异常:字段重复、有外键关联等");
  }
}自定义异常ServiceException
package com.jege.spring.boot.exception;
/**
 * @author JE哥
 * @email 1272434821@qq.com
 * @description:自定义异常类
 */
public class ServiceException extends RuntimeException {
  public ServiceException(String msg) {
    super(msg);
  }
}不需要application.properties
控制器AdviceController
package com.jege.spring.boot.controller;
import java.util.List;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.jege.spring.boot.exception.ServiceException;
/**
 * @author JE哥
 * @email 1272434821@qq.com
 * @description:全局异常处理演示入口
 */
@RestController
public class AdviceController {
  @RequestMapping("/hello1")
  public String hello1() {
    int i = 1 / 0;
    return "hello";
  }
  @RequestMapping("/hello2")
  public String hello2(Long id) {
    String string = null;
    string.length();
    return "hello";
  }
  @RequestMapping("/hello3")
  public List<String> hello3() {
    throw new ServiceException("test");
  }
}访问
- http://localhost:8080/hello1
 {"meta":{"success":false,"message":"通用异常:/ by zero"},"data":null}
- http://localhost:8080/hello2
 {"meta":{"success":false,"message":"通用异常:null"},"data":null}
- http://localhost:8080/hello3
 {"meta":{"success":false,"message":"业务逻辑异常:test"},"data":null}
源码地址
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。其处理的异常类型有:
handleNoSuchRequestHandlingMethod
handleHttpRequestMethodNotSupported
handleHttpMediaTypeNotSupported
handleMissingServletRequestParameter
handleServletRequestBindingException
handleTypeMismatch
handleHttpMessageNotReadable
handleHttpMessageNotWritable
handleMethodArgumentNotValidException
handleMissingServletRequestParameter
handleMissingServletRequestPartException
handleBindExceptionResponseStatusExceptionResolver
用来支持ResponseStatus的使用,处理使用了ResponseStatus注解的异常,根据注解的内容,返回相应的HTTP Status Code和内容给客户端。如果Web应用程序中配置了ResponseStatusExceptionResolver,那么我们就可以使用ResponseStatus注解来注解我们自己编写的异常类,并在Controller中抛出该异常类,之后ResponseStatusExceptionResolver就会自动帮我们处理剩下的工作。
这是一个自己编写的异常,用来表示订单不存在:
 @ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")  // 404
    public class OrderNotFoundException extends RuntimeException {
        // ...
    }这是一个使用该异常的Controller方法:
@RequestMapping(value="/orders/{id}", method=GET)
    public String showOrder(@PathVariable("id") long id, Model model) {
        Order order = orderRepository.findOrderById(id);
        if (order == null) throw new OrderNotFoundException(id);
        model.addAttribute(order);
        return "orderDetail";
    }这样,当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注解的使用例子:
@Controller
public class ExceptionHandlingController {
  // @RequestHandler methods
  ...
  // 以下是异常处理方法
  // 将DataIntegrityViolationException转化为Http Status Code为409的响应
  @ResponseStatus(value=HttpStatus.CONFLICT, reason="Data integrity violation")  // 409
  @ExceptionHandler(DataIntegrityViolationException.class)
  public void conflict() {
    // Nothing to do
  }
  // 针对SQLException和DataAccessException返回视图databaseError
  @ExceptionHandler({SQLException.class,DataAccessException.class})
  public String databaseError() {
    // Nothing to do.  Returns the logical view name of an error page, passed to
    // the view-resolver(s) in usual way.
    // Note that the exception is _not_ available to this view (it is not added to
    // the model) but see "Extending ExceptionHandlerExceptionResolver" below.
    return "databaseError";
  }
  // 创建ModleAndView,将异常和请求的信息放入到Model中,指定视图名字,并返回该ModleAndView
  @ExceptionHandler(Exception.class)
  public ModelAndView handleError(HttpServletRequest req, Exception exception) {
    logger.error("Request: " + req.getRequestURL() + " raised " + exception);
    ModelAndView mav = new ModelAndView();
    mav.addObject("exception", exception);
    mav.addObject("url", req.getRequestURL());
    mav.setViewName("error");
    return mav;
  }
}需要注意的是,上面例子中的ExceptionHandler方法的作用域,只是在本Controller类中。如果需要使用ExceptionHandler来处理全局的Exception,则需要使用ControllerAdvice注解。
@ControllerAdvice
class GlobalDefaultExceptionHandler {
    public static final String DEFAULT_ERROR_VIEW = "error";
    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        // 如果异常使用了ResponseStatus注解,那么重新抛出该异常,Spring框架会处理该异常。
        if (AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class) != null)
            throw e;
        // 否则创建ModleAndView,处理该异常。
        ModelAndView mav = new ModelAndView();
        mav.addObject("exception", e);
        mav.addObject("url", req.getRequestURL());
        mav.setViewName(DEFAULT_ERROR_VIEW);
        return mav;
    }
}SimpleMappingExceptionResolver
提供了将异常映射为视图的能力,高度可定制化。其提供的能力有:
- 根据异常的类型,将异常映射到视图;
- 可以为不符合处理条件没有被处理的异常,指定一个默认的错误返回;
- 处理异常时,记录log信息;
- 指定需要添加到Modle中的Exception属性,从而在视图中展示该属性。
@Configuration
@EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter {
    @Bean(name="simpleMappingExceptionResolver")
    public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        mappings.setProperty("DatabaseException", "databaseError");
        mappings.setProperty("InvalidCreditCardException", "creditCardError");
        r.setExceptionMappings(mappings);  // 默认为空
        r.setDefaultErrorView("error");    // 默认没有
        r.setExceptionAttribute("ex");
        r.setWarnLogCategory("example.MvcLogger");
        return r;
    }
    ...
}自定义ExceptionResolver
Spring MVC的异常处理非常的灵活,如果提供的ExceptionResolver类不能满足使用,我们可以实现自己的异常处理类。可以通过继承SimpleMappingExceptionResolver来定制Mapping的方式和能力,也可以直接继承AbstractHandlerExceptionResolver来实现其它类型的异常处理类。
Spring MVC是如何创建和使用这些Resolver的?
首先看Spring MVC是怎么加载异常处理bean的。
- Spring MVC有两种加载异常处理类的方式,一种是根据类型,这种情况下,会加载ApplicationContext下所有实现了ExceptionResolver接口的bean,并根据其order属性排序,依次调用;一种是根据名字,这种情况下会加载ApplicationContext下,名字为handlerExceptionResolver的bean。
- 不管使用那种加载方式,如果在ApplicationContext中没有找到异常处理bean,那么Spring MVC会加载默认的异常处理bean。
- 默认的异常处理bean定义在DispatcherServlet.properties中。
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver以下代码摘自ispatcherServlet,描述了异常处理类的加载过程:
/**
 * Initialize the HandlerMappings used by this class.
 * <p>If no HandlerMapping beans are defined in the BeanFactory for this namespace,
 * we default to BeanNameUrlHandlerMapping.
 */
private void initHandlerMappings(ApplicationContext context) {
	this.handlerMappings = null;
	if (this.detectAllHandlerMappings) {
		// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
		Map<String, HandlerMapping> matchingBeans =
				BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
		if (!matchingBeans.isEmpty()) {
			this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values());
			// We keep HandlerMappings in sorted order.
			OrderComparator.sort(this.handlerMappings);
		}
	}
	else {
		try {
			HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
			this.handlerMappings = Collections.singletonList(hm);
		}
		catch (NoSuchBeanDefinitionException ex) {
			// Ignore, we'll add a default HandlerMapping later.
		}
	}
	// Ensure we have at least one HandlerMapping, by registering
	// a default HandlerMapping if no other mappings are found.
	if (this.handlerMappings == null) {
		this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
		if (logger.isDebugEnabled()) {
			logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default");
		}
	}
}然后看Spring MVC是怎么使用异常处理bean的。
- Spring MVC把请求映射和处理过程放到try catch中,捕获到异常后,使用异常处理bean进行处理。
- 所有异常处理bean按照order属性排序,在处理过程中,遇到第一个成功处理异常的异常处理bean之后,不再调用后续的异常处理bean。
以下代码摘自DispatcherServlet,描述了处理异常的过程。
/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
	HttpServletRequest processedRequest = request;
	HandlerExecutionChain mappedHandler = null;
	boolean multipartRequestParsed = false;
	WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
	try {
		ModelAndView mv = null;
		Exception dispatchException = null;
		try {
			processedRequest = checkMultipart(request);
			multipartRequestParsed = (processedRequest != request);
			// Determine handler for the current request.
			mappedHandler = getHandler(processedRequest);
			if (mappedHandler == null || mappedHandler.getHandler() == null) {
				noHandlerFound(processedRequest, response);
				return;
			}
			// Determine handler adapter for the current request.
			HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
			// Process last-modified header, if supported by the handler.
			String method = request.getMethod();
			boolean isGet = "GET".equals(method);
			if (isGet || "HEAD".equals(method)) {
				long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
				if (logger.isDebugEnabled()) {
					logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
				}
				if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
					return;
				}
			}
			if (!mappedHandler.applyPreHandle(processedRequest, response)) {
				return;
			}
			// Actually invoke the handler.
			mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
			if (asyncManager.isConcurrentHandlingStarted()) {
				return;
			}
			applyDefaultViewName(request, mv);
			mappedHandler.applyPostHandle(processedRequest, response, mv);
		}
		catch (Exception ex) {
			dispatchException = ex;
		}
		processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
	}
	catch (Exception ex) {
		triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
	}
	catch (Error err) {
		triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
	}
	finally {
		if (asyncManager.isConcurrentHandlingStarted()) {
			// Instead of postHandle and afterCompletion
			if (mappedHandler != null) {
				mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
			}
		}
		else {
			// Clean up any resources used by a multipart request.
			if (multipartRequestParsed) {
				cleanupMultipart(processedRequest);
			}
		}
	}
}
/**
 * Determine an error ModelAndView via the registered HandlerExceptionResolvers.
 * @param request current HTTP request
 * @param response current HTTP response
 * @param handler the executed handler, or {@code 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
 * @throws Exception if no error ModelAndView found
 */
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
		Object handler, Exception ex) throws Exception {
	// Check registered HandlerExceptionResolvers...
	ModelAndView exMv = null;
	for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
		exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
		if (exMv != null) {
			break;
		}
	}
	if (exMv != null) {
		if (exMv.isEmpty()) {
			request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
			return null;
		}
		// We might still need view name translation for a plain error model...
		if (!exMv.hasView()) {
			exMv.setViewName(getDefaultViewName(request));
		}
		if (logger.isDebugEnabled()) {
			logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
		}
		WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
		return exMv;
	}
	throw ex;
}何时该使用何种ExceptionResolver?
Spring提供了很多选择和非常灵活的使用方式,下面是一些使用建议:
- 如果自定义异常类,考虑加上ResponseStatus注解;
- 对于没有ResponseStatus注解的异常,可以通过使用ExceptionHandler+ControllerAdvice注解,或者通过配置SimpleMappingExceptionResolver,来为整个Web应用提供统一的异常处理。
- 如果应用中有些异常处理方式,只针对特定的Controller使用,那么在这个Controller中使用ExceptionHandler注解。
- 不要使用过多的异常处理方式,不然的话,维护起来会很苦恼,因为异常的处理分散在很多不同的地方。
http://www.cnblogs.com/xinzhao/p/4902295.html
https://www.cnblogs.com/softidea/p/4949620.html
Spring MVC异常处理详解 ExceptionHandler good的更多相关文章
- Spring MVC异常处理详解
		Spring MVC中异常处理的类体系结构 下图中,我画出了Spring MVC中,跟异常处理相关的主要类和接口. 在Spring MVC中,所有用于处理在请求映射和请求处理过程中抛出的异常的类,都要 ... 
- Spring MVC异常处理详解(转)
		下图中,我画出了Spring MVC中,跟异常处理相关的主要类和接口. 在Spring MVC中,所有用于处理在请求映射和请求处理过程中抛出的异常的类,都要实现HandlerExceptionReso ... 
- Spring Boot异常处理详解
		在Spring MVC异常处理详解中,介绍了Spring MVC的异常处理体系,本文将讲解在此基础上Spring Boot为我们做了哪些工作.下图列出了Spring Boot中跟MVC异常处理相关的类 ... 
- Spring MVC配置详解(3)
		一.Spring MVC环境搭建:(Spring 2.5.6 + Hibernate 3.2.0) 1. jar包引入 Spring 2.5.6:spring.jar.spring-webmvc.ja ... 
- spring mvc 注解详解
		1.@Controller 在SpringMVC 中,控制器Controller 负责处理由DispatcherServlet 分发的请求,它把用户请求的数据经过业务处理层处理之后封装成一个Model ... 
- spring mvc DispatcherServlet详解之前传---FrameworkServlet
		做项目时碰到Controller不能使用aop进行拦截,从网上搜索得知:使用spring mvc 启动了两个context:applicationContext 和WebapplicationCont ... 
- (转载)spring mvc DispatcherServlet详解之一---处理请求深入解析
		要深入理解spring mvc的工作流程,就需要先了解spring mvc的架构: 从上图可以看到 前端控制器DispatcherServlet在其中起着主导作用,理解了DispatcherServl ... 
- spring MVC配置详解
		现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了.不过 ... 
- Spring mvc 配置详解
		现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了.不过 ... 
随机推荐
- java基础篇---Servlet过滤器
			Servlet过滤器从字面上的字意理解为景观一层次的过滤处理才达到使用的要求,而其实Servlet过滤器就是服务器与客户端请求与响应的中间层组件,在实际项目开发中Servlet过滤器主要用于对浏览器的 ... 
- swift-Xcode7.x(7.1,7.2,7.3)新建playground运行不能运行
			swift-Xcode7.xhtml, body {overflow-x: initial !important;}.CodeMirror { height: auto; } .CodeMirror- ... 
- gSoap的“error LNK2001: 无法解析的外部符号 _namespaces”解决方法
			错误 2 error LNK2001: 无法解析的外部符号 _namespaces 解决方法: 1. 在工程中定义 WITH_NONAMESPACES 宏 2.尝试 "#include &q ... 
- Spring WebSocket初探1 (Spring WebSocket入门教程)<转>
			See more: Spring WebSocket reference整个例子属于WiseMenuFrameWork的一部分,可以将整个项目Clone下来,如果朋友们有需求,我可以整理一个独立的de ... 
- Java FutureTask<V> 源码分析 Android上的实现
			FutureTask类提供了可取消的异步计算,并且可以利用开始和取消计算的方法.查询计算是否完成的方法和获取计算结果的方法. 首先看一下继承关系 public class FutureTask< ... 
- Extjs4.x treepanel,treegrid 节点选择,选中某个节点
			//官方推荐 this.getModuleGrid().getRootNode().cascadeBy(function () { this.set("checked", fals ... 
- Entity Framework应用:使用EF的DataBase First模式实现数据库的增删改查
			在上一篇文章中讲解了如何生成EF的DBFirst模式,接下来讲解如何使用DBFirst模式实现数据库数据的增删改查 一.新增数据 新增一个Student,代码如下: static void Add() ... 
- js 实现遮罩某一部分
			借用一下喜马拉雅的效果图,比较一下下面的遮罩层的高度: 我们可以看到它的遮罩层一直都是只遮住的下面.开始想着怎么用CSS实现,几经测试也没弄出来,如果正在看这篇文章的你,知道如何用CSS达 ... 
- JSP字符集编码集合
			在这里,我们先说说JSP/Servlet中的几个编码的作用. 在JSP/Servlet 中主要有以下几个地方可以设置编码,pageEncoding="UTF-8".contentT ... 
- Q_UNUSED() 方法的使用
			Q_UNUSED() 没有实质性的作用,用来避免编译器警告 //比如说 int testFunc(int a, int b, int c, int d) { int e; return a+b+c; ... 
