SpringBoot统一功能处理——统一异常处理

目录

一、异常简单使用

二、@ControllerAdvice 源码分析


一、异常简单使用

统一异常处理使用的是 @ControllerAdvice + @ExceptionHandler 来实现的,@ControllerAdvice 表示控制器通知类, @ExceptionHandler 是异常处理器,两个结合表示当出现异常的时候执行某个通知,也就是执行某个方法事件。
import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ControllerAdvice
@ResponseBody
public class ErrorAdvice {

 @ExceptionHandler
 public Object handler(Exception e) {
   return Result.fail(e.getMessage());
 }
}
以上代码表示,如果代码出现Exception异常(包括Exception的子类), 就返回一个 Result的对象, Result 对象的设置参考 Result.fail(e.getMessage())
public static Result fail(String msg) {
 Result result = new Result();
 result.setStatus(ResultStatus.FAIL);
 result.setErrorMessage(msg);
 result.setData("");
 return result;
}
针对不同的异常,返回不同的结果:
import com.example.demo.model.Result;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

@ResponseBody
@ControllerAdvice
public class ErrorAdvice {

 @ExceptionHandler
 public Object handler(Exception e) {
  return Result.fail(e.getMessage());
 }

 @ExceptionHandler
 public Object handler(NullPointerException e) {
  return Result.fail("发⽣NullPointerException:"+e.getMessage());
 }

@ExceptionHandler
 public Object handler(ArithmeticException e) {
  return Result.fail("发⽣ArithmeticException:"+e.getMessage());
 }
}

二、@ControllerAdvice 源码分析

统一数据返回和统一异常都是基于 @ControllerAdvice 注解来实现的,通过分析@ControllerAdvice 的源码,可以知道他们的执行流程。
点击 @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 组件,这也就是为什么没有五大注解, ControllerAdvice 就生效的原因。
下面看看Spring是怎么实现的,还是从 DispatcherServlet 的代码开始分析。DispatcherServlet 对象在创建时会初始化一系列的对象:
public class DispatcherServlet extends FrameworkServlet {
 //...
 @Override
 protected void onRefresh(ApplicationContext context) {
 initStrategies(context);
 }
 
 /**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further 
strategy objects.
 */
 protected void initStrategies(ApplicationContext context) {
 initMultipartResolver(context);
 initLocaleResolver(context);
 initThemeResolver(context);
 initHandlerMappings(context);
 initHandlerAdapters(context);
 initHandlerExceptionResolvers(context);
 initRequestToViewNameTranslator(context);
 initViewResolvers(context);
 initFlashMapManager(context);
 }
 //...
}
对于 @ControllerAdvice 注解,重点关注 initHandlerAdapters(context) 和 initHandlerExceptionResolvers(context) 这两个方法:

  • initHandlerAdapters(context) 
