美文网首页
mybatis自定义插件获取spring容器bean为空

mybatis自定义插件获取spring容器bean为空

作者: 捞月亮的阿汤哥 | 来源:发表于2020-05-12 14:12 被阅读0次

    问题场景: 今天头儿给我了个需求,简单来说就是获取mybatis执行期间的sql,然后使用小米开源的soar https://github.com/XiaoMi/soar 对其进行sql评分,因为我要用到redis客户端的bean,但是发现@Autowired注入进去是空的。
    想看解决的直接看文末好了。

    问题描述:

    1. 插件代码
    @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
    @Component
    public class SlowSqlInterceptor implements Interceptor{
       @Resource
        //redis客户端的bean
        private RedisClientWrapper redisClientWrapper;
       @Override
        public Object intercept(Invocation invocation) throws Throwable {
            //去获取redisClientWrapper发现是空的,使用不当会发生NPE
        }
    }
    
    1. 尝试解决
      a. 让SlowSqlInterceptor类实现ApplicationContextAware接口,代码如下:
    @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
    @Component
    public class SlowSqlInterceptor implements Interceptor, ApplicationContextAware{
       @Resource
        //redis客户端的bean
        private RedisClientWrapper redisClientWrapper;
        private ApplicationContext context;
    
       @Override
        public Object intercept(Invocation invocation) throws Throwable {
            //去获取redisClientWrapper发现是空的,使用不当会发现空指针
        }
    
       @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            this.context = applicationContext;
            //正常的bean的话是肯定可以通过spring的context获取到的
            this.redisClientWrapper = context.getBean(RedisClientWrapper.class);
        }
    }
    

    结果: 失败

    1. 分析原因
      简单来说就是:
      a. mybatis的插件先于spring容器的完全初始化,所以一般的implements ApplicationContextAware,implements InitializingBean的spring容器生命周期接口一般没啥用
      b. mybatis的插件虽然被声明成了@Component通过自动扫描,看上去好像可以被spring容器管理,但是你可以发现你通过spring容器获取的这个bean和mybatis的拦截器链持有的对象不是一个。

    2. 解决
      那咋办嘛? 所以我使用了spring容器初始化的时候,来初始化这个拦截器
      a. 添加新类 InterceptorBeanAdaptor:

    @Component
    public class InterceptorBeanAdaptor implements InitializingBean {
    
        @Resource
        private RedisClientWrapper redisClientWrapper;
    
        @Resource
        private SqlSessionFactory sqlSessionFactory;
    
        @Override
        public void afterPropertiesSet() throws Exception {
           //需要获取拦截器链持有的拦截器实例
            List<Interceptor> interceptors = sqlSessionFactory.getConfiguration().getInterceptors();
            if (null == interceptors || interceptors.size() == 0) {
                return;
            }
            for (Interceptor interceptor : interceptors) {
                if (interceptor instanceof SlowSqlInterceptor) {
                    SlowSqlInterceptor slowSqlInterceptor = (SlowSqlInterceptor) interceptor;
                    //注入redisWrapper
                    slowSqlInterceptor.setRedisClientWrapper(redisClientWrapper);
                    //拦截器初始化标示
                    slowSqlInterceptor.setBeanInit(true);
                    break;
                }
            }
        }
    }
    

    b. 修改原先类 SlowSqlInterceptor

    @Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
    public class SlowSqlInterceptor implements Interceptor{
        private RedisClientWrapper redisClientWrapper;
        
        //bean是否初始化了
        private volatile boolean beanInit = false;
    
       @Override
        public Object intercept(Invocation invocation) throws Throwable {
            //延迟初始化
            if (!this.beanInit) {
                return invocation.proceed();
            }
            // 接下来可以写你的拦截逻辑了,注意最后别忘了 return invocation.proceed();
        }
    
         //添加set方法
         public void setRedisClientWrapper(RedisClientWrapper redisClientWrapper) {
            this.redisClientWrapper = redisClientWrapper;
        }
        
        public void setBeanInit(boolean beanInit) {
            this.beanInit = beanInit;
        }
    }
    
    1. 总结
      好了这个问题就解决了,主要是mybatis拦截器生命周期和spring容器生命周期不一致的原因造成的,通用的解法可以通过后完成的去初始化先完成的对象。

    相关文章

      网友评论

          本文标题:mybatis自定义插件获取spring容器bean为空

          本文链接:https://www.haomeiwen.com/subject/yudbnhtx.html