spring全局异常处理

spring 全局异常处理机制

我们在做业务开发或者框架开发的时候,常常涉及到异常的处理。通常会定义一个和当前业务相关的统一异常,业务层涉及到异常的处理常常把异常统一抛出的控制层。控制层在每个接口中单独处理try{}catch(Exception e){} 。这样做存在以下几个问题

  • 异常捕获冗余
  • 有的开发同学可能忘了在控制层处理异常
  • 后期需要统一异常拦截时,代码改动太多

其实在spring 中我们大可不必这样做,spring 已经尽量的帮开发人员减少工作量了。今天我们一起来分析一下spring到底是如何做到的。

接口方式

public interface HandlerExceptionResolver {
	@Nullable
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

开发异常处理器只需要集成它

@Component
public class BaseRuntimeExceptionHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try{
            if(ex instanceof BizRuntimeException){
                BizRuntimeException bx = (BizRuntimeException)ex;
                response.getWriter().println(bx.getMessage());
            }else {
                response.getWriter().println(ex.getMessage());
            }
        }catch (Exception e){
            return new ModelAndView();
        }
        return new ModelAndView();
    }
}

配置成spring的bean即可

DispatcherServlet中初始化9大对象

protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
    	//初始化异常处理器
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

处理过程中可能会碰到异常经过多了异常处理器处理,这个时候需要把多个异常处理器,spring 中提供了org.springframework.web.servlet.handler.HandlerExceptionResolverComposite这个类。这个类是异常的聚合类,可以把多个异常聚合在一起。

public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {

	@Nullable
	private List<HandlerExceptionResolver> resolvers;

	private int order = Ordered.LOWEST_PRECEDENCE;

	public void setExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
		this.resolvers = exceptionResolvers;
	}

	public List<HandlerExceptionResolver> getExceptionResolvers() {
		return (this.resolvers != null ? Collections.unmodifiableList(this.resolvers) : Collections.emptyList());
	}
	public void setOrder(int order) {
		this.order = order;
	}
	@Override
	public int getOrder() {
		return this.order;
	}
	@Override
	@Nullable
	public ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {

		if (this.resolvers != null) {
			for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
				ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
				if (mav != null) {
					return mav;
				}
			}
		}
		return null;
	}

}

这个类内部维护了**private List resolvers;**这个存的就是我们自定义的异常处理器

配置异常处理器到HandlerExceptionResolverComposite
@SpringBootApplication
public class SofaRpcConsumerApplication extends WebMvcConfigurationSupport {

    public static void main(String[] args) throws IOException {
        SpringApplication.run(SofaRpcConsumerApplication.class, args);
    }

    @Autowired
    private BaseOneRuntimeExceptionHandlerExceptionResolver baseOneRuntimeExceptionHandlerExceptionResolver;

    @Autowired
    private BaseRuntimeExceptionHandlerExceptionResolver baseRuntimeExceptionHandlerExceptionResolver;

    @Override
    protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
        exceptionResolvers.add(baseOneRuntimeExceptionHandlerExceptionResolver);
        exceptionResolvers.add(baseRuntimeExceptionHandlerExceptionResolver);
    }

注解方式

重点说明一下注解的方式如何配置异常处理器,以及spring是如何实现

@ControllerAdvice

@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注释的派生,只能放到类上

@RestControllerAdvice

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice {

   @AliasFor(annotation = ControllerAdvice.class)
   String[] value() default {};

   @AliasFor(annotation = ControllerAdvice.class)
   String[] basePackages() default {};

   @AliasFor(annotation = ControllerAdvice.class)
   Class<?>[] basePackageClasses() default {};

   @AliasFor(annotation = ControllerAdvice.class)
   Class<?>[] assignableTypes() default {};

   @AliasFor(annotation = ControllerAdvice.class)
   Class<? extends Annotation>[] annotations() default {};

}

@ExceptionHandler

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {

   /**
    * Exceptions handled by the annotated method. If empty, will default to any
    * exceptions listed in the method argument list.
    */
   Class<? extends Throwable>[] value() default {};

}

@ExceptionHandler中的value可以指定要处理的异常

处理异常代码

@RestControllerAdvice
public class BaseRuntimeExceptionHandlerExceptionResolver{

    @ExceptionHandler
    public ReturnV handException(Exception e){

        if(e instanceof BizRuntimeException){

            return ReturnV.fail;
        }else {
            return ReturnV.fail;
        }
    }
}

接下来分析一下spring是如何初始化和执行的

初始化

异常处理器ExceptionHandlerExceptionResolver

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
      implements ApplicationContextAware, InitializingBean {}

通过类的继承关系看到ExceptionHandlerExceptionResolver继承了

InitializingBean,

@Override
public void afterPropertiesSet() {
   // Do this first, it may add ResponseBodyAdvice beans
   initExceptionHandlerAdviceCache();

   if (this.argumentResolvers == null) {
      List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
      this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
   }
   if (this.returnValueHandlers == null) {
      List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
      this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
   }
}

