栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

在spring中手写全局异常拦截器

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

在spring中手写全局异常拦截器

为什么要重复造轮子

你可能会问,Spring已经自带了全局异常拦截,为什么还要重复造轮子呢?

这是个好问题,我觉得有以下几个原因

  1. 装逼
  2. Spring的全局异常拦截只是针对于Spring MVC的接口,对于你的RPC接口就无能为力了
  3. 无法定制化
  4. 除了写业务代码,我们其实还能干点别的事

我觉得上述理由已经比较充分的解答了为什么要重复造轮子,接下来就来看一下怎么造轮子

造个什么样的轮子?

我觉得全局异常拦截应该有如下特性

  1. 使用方便,最好和spring原生的使用方式一致,降低学习成本
  2. 能够支持所有接口
  3. 调用异常处理器可预期,比如说定义了RuntimeException的处理器和Exception的处理器,如果这个时候抛出NullPointException,这时候要能没有歧义的选择预期的处理器

如何造轮子?

由于现在的应用基本上都是基于spring的,因此我也是基于SpringAop来实现全局异常拦截

首先先定义几个注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@documented
@Component
public @interface ExceptionAdvice {
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface ExceptionHandler {
  Class[] value();
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@documented
public @interface ExceptionIntercept {
}

@ExceptionAdvice 的作用是标志定义异常处理器的类,方便找到异常处理器

@ExceptionHandler 的作用是标记某个方法是处理异常的,里面的值是能够处理的异常类型

@ExceptionIntercept 的作用是标记需要异常拦截的方法

接下来定义统一返回格式,以便出现错误的时候统一返回

@Data
public class baseResponse {
  private Integer code;
  private String message;
  private T data;

  public baseResponse(Integer code, String message) {
    this.code = code;
    this.message = message;
  }
}

然后定义一个收集异常处理器的类

public class ExceptionMethodPool {
  private List methods;
  private Object excutor;

  public ExceptionMethodPool(Object excutor) {
    this.methods = new ArrayList();
    this.excutor = excutor;
  }

  public Object getExcutor() {
    return excutor;
  }

  public void add(Class clazz, Method method) {
    methods.add(new ExceptionMethod(clazz, method));
  }
		
 	//按序查找能够处理该异常的处理器
  public Method obtainMethod(Throwable throwable) {
    return methods
 .stream()
 .filter(e -> e.getClazz().isAssignableFrom(throwable.getClass()))
 .findFirst()
 .orElseThrow(() ->new RuntimeException("没有找到对应的异常处理器"))
 .getMethod();
  }

  @AllArgsConstructor
  @Getter
  class ExceptionMethod {
    private Class clazz;
    private Method method;
  }
}

ExceptionMethod 里面有两个属性

  • clazz:这个代表着能够处理的异常
  • method:代表着处理异常调用的方法

ExceptionMethodPool 里面按序存放所有异常处理器,excutor是执行这些异常处理器的对象

接下来把所有定义的异常处理器收集起来

@Component
public class ExceptionBeanPostProcessor implements BeanPostProcessor {
  private ExceptionMethodPool exceptionMethodPool;
  @Autowired
  private ConfigurableApplicationContext context;

  @Override
  public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    Class clazz = bean.getClass();
    ExceptionAdvice advice = clazz.getAnnotation(ExceptionAdvice.class);
    if (advice == null) return bean;
    if (exceptionMethodPool != null) throw new RuntimeException("不允许有两个异常定义类");
    exceptionMethodPool = new ExceptionMethodPool(bean);

    //保持处理异常方法顺序
    Arrays.stream(clazz.getDeclaredMethods())
 .filter(method -> method.getAnnotation(ExceptionHandler.class) != null)
 .forEach(method -> {
   ExceptionHandler exceptionHandler = method.getAnnotation(ExceptionHandler.class);
   Arrays.stream(exceptionHandler.value()).forEach(c -> exceptionMethodPool.add(c,method));
 });
    //注册进spring容器
    context.getBeanFactory().registerSingleton("exceptionMethodPool",exceptionMethodPool);
    return bean;
  }
}

ExceptionBeanPostProcessor 通过实现BeanPostProcessor 接口,在bean初始化之前,把所有异常处理器塞进 ExceptionMethodPool,并把其注册进Spring容器

然后定义异常处理器

@Component
public class ExceptionProcessor {
  @Autowired
  private ExceptionMethodPool exceptionMethodPool;

  public baseResponse process(Throwable e) {
    return (baseResponse) FunctionUtil.computeOrGetDefault(() ->{
      Method method = exceptionMethodPool.obtainMethod(e);
      method.setAccessible(true);
      return method.invoke(exceptionMethodPool.getExcutor(),e);
    },new baseResponse(0,"未知错误"));
  }
}

这里应用了我自己通过函数式编程封装的一些语法糖,有兴趣的可以看下

最后通过AOP进行拦截

@Aspect
@Component
public class ExceptionInterceptAop {
  @Autowired
  private ExceptionProcessor exceptionProcessor;
 
  @Pointcut("@annotation(com.example.exception.intercept.ExceptionIntercept)")
  public void pointcut() {
  }
 
  @Around("pointcut()")
  public Object around(ProceedingJoinPoint point) {
    return computeAndDealException(() -> point.proceed(),
 e -> exceptionProcessor.process(e));
  }
 
  public static  R computeAndDealException(ThrowExceptionSupplier supplier, Function dealFunc) {
    try {
      return supplier.get();
    } catch (Throwable e) {
      return dealFunc.apply(e);
    }
  }
  @FunctionalInterface
  public interface ThrowExceptionSupplier {
    T get() throws Throwable;
  }
}

到这里代码部分就已经完成了,我们来看下如何使用

@ExceptionAdvice
public class ExceptionConfig {
  @ExceptionHandler(value = NullPointerException.class)
  public baseResponse process(NullPointerException e){
    return new baseResponse(0,"NPE");
  }

  @ExceptionHandler(value = Exception.class)
  public baseResponse process(Exception e){
    return new baseResponse(0,"Ex");
  }
  
}

@RestController
public class TestControler {

  @RequestMapping("/test")
  @ExceptionIntercept
  public baseResponse test(@RequestParam("a") Integer a){
    if (a == 1){
      return new baseResponse(1,a+"");
    }
    else if (a == 2){
      throw new NullPointerException();
    }
    else throw new RuntimeException();
  }
}

我们通过@ExceptionAdvice标志定义异常处理器的类,然后通过@ExceptionHandler标注处理异常的方法,方便收集

最后在需要异常拦截的方法上面通过@ExceptionIntercept进行异常拦截

我没有使用Spring那种匹配最近父类的方式寻找匹配的异常处理器,我觉得这种设计是一个败笔,理由如下

  • 代码复杂
  • 不能一眼看出要去调用哪个异常处理器,尤其是定义的异常处理器非常多的时候,要是弄多个定义类就更不好找了,可能要把所有的处理器看完才知道应该调用哪个

出于以上考虑,我只保留了一个异常处理器定义类,并且匹配顺序和方法定义顺序一致,从上到下依次匹配,这样只要找到一个能够处理的处理器,那么就知道了会如何调用

原创不易,如果觉得对你有帮助,麻烦点个赞!

我会不定期分享一些有意思的技术,点个关注不迷路-。 -

以上就是在spring中手写全局异常拦截器的详细内容,更多关于spring 全局异常拦截的资料请关注考高分网其它相关文章!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/130477.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号