• 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

  • 相关阅读:
    Java 入门 36 泛型深入 泛型的概述和优势 自定义泛型类 自定义泛型方法 自定义泛型接口 泛型通配符 上下限
    Java 入门34 常见的数据结构
    Java 入门 40 日志框架 项目阶段 (Java 入门 1924天 需要使用在学)
    Java 入门 31 常用API 日期与时间
    Java 入门 32 包装类 正则表达式 Arrays类 Lambda表达式枚举
    Java 入门35 List系列集合, 几黑的并发修改异常问题 LinkedList
    OpenStack yoga安装(Ubuntu)
    OpenStack命令行添加网卡
    Ceph删除pool
    ceph osd 初始化硬盘时提示OSD::mkfs: ObjectStore::mkfs failed with error (5) Input/output error
  • 原文地址:https://www.cnblogs.com/sjqq/p/10103838.html
Copyright © 2020-2023  润新知