具体看initExceptionHandlerAdviceCache();

private void initExceptionHandlerAdviceCache() {
   if (getApplicationContext() == null) {
      return;
   }

   List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
   for (ControllerAdviceBean adviceBean : adviceBeans) {
      Class<?> beanType = adviceBean.getBeanType();
      if (beanType == null) {
         throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
      }
      ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
      if (resolver.hasExceptionMappings()) {
         this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
      }
      if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
         this.responseBodyAdvice.add(adviceBean);
      }
   }

   if (logger.isDebugEnabled()) {
      int handlerSize = this.exceptionHandlerAdviceCache.size();
      int adviceSize = this.responseBodyAdvice.size();
      if (handlerSize == 0 && adviceSize == 0) {
         logger.debug("ControllerAdvice beans: none");
      }
      else {
         logger.debug("ControllerAdvice beans: " +
               handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
      }
   }
}

ControllerAdviceBean是处理@ControllerAdvice和映射注解的,通过 findAnnotatedBeans 方法找到IOC容器中标注@ControllerAdvice注解的bean

public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
   List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
   for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context, Object.class)) {
      if (!ScopedProxyUtils.isScopedTarget(name)) {
         ControllerAdvice controllerAdvice = context.findAnnotationOnBean(name, ControllerAdvice.class);
         if (controllerAdvice != null) {
            // Use the @ControllerAdvice annotation found by findAnnotationOnBean()
            // in order to avoid a subsequent lookup of the same annotation.
            adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));
         }
      }
   }
   OrderComparator.sort(adviceBeans);
   return adviceBeans;
}
private void initExceptionHandlerAdviceCache() {
   if (getApplicationContext() == null) {
      return;
   }

   List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
   for (ControllerAdviceBean adviceBean : adviceBeans) {
      Class<?> beanType = adviceBean.getBeanType();
      if (beanType == null) {
         throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
      }
      ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
      if (resolver.hasExceptionMappings()) {
         this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
      }
      if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
         this.responseBodyAdvice.add(adviceBean);
      }
   }

   if (logger.isDebugEnabled()) {
      int handlerSize = this.exceptionHandlerAdviceCache.size();
      int adviceSize = this.responseBodyAdvice.size();
      if (handlerSize == 0 && adviceSize == 0) {
         logger.debug("ControllerAdvice beans: none");
      }
      else {
         logger.debug("ControllerAdvice beans: " +
               handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");
      }
   }
}

重点的这个异常方法处理器ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);

public ExceptionHandlerMethodResolver(Class<?> handlerType) {
   for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
      for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
         addExceptionMapping(exceptionType, method);
      }
   }
}

这个构造会把当前类中标注@ExceptionHandler注解的方法找到映射到内存,最后也会把this.exceptionHandlerAdviceCache.put(adviceBean, resolver)

ControllerAdviceBean和ExceptionHandlerMethodResolver绑定再一起

执行

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
      HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {

   ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
   if (exceptionHandlerMethod == null) {
      return null;
   }

   if (this.argumentResolvers != null) {
      exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
   }
   if (this.returnValueHandlers != null) {
      exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
   }

   ServletWebRequest webRequest = new ServletWebRequest(request, response);
   ModelAndViewContainer mavContainer = new ModelAndViewContainer();

   try {
      if (logger.isDebugEnabled()) {
         logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
      }
      Throwable cause = exception.getCause();
      if (cause != null) {
         // Expose cause as provided argument as well
         exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
      }
      else {
         // Otherwise, just the given exception as-is
         exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
      }
   }
   catch (Throwable invocationEx) {
      // Any other than the original exception (or its cause) is unintended here,
      // probably an accident (e.g. failed assertion or the like).
      if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) {
         logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
      }
      // Continue with default processing of the original exception...
      return null;
   }

   if (mavContainer.isRequestHandled()) {
      return new ModelAndView();
   }
   else {
      ModelMap model = mavContainer.getModel();
      HttpStatus status = mavContainer.getStatus();
      ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
      mav.setViewName(mavContainer.getViewName());
      if (!mavContainer.isViewReference()) {
         mav.setView((View) mavContainer.getView());
      }
      if (model instanceof RedirectAttributes) {
         Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
         RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
      }
      return mav;
   }
}

具体的逻辑都在上面的代码中

  1. 按照类型标注@ControllerAdvice类型找到对应的ExceptionHandlerMethodResolver,再按照异常类型找到对应的Method封装成ServletInvocableHandlerMethod,如果配置对应的HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler时绑定对应的参数处理器和返回值处理器

  2. 得到抛出的异常,经过参数处理器的处理,经过反射调用。返回值经过返回值处理器。完成调用

    exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
    
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
      Object... providedArgs) throws Exception {

   Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
   if (logger.isTraceEnabled()) {
      logger.trace("Arguments: " + Arrays.toString(args));
   }
   return doInvoke(args);
}

执行流程中涉及到一些本地缓存,没具体展开。

整个spring全局异常处理到此。微信公众号奋进的IT创业者

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值