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
@Slf4g
public class AuthAopBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    
    @Override    
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        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{
                	// 方法执行过程,在这里捕获异常,注意这里的第一个参数用bean,不能使用o,如果用o会造成发生无限递归造成StackOverflow
                    res = method.invoke(bean,objects);
                } catch (Exception e){
                    log.info(e.getCause().getMessage());
                    if (checkToken()){
                        res = methodProxy.invokeSuper(o, objects);
                    } else {
                        throw e;
                    }                
                }                
                return res;            
            });
            // 返回新的代理bean
            return enhancer.create();
        }        
        return bean;
    }    
    // 该逻辑自己修改补充
    private boolean checkToken() {
        return true;    
    }
}

关键点我都写在注释里了,因为公司的代码是保密的,所以我只是把思路搬了过来,大家想用的时候可以自己改一下。这样就利用BeanPostProcessor,无侵入的对之前的AOP过程进行了拓展。

总结

这次探索还是很有收获的,对Spring的BeanPostProcessor有了实际的使用经验,也稍微学习了一下cglib动态代理,但对这方面还没有深入研究,所以也不多发言。

当然这只是我自己研究的结果,并不一定是最好的方法,如果有哪位大佬有更好的方法,麻烦告知一下,我去学习一下(我真的是因为没在网上找到更好的方法才自己研究的)。

Spring作为Java开发的业界标准,要学习的东西还很多,总之继续加油吧~~~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值