问题场景: 今天头儿给我了个需求,简单来说就是获取mybatis执行期间的sql,然后使用小米开源的soar https://github.com/XiaoMi/soar 对其进行sql评分,因为我要用到redis客户端的bean,但是发现@Autowired注入进去是空的。
想看解决的直接看文末好了。
问题描述:
- 插件代码
@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
}
}
- 尝试解决
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);
}
}
结果: 失败
-
分析原因
简单来说就是:
a. mybatis的插件先于spring容器的完全初始化,所以一般的implements ApplicationContextAware,implements InitializingBean的spring容器生命周期接口一般没啥用
b. mybatis的插件虽然被声明成了@Component通过自动扫描,看上去好像可以被spring容器管理,但是你可以发现你通过spring容器获取的这个bean和mybatis的拦截器链持有的对象不是一个。 -
解决
那咋办嘛? 所以我使用了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;
}
}
- 总结
好了这个问题就解决了,主要是mybatis拦截器生命周期和spring容器生命周期不一致的原因造成的,通用的解法可以通过后完成的去初始化先完成的对象。
网友评论