initHandlerAdapters(context) 方法会取得所有实现了 HandlerAdapter 接口的bean并 保存起来,其中有一个类型为 RequestMappingHandlerAdapter 的 bean,这个bean就是 @RequestMapping 注解能起作用的关键,这个bean在应用启动过程中会获取所有被 @ControllerAdvice 注解标注 bean对象, 并做进一步处理,关键代码如下:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
 implements BeanFactoryAware, InitializingBean {
 //...
 
 
 /**
 * 添加ControllerAdvice bean的处理
 */
 private void initControllerAdviceCache() {
 if (getApplicationContext() == null) {
 return;
 }
 
 //获取所有所有被 @ControllerAdvice 注解标注的bean对象
 List<ControllerAdviceBean> adviceBeans = 
ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
 
 List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
 
 for (ControllerAdviceBean adviceBean : adviceBeans) {
 Class<?> beanType = adviceBean.getBeanType();
 if (beanType == null) {
 throw new IllegalStateException("Unresolvable type for 
ControllerAdviceBean: " + adviceBean);
 }
 Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, 
MODEL_ATTRIBUTE_METHODS);
 if (!attrMethods.isEmpty()) {
 this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
 }
 Set<Method> binderMethods = 
MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
 if (!binderMethods.isEmpty()) {
 this.initBinderAdviceCache.put(adviceBean, binderMethods);
 }
 if (RequestBodyAdvice.class.isAssignableFrom(beanType) || 
ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
 requestResponseBodyAdviceBeans.add(adviceBean);
 }
}
 
 if (!requestResponseBodyAdviceBeans.isEmpty()) {
 this.requestResponseBodyAdvice.addAll(0, 
requestResponseBodyAdviceBeans);
 }
 
 if (logger.isDebugEnabled()) {
 int modelSize = this.modelAttributeAdviceCache.size();
 int binderSize = this.initBinderAdviceCache.size();
 int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
 int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
 if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount 
== 0) {
 logger.debug("ControllerAdvice beans: none");
 }
 else {
 logger.debug("ControllerAdvice beans: " + modelSize + " 
@ModelAttribute, " + binderSize +
 " @InitBinder, " + reqCount + " RequestBodyAdvice, " + 
resCount + " ResponseBodyAdvice");
 }
 }
 }
 //...
 
 
}
这个方法在执行时会查找使用所有的 @ControllerAdvice 类,把 ResponseBodyAdvice 类放在容器中,当发生某个事件时,调用相应的 Advice方法,比如返回数据前调用统一数据封装。
  • initHandlerExceptionResolvers(context)
接下来看 DispatcherServlet initHandlerExceptionResolvers(context) 方法,这个方法会取得所有实现了 HandlerExceptionResolver 接口的bean并保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver 的bean,这个bean在应用启动过程中会获 取所有被 @ControllerAdvice 注解标注的bean对象做进一步处理, 代码如下:
public class ExceptionHandlerExceptionResolver extends
AbstractHandlerMethodExceptionResolver
implements ApplicationContextAware, InitializingBean {

//...
 
 private void initExceptionHandlerAdviceCache() {
 if (getApplicationContext() == null) {
 return;
 }
 
 // 获取所有所有被 @ControllerAdvice 注解标注的bean对象
 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");
 }
 }
 }
 //...
}
当Controller抛出异常时, DispatcherServlet 通过 ExceptionHandlerExceptionResolver 来解析异常,而ExceptionHandlerExceptionResolver 又通过 ExceptionHandlerMethodResolver 来解析异常, ExceptionHandlerMethodResolver 最终解析异常找到适用的@ExceptionHandler标注 的方法是这里:
public class ExceptionHandlerMethodResolver {
 //...
 
 private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
 List<Class<? extends Throwable>> matches = new ArrayList();
 //根据异常类型, 查找匹配的异常处理⽅法
 //⽐如NullPointerException会匹配两个异常处理⽅法:
 //handler(Exception e) 和 handler(NullPointerException e)
 for (Class<? extends Throwable> mappedException : 
this.mappedMethods.keySet()) {
 if (mappedException.isAssignableFrom(exceptionType)) {
 matches.add(mappedException);
 }
 }
 //如果查找到多个匹配, 就进⾏排序, 找到最使⽤的⽅法. 排序的规则依据抛出异常相对于声明异常的深度
 //⽐如抛出的是NullPointerException(继承于RuntimeException, RuntimeException⼜继承于Exception)
 //相对于handler(NullPointerException e) 声明的NullPointerException深度为0,
 //相对于handler(Exception e) 声明的Exception 深度 为2
 //所以 handler(NullPointerException e)标注的⽅法会排在前⾯
 if (!matches.isEmpty()) {
 if (matches.size() > 1) {
 matches.sort(new ExceptionDepthComparator(exceptionType));
 }
 return this.mappedMethods.get(matches.get(0));
 }
 else {
 return NO_MATCHING_EXCEPTION_HANDLER_METHOD;
 }
 }
 //...
}
  • 18
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值