天天看點

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中,跟異常處理相關的主要類和接口。

Spring MVC異常處理詳解 ExceptionHandler good

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标注方法出現異常的統一處理。

項目圖檔

Spring MVC異常處理詳解 ExceptionHandler good

這裡寫圖檔描述

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 [email protected]
 * @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 [email protected]
 * @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 [email protected]
 * @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
handleBindException           

ResponseStatusExceptionResolver

用來支援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

提供了将異常映射為視圖的能力,高度可定制化。其提供的能力有:

  1. 根據異常的類型,将異常映射到視圖;
  2. 可以為不符合處理條件沒有被處理的異常,指定一個預設的錯誤傳回;
  3. 處理異常時,記錄log資訊;
  4. 指定需要添加到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的。

  1. Spring MVC有兩種加載異常處理類的方式,一種是根據類型,這種情況下,會加載ApplicationContext下所有實作了ExceptionResolver接口的bean,并根據其order屬性排序,依次調用;一種是根據名字,這種情況下會加載ApplicationContext下,名字為handlerExceptionResolver的bean。
  2. 不管使用那種加載方式,如果在ApplicationContext中沒有找到異常處理bean,那麼Spring MVC會加載預設的異常處理bean。
  3. 預設的異常處理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的。

  1. Spring MVC把請求映射和處理過程放到try catch中,捕獲到異常後,使用異常處理bean進行處理。
  2. 所有異常處理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提供了很多選擇和非常靈活的使用方式,下面是一些使用建議:

  1. 如果自定義異常類,考慮加上ResponseStatus注解;
  2. 對于沒有ResponseStatus注解的異常,可以通過使用ExceptionHandler+ControllerAdvice注解,或者通過配置SimpleMappingExceptionResolver,來為整個Web應用提供統一的異常處理。
  3. 如果應用中有些異常處理方式,隻針對特定的Controller使用,那麼在這個Controller中使用ExceptionHandler注解。
  4. 不要使用過多的異常處理方式,不然的話,維護起來會很苦惱,因為異常的處理分散在很多不同的地方。

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