Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 328|回复: 0

Spring MVC异常处理详解 ExceptionHandler good

[复制链接]
  • TA的每日心情
    奋斗
    昨天 14:28
  • 签到天数: 796 天

    [LV.10]以坛为家III

    2050

    主题

    2108

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    724400
    发表于 2021-6-21 14:01:00 | 显示全部楼层 |阅读模式
    @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

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-10-10 14:25 , Processed in 0.069236 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表