最近做了一个备份被delete语句删除的数据的小插件,是用MyBatis的拦截器去做的。
但是发现在一个项目中会失效,没有去备份删除的数据,查看日志发现请求并没有进入到拦截器中,换句话说就是拦截器失效了。
百度了一下(哎,百度)发现说的最多的就是分页插件导致的,但是我这个项目中并没有用到分页插件,所以应该不是这个问题,看来只能自己找了。
找问题
拦截器失效了,第一反应肯定要去找找拦截器执行的方法,在InterceptorChain里面的pluginAll:
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
// 拦截器执行的方法
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
这里的pluginAll方法会循环遍历interceptors,去调用plugin方法,plugin方法会返回动态代理之后的对象。
Plugin实现了InvocationHandler,在invoke方法中,去调用了interceptor的intercept方法
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 返回动态代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 调用intercept方法,返回结果
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
我在InterceptorChain的pluginAll方法里打了个断点,发现List<Interceptor> interceptors中并没有我的那个拦截器,只有一个MPJInterceptor mybatis-plus的拦截器,那估计就是拦截器没有加到这个list中,但是SpringBoot使用这个拦截器很简单,连配置都没有,不应该没有扫到,而且其他项目中都是正常的,扫描配置应该没问题,可能是添加的地方有问题。
interceptors是私有的,只需要看addInterceptor方法的调用就行了(mybatis的源码看起来还是很友好的),中间的调用就不赘述了,最上层是在MybatisPlusAutoConfiguration中的sqlSessionFactory方法里:
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
// TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
// 这里,是添加拦截器的
// 至于interceptors是怎么来的,那就是另外一个故事了
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
@Bean说明它是一个bean的定义方法,重点是@ConditionalOnMissingBean:容器里没有SqlSessionFactory的bean时,才会去走这个方法生成SqlSessionFactory的bean。
然后调试了一下,发现初始化的时候并没有走到这个方法里去,说明之前就有SqlSessionFactory的bean了。
然后发现配置了多数据源。。。配置多数据源的时候,配置了SqlSessionFactory的bean,还是多个,这样就不会走上面的初始化流程了,所以也就不会添加自定义的拦截器。
但是MPJInterceptor拦截器是怎么添加进去的?
然后我找到了InterceptorConfig:
/**
* 拦截器配置类 如果配置了分页插件,可能会使拦截器失效
* 此类的作用就是校验拦截器顺序,保证连表插件在其他拦截器之前执行
*
* @author yulichang
*/
@SuppressWarnings("SpringJavaAutowiredMembersInspection")
public class InterceptorConfig implements ApplicationListener<ApplicationReadyEvent> {
private static final Log logger = LogFactory.getLog(InterceptorConfig.class);
@Autowired(required = false)
private List<SqlSessionFactory> sqlSessionFactoryList;
@Autowired(required = false)
private MPJInterceptor mpjInterceptor;
@Autowired(required = false)
private ISqlInjector iSqlInjector;
@Override
@SuppressWarnings("unchecked")
public void onApplicationEvent(ApplicationReadyEvent event) {
if (CollectionUtils.isNotEmpty(sqlSessionFactoryList) && Objects.nonNull(mpjInterceptor)) {
try {
for (SqlSessionFactory factory : sqlSessionFactoryList) {
Field interceptorChain = Configuration.class.getDeclaredField("interceptorChain");
interceptorChain.setAccessible(true);
InterceptorChain chain = (InterceptorChain) interceptorChain.get(factory.getConfiguration());
Field interceptors = InterceptorChain.class.getDeclaredField("interceptors");
interceptors.setAccessible(true);
List<Interceptor> list = (List<Interceptor>) interceptors.get(chain);
if (CollectionUtils.isNotEmpty(list)) {
if (list.get(list.size() - 1) != mpjInterceptor) {
list.removeIf(i -> i == mpjInterceptor);
list.add(mpjInterceptor);
}
} else {
list.add(mpjInterceptor);
}
}
} catch (Exception ignored) {
throw new MPJException("mpjInterceptor exception");
}
}
if (iSqlInjector != null && !(iSqlInjector instanceof MPJSqlInjector)) {
logger.error("sql注入器未继承 MPJSqlInjector -> " + iSqlInjector.getClass());
}
}
}
原来是手动塞进去的,难怪,顺便解决了分页插件的问题。文章来源:https://www.toymoban.com/news/detail-621541.html
到这里,问题的解决方法是很明显了吧。。。文章来源地址https://www.toymoban.com/news/detail-621541.html
到了这里,关于关于MyBatis拦截器失效问题的解决(多数据源、分页插件)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!