Spring捕获AOP抛出的异常

Spring捕获AOP抛出的异常

背景

在最近开发中出现了这样的一个场景,有一个鉴权SDK引入到我的项目,他会对所有Controller进行鉴权,当然是通过自己定义Id-Token进行解析鉴权,如果Controller有权限则可以调用对应的Controller,如果不通过则直接抛出异常。

现在根据业务情况变更,要在以往的鉴权过程中新增一种情况,除了可以通过原有的Id-Token鉴权外,还可以通过另一个服务产生的Token字段进行鉴权,这两种情况只需要满足一个即可通过鉴权。

后面的解决方案记录了我尝试的各种过程,以及不采用失败原因,如果观众老爷不感兴趣,可以直接去看最终解决方案。

解决过程

最初方案

最直接能想到的方案肯定是重新修改鉴权的SDK,直接添加鉴权逻辑,然后重新编译。

但这个方案缺陷也很明显,这既然是个SDK,肯定是外部(其他部门)提供给我的,说不定啥时候就升级版本。修改它本身的鉴权过程,就是把两种鉴权过程耦合在一起了,那必然在后续升级过程中会产生很多麻烦,所以肯定不能这么干,这种方案PASS。

失败探索

添加AOP

“既然不能在他的SDK中做耦合,那我再写个AOP可以吗?”在放弃修改SDK的计划后,这个想法油然而生。

这个方案也是探究了很久,到最后确定不管是我们写个在它之前的AOP还是在它之后的AOP,都无法绕过原来鉴权的这个AOP。

我们把原有的AOP称为AOP1,新添加的AOP成为AOP2,如果AOP1放在AOP2之前,如果鉴权不通过,走不到AOP2就会报错返回;如果AOP2放在AOP1之前,那AOP2通过,AOP1还是会鉴权报错。所以这个方案也PASS了。

继承SDK的AOP类

秉持着“无法打败就加入”的想法,那既然我绕不开你,就加入你,或者说替代你。

所以我就想是不是可以自己重写一个AOP类,继承原有SDK的AOP类,把父类的鉴权逻辑全继承过来,捕获父类鉴权发生异常。

其实这个过程和上面的添加AOP本质上是一致的,因为你这个也是添加了一个AOP,而且继承并不会让原本的AOP失效,最后的结果就是回到了上面的情况。

修改AOP生效条件

原来AOP是通过注解生效的,它虽然把所有Controller都定义为切点,但只对加了@Auth注解生效,这个注解是SDK中自定的注解,那我可以重新定义一个注解去继承原来的Auth吗?

这个方案只要想一下就不可能,并不是说不能实现,而且是要自己增加很多和原有SDK重复的代码,还要改掉原来Controller上@Auth注解,这么巨大的改动出了问题谁负责,这个锅谁爱背谁背,反正我不背。因此这个办法也PASS了。

最终解决方案——BeanPostProcessor

回到问题最初,原本的AOP在鉴权不通过是抛出异常,之所以一直解决不了,主要问题是无法捕获这个异常,如果可以捕获异常,那我直接在报错的时候catch到然后重新处理不就行了吗。那如何无侵入的捕获这个异常呢?这个问题我查了很久都没找到答案,这也是我这篇文章起名的原因,希望可以帮助到有类似问题的小伙伴。

在我毫无头绪之时有个朋友说,如果能获取到代理对象,就能重新拓展这个方法,并且给我发了一段他在之前项目上做的处理。我看到了一丝丝曙光——BeanPostProcessor(后置处理器)。

BeanPostProcessor是什么我就不说详细阐述了(主要我也是一知半解),简单的说就是Spring帮你实例化Bean后,在执行初始化方法前后,可以执行一些自己的逻辑,然后返回bean。

那在这里返回的bean,你可以做一些手脚,在这里利用cglib重新对bean进行代理,进而完成对bean本身的功能的拓展加强。

当然以上只是我的理解,如果有问题可以一起讨论下,下面上代码。

@Component
public class AuthAopBeanPostProcessor implements BeanPostProcessor {
     // beanName 默认 Bean类名首字母改小写
		if (beanName.equals("authAop")) {
		     	// cglib动态代理
			  Enhancer enhancer = new Enhancer();
         	  enhancer.setSuperclass(bean.getClass());
         	  enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
              // 仅对鉴权方法进行改造,其他方法正常执行返回
              if ( !"authMethod".equals(method.getName()){
                  // 正常执行结果
              		return methodProxy.invokeSuper(o, objects);
              }
			  Object res = null;
              try{
                  // 执行AOP的过程,在这里捕获异常,注意这里的第一个参数用bean,不能使用o,如果用o会造成发生无限递归造成*
              		res = method.invoke(bean,objects);
              } catch (Exception e){
                  log.info(e.getCause().getMessage());
                  // 如果报错,再继续检查Token,如果校验Token有问题,直接抛出错误,校验Token方法逻辑可可改为自己的逻辑
                  if (checkToken()){
                  		res = methodProxy.invokeSuper(o, objects);
                  } else {
                        throw e;
                  }                
              }                
              return res; 
         });
         // 返回新的代理bean
         return enhancer.create();
		}
     return bean;
}

这样我们就利用BeanPostProcessor,无侵入的对之前的AOP过程进行了拓展。

总结

这次探索还是很有收获的,对Spring的BeanPostProcessor有了实际的使用经验,也稍微学习了一下cglib动态代理,但对这方面还没有深入研究,所以也不多发言,Spring作为Java开发的业界标准,要学习的东西还很多,总之继续加油吧~~~

上一篇:罗技方向盘套件开发记录


下一篇:使用dnf管理软件包