问题场景: 有个需求,简单来说就是需要对用户输入的明文信息进行加密后存储密文,而在页面展示的时候要进行明文展示。这就想到了Mybatis提供的插件功能,插件分别可以对 StatementHandler、 ParameterHandler、ResultSetHandler、Executor四种操作进行拦截。结合我们本次的需求,只需要用到ParameterHandler和ResultSetHandler两种。ParameterHandler是对SQL查询参数进行拦截处理,而ResultSetHandler是对查询结果进行拦截处理。
1、插件代码
下面给出两个插件的代码:代码进行了精简化,只保留了需要用到的Spring IOC中需要注入的service对象
@Component
@Intercepts(@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class))
public class EncrypInterceptor implements Interceptor{
@Autowired
private YourService yourService;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//调用yourService提供的方法进行业务处理
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
@Component
@Intercepts(@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class))
public class DecryInterceptor implements Interceptor{
@Autowired
private YourService yourService;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//调用yourService提供的方法进行业务处理
return proceed;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
插件定义好后,在Mybatis的配置文件mybatis-config.xml中配置好插件
<plugins>
<plugin interceptor="com.xxx.xxx.interceptor.DecryInterceptor"></plugin>-->
<plugin interceptor="com.xxx.xxx.interceptor.EncrypInterceptor"></plugin>-->
</plugins>
但是项目启动后,运行发现插件中注入的Service是空的,导致了NPE异常了。
2、原因分析
Mybatis的插件虽然被声明成了@Component通过自动扫描看上去好像可以被spring容器管理,但是你可以发现你通过Spring IOC容器获取的这个bean和Mybatis的拦截器链持有的对象不是同一个。那就说明我们容器中存在了2种不同的插件对象了。一种里面的service属性为非空,而另一种里面的service属性为空。通过查看分析Spring和SqlSessionFactoryBean的源码查看插件对象的加载过程发现,当Spring IOC容器启动时会通过自动扫描把这两个插件自动载入到IOC容器中,而它们内部的Service属性也能通过IOC容器进行自动的装配, 这是里面的service属性为非空的那一类插件。而当Spring IOC加载我们配置的SqlSessionFactoryBean对象时,在它内部会加载解析mybatis-config.xml配置文件,然后生成Configuration对象,再通过Configuration对象生SqlSessionFactory对象。而在生成Configuration对象过程中,内部的插件是通过Mybatis自己去创建的,而不是从Spring IOC 容器中去获取,从而创建的插件中Service属性就为空。而我们调用的时候,真正生效的是Mybatis内部创建的创建对象而不是Spring IOC容器中的。而Mybatis内部创建的对象中service是空的,这就导致最后调用的时候发生了NPE了。
3、解决
既然问题发生的原因找到了,那就有解决办法了。办法就是让Mybatis内部不要创建插件对象,所需的插件对象由我们从外部传给Mybatis,而传给Mybatis的插件是由Spring IOC容器管理的内部service为非空的。
改动一:在Mybatis配置文件mybatis-config.xml注释掉插件的声明,这样在Mybatis内部就不会创建插件对象
<!-- <plugins>-->
<!-- <plugin interceptor="com.yun.hom.interceptor.DecryInterceptor"></plugin>-->
<!-- <plugin interceptor="com.yun.hom.interceptor.EncrypInterceptor"></plugin>-->
<!-- </plugins>-->
改动二:将由Spring IOC 创建管理的插件对象配置到SqlSessionFactoryBean对象的定义中
<!-- 3.配置SqlSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath*:/mapper/*.xml"/>
<!-- 配置创建的插件 -->
<property name="plugins" >
<array>
<ref bean="decryInterceptor" />
<ref bean="encrypInterceptor"/>
</array>
</property>
</bean>
4、总结
好了这个问题就解决了,主要是Mybatis内部插件和spring IOC 管理的插件的生命周期不一致(这两者不是同一个对象)的原因造成。