深入理解Mybatis插件

首先,我并没有使用过 Mybatis插件,但是这个和我写这篇文章并不冲突,估计能真正使用到插件的人也比较少,写这篇文章的目的主要是看源码时稍微看到了下它的插件实现,发现还挺绕的,于是就特意琢磨了下,然后留了一个记录。

mybatis中的插件,也就是拦截器interceptor,也挺有意思的。

它的简单使用,就直接拿文档中的示例来简单说下

使用

使用方式很简单

// 使用这个注解,表明这是一个拦截器 @Intercepts( // 方法签名 {@Signature( // 被拦截方法所在的类 type= Executor.class, // 被拦截方法名 method = "update", // 被拦截方法参数列表类型 args = {MappedStatement.class ,Object.class})} ) public class ExamplePlugin implements Interceptor { // 拦截时具体行为 @Override public Object intercept(Invocation invocation) throws Throwable { // implement pre-processing if needed Object returnObject = invocation.proceed(); // implement post-processing if needed return returnObject; } }

然后如果是xml配置的话

<plugins> <plugin interceptor="org.mybatis.example.ExamplePlugin"> <property value="100"/> </plugin> </plugins>

如果是 SpringBoot的话,应该是配合自动配置使用,将上面的类使用@Component注解,交由Spring容器管理,然后注册到mybatis的InterceptorChain

mybaits目前支持拦截的类和方法,有下面这些

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

StatementHandler (prepare, parameterize, batch, update, query)

ParameterHandler (getParameterObject, setParameters)

ResultsetHandle (handleResultSets, handleOutputParameters)

实现原理

有可能从上面的使用猜出来它的拦截器的实现原理,那就是动态代理的方式,只不过 mybatis这里并不是很直接的来使用代理,绕了个弯,于是给人感觉特别晕,也说不好这个实现是不是有些问题

就先从Executor说起吧

我们从SqlSessionFactory获取一个SqlSession时,会创建一个新的Executor实例,这个实际的创建动作在这里

org.apache.ibatis.session.Configuration#newExecutor

public Executor newExecutor(Transaction transaction, ExecutorType executorType) { // 根据设置的执行器类型,创建相应类型的执行器 executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // 如果开启了缓存,使用缓存,这里缓存执行器有点类似静态代理了 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 将原始执行器对象,包装下,生成一个新的执行器,代理后的对象 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }

InterceptorChain是一个管家,有点类似于FilterChain,但是注意,其实差别非常的大

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); } }

Interceptor是一个接口,也就是mybatis直接暴露给用户使用的需要用户实现的拦截器接口

public interface Interceptor { // 实现类填充自己的逻辑,参数为Invocation, Object intercept(Invocation invocation) throws Throwable; // 默认方法,创建代理对象 default Object plugin(Object target) { return Plugin.wrap(target, this); } // 实现类去做些事情 default void setProperties(Properties properties) { // NOP } }

着重看下一这个

// 首先这是一个Jdk动态代理的InvocationHandler实现类 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 Class<?> type = target.getClass(); // 把被代理对象所有能在签名Map中找到的直接实现的接口和祖先接口,查找出来 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); // 找到了就创建代理对象 if (interfaces.length > 0) { return Proxy.newProxyInstance( type.getClassLoader(), interfaces, // 创建一个InvocationHandler实例 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)) { // 如果当前被调用的方法是需要被拦截的,那么就执行我们自定义的拦截逻辑 // interceptor是我们自定义的拦截器,在我们自定义的拦截器里,需要获取到 // 原委托对象,被调用的方法,以及参数,这里做了很好的封装,将用户的使用和 // 具体的实现,做了一个完全的分离,用户感知不到任何具体的实现 // Invocation#proceed 就做了一件事 method.invoke(target,args); return interceptor.intercept(new Invocation(target, method, args)); } // 如果当前被调用的方法没有被拦截,那么直接调用原方法 return method.invoke(target, args); } catch (Exception e) { throw ExceptionUtil.unwrapThrowable(e); } } // 获取拦截器指定的被拦截方法的方法签名 // key是被拦截方法的返回值类型 private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) { // 获取注解 @Intercepts信息 Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class); // issue #251 避免出现没有具体的拦截信息的情况 if (interceptsAnnotation == null) { throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName()); } // 获取配置的被拦截方法的签名信息 Signature[] sigs = interceptsAnnotation.value(); Map<Class<?>, Set<Method>> signatureMap = new HashMap<>(); // 统统放到Map for (Signature sig : sigs) { Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>()); try { // 将定义构建成实际的Method对象 Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); } catch (NoSuchMethodException e) { throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e); } } return signatureMap; } // 获取被代理对象所有的接口信息 private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) { Set<Class<?>> interfaces = new HashSet<>(); while (type != null) { for (Class<?> c : type.getInterfaces()) { if (signatureMap.containsKey(c)) { // 如果被代理类接口和返回值类型一致,接口加进来 interfaces.add(c); } } // 找到被代理类的父类,然后继续查找接口信息 type = type.getSuperclass(); } return interfaces.toArray(new Class<?>[interfaces.size()]); } }

到这里就说完了mybatis插件的实现原理

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppydw.html