zoukankan      html  css  js  c++  java
  • 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中,跟异常处理相关的主要类和接口。

    SpringMVCExceptionResolver

    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");
      }
    }

    访问

    源码地址

    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

    https://www.cnblogs.com/softidea/p/4949620.html

  • 相关阅读:
    数据类型
    一些骚操作
    re
    多任务
    监听按钮点击事件
    监听按钮点击事件
    监听按钮点击事件
    将博客搬至博客园
    将博客搬至博客园
    将博客搬至博客园
  • 原文地址:https://www.cnblogs.com/sjqq/p/10103838.html
Copyright © 2011-2022 走看看