写点什么

为啥写的 mybatis 插件没用?一场 mybatis 插件加载机制的探索之旅

用户头像
altantisor
关注
发布于: 2021 年 02 月 01 日
为啥写的mybatis插件没用?一场mybatis插件加载机制的探索之旅

某天,由于业务需要,要对系统里的每张业务表增加每条记录的创建者,创建时间,最新更新者,最新更新时间这些审计信息字段,想到每张表和每个业务逻辑上都需要增加类似的代码片段,虽然很简单,但改动涉及的类很多,而且未来其他人增加新逻辑时,也非常容易忘记加上这段逻辑;所以想实现一个技术方案来统一处理审计信息字段填充,避免掺杂到各个业务逻辑中。于是,就想到了使用 mybatis 的插件机制,实现一个自定义的插件来填充创建者,创建时间,最新更新者,最新更新时间这些审计信息字段,代码片段如下:

@Component@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})public class AuditingInterceptor implements Interceptor {
@Override public Object intercept(Invocation invocation) throws Throwable { //获取当前的sql操作 MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType(); //获取对象属性 Object parameter = invocation.getArgs()[1]; List<Field> fields = getFieldList(parameter); //获取当前用户 String userId = getUserId(); Date currentTime = new Date();
if (SqlCommandType.UPDATE == sqlCommandType) { //在待插入或更新的对象里,设置最新更新者,最新更新时间 ...... } else if (SqlCommandType.INSERT == sqlCommandType) { //待插入或更新的对象里,设置创建者,创建时间,最新更新者,最新更新时间 ...... } //执行后续插入或更新操作 return invocation.proceed(); } @Override public Object plugin(Object target) { if (target instanceof Executor) { return Plugin.wrap(target, this); } else { return target; } }
@Override public void setProperties(Properties properties) { } }
复制代码

然后自信满满地等待着插件被加载,上面逻辑被执行。what?为啥写的插件没被加载,不是加了 @Component 吗?求助于搜索引擎,一顿乱搜和尝试,最终通过在 mybatis-config.xml 增加插件声明,解决了。

<configuration	<plugins>        <plugin interceptor="com.example.AuditInterceptor">        </plugin>    </plugins></configuration>
复制代码

可是为什么呢?还是要回到 mybatis 源代码中去。

首先,由 InterceptorChain.java 出发,看到最终是由 SqlSessionFactoryBean 的 afterPropertiesSet()实现,而据我们所知,afterPropertiesSet 方法是 Spring 为 bean 提供了初始化方法,在 bean 初始化时被调用,由于在我们系统里(使用的是 mybatis-spring-boot-starter),SqlSessionFactoryBean 并不是作为一个 bean 注册到 spring 的,所以这里 afterPropertiesSet()是被 SqlSessionFactoryBean 的 getObject 方法调用的。


接着,看一下 SqlSessionFactoryBean 是在哪里被创建的。查看了 mybatis 源码,发现它是在 MybatisAutoConfiguration 类中的 sqlSessionFactory 方法里创建的。

在 MybatisAutoConfiguration 会扫描所有 Interceptor 类的 bean,然后在 MybatisAutoConfiguration 类中的 sqlSessionFactory 方法里,注入到 SqlSessionFactoryBean 里。

    @Bean    @ConditionalOnMissingBean    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();        factory.setDataSource(dataSource);        factory.setVfs(SpringBootVFS.class);				......                  if (!ObjectUtils.isEmpty(this.interceptors)) {            factory.setPlugins(this.interceptors);        }        .......        return factory.getObject();    }
复制代码

但是,由于在项目里定义了 SqlSessionFactory 的 bean(如下)且上述方法声明了 ConditionalOnMissingBean,所以导致 MybatisAutoConfiguration 类中的 sqlSessionFactory 方法没被调用。

不过,这里调用了 SqlSessionFactoryBean 的 setConfigLocation 方法,从而在加载 mybatis-config.xml 时,实现了 Interceptor 类的注入;也就是之前为什么在 mybatis-config.xml 增加插件声明能解决了问题的原因,有点误打误撞的意味。

    @Bean(name = "mySessionFactory")    public SqlSessionFactory mySessionFactory(@Qualifier("myDataSource") DataSource dataSource) throws Exception{        SqlSessionFactoryBean factory = new SqlSessionFactoryBean();        factory.setMapperLocations(resolver.getResources("classpath:mapper/*.xml"));        factory.setConfigLocation(resolver.getResource("classpath:mybatis-config.xml"));        factory.setDataSource(dataSource);        return factory.getObject();    }
复制代码


至此,真相大白了。

接下来,梳理一下 mybatis-spring-boot-starter 加载插件的几个地方:

1.如果未注册 sqlSessionFactory bean 的话,MybatisAutoConfiguration 类中的 sqlSessionFactory 方法,会将所有 Interceptor 类的 bean 注入;

2.如果在第一种里,没有声明 Interceptor 类的 bean 的话,也可以通过实现 ConfigurationCustomizer 的 bean,在 ConfigurationCustomizer 中实现的 customize 方法时,调用 configuration.addInterceptor()方法加载插件;

3.如果在 sqlSessionFactory bean 里,有设置 configLocation,那么可以通过 mybatis 配置文件里定义 plugins 方式注入;


后来,又研究了一下 mybatis 分页插件 pagehelper,发现它的插件加载实现是这样的:在它的 PageHelperAutoConfiguration 里会注入所有 SqlSessionFactory 的 Bean,然后通过 sqlSessionFactory.getConfiguration().addInterceptor()方法,将创建的 PageInterceptor 插件注入到 mybatis 插件机制里。这又提供了一种注入 mybatis 插件的方案。

@Configuration@ConditionalOnBean({SqlSessionFactory.class})@EnableConfigurationProperties({PageHelperProperties.class})@AutoConfigureAfter({MybatisAutoConfiguration.class})public class PageHelperAutoConfiguration {    @Autowired    private List<SqlSessionFactory> sqlSessionFactoryList;    @Autowired    private PageHelperProperties properties;
public PageHelperAutoConfiguration() { }
@Bean @ConfigurationProperties( prefix = "pagehelper" ) public Properties pageHelperProperties() { return new Properties(); } @PostConstruct public void addPageInterceptor() { PageInterceptor interceptor = new PageInterceptor(); Properties properties = new Properties(); properties.putAll(this.pageHelperProperties()); properties.putAll(this.properties.getProperties()); interceptor.setProperties(properties); Iterator var3 = this.sqlSessionFactoryList.iterator();
while(var3.hasNext()) { SqlSessionFactory sqlSessionFactory = (SqlSessionFactory)var3.next(); sqlSessionFactory.getConfiguration().addInterceptor(interceptor); } }}
复制代码


参考资料:

1.http://mybatis.org/spring/zh/boot.html

2.http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/

3.https://mybatis.org/mybatis-3/configuration.html#properties

4.https://mybatis.org/mybatis-3/configuration.html#settings

  1. https://pagehelper.github.io/


发布于: 2021 年 02 月 01 日阅读数: 32
用户头像

altantisor

关注

还未添加个人签名 2018.02.19 加入

还未添加个人简介

评论

发布
暂无评论
为啥写的mybatis插件没用?一场mybatis插件加载机制的探索之旅