MyBatis 的插件对象如何创建出来的

栏目: 数据库 · 发布时间: 6年前

内容简介:MyBatis 允许我们在已映射 SQL 语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:这些接口中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。通过 MyBatis 提供的强大机制,使用插

1. 自定义插件友情提醒

MyBatis 允许我们在已映射 SQL 语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

这些接口中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。

2. 自定义插件方式

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

// ExamplePlugin.java
@Intercepts({
  @Signature(  
     type= Executor.class,
     method = "query",
     args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
public class ExamplePlugin implements Interceptor {

  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }

  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  public void setProperties(Properties properties) {
  }

}复制代码

3. 插件配置方式

定义好插件后,一般将插件配置在 MyBatis-config.xml 中即可使用:

<plugins>  
    <plugin interceptor="org.apache.ibatis.builder.ExamplePlugin" >    
        <property name="pluginProperty" value="100"/>  
    </plugin>
</plugins>复制代码

如果你用的是 Spring 项目,还可以通过在 SqlSessionFactoryBean 中注入使用:

//配置分页插件,详情请查阅官方文档
ExamplePlugin examplePlugin = new ExamplePlugin();Properties properties = new Properties();
properties.setProperty("xxx", "xxx");

examplePlugin .setProperties(properties);

//添加插件
factory.setPlugins(new Interceptor[]{examplePlugin });复制代码

4. 插件对象如何创建的

在 ExamplePlugin 中,有一个 plugin(Object target) 方法,这个方法内部调用了 Plugin.warp(Object target, Interceptor interceptor) 方法,这个 warp 方法就是要为 target 对象创建代理对象 proxy 。而这个创建出来的代理对象 proxy 就是我们要的插件对象 。那这个对象是何时何地创建出来的呢?

在 Mybatis 中,创建代理对象 proxy 的类只有一个,就是上面说的 Plugin 类,源码如下:

public class Plugin implements InvocationHandler {  
    // 保存拦截器拦截的对象(这个target可能是个拦截器,也可能就是目标对象)
    private final Object target;  
    // 拦截器对象
    private final Interceptor interceptor;  
    // 见 getSignatureMap 方法
    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;  
    } 

    // 判断interceptor是否支持target拦截,支持则创建target的代理对象,否则原样返回target  
    // 如果一个对象适配多个interceptor,则会被多次代理  
    public static Object wrap(Object target, Interceptor interceptor) {  
        // 解析 interceptor 的 @interceptors 注解中定义的内容
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
   
        // 获取 target 的字节码对象
        Class<?> type = target.getClass();    

        // 根据 type 和 signatureMap,获取 type 查找所有匹配的拦截器支持拦截的接口字节码对象数组
        Class<?>[] interfaces = getAllInterfaces(type, signatureMap);   

        // 如果 interfaces 的大小大于 0,则给 target 对象创建代理对象。否则不创建代理对象
        if (interfaces.length > 0) {      

            // 创建 target 的代理类对象。利用的是 JDK 动态代理  
            return Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap));    
        }    
        return target; 
     } 

     // 实现 JDK 的 InvocationHandler 接口的 invoke 方法
     @Override  
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {    
        try {      
            // 获取 method 所属类或接口的字节码对象
            Set<Method> methods = signatureMap.get(method.getDeclaringClass()); 
            // 判断 methods 中是否包括 method,包含则执行 拦截器的 intercept 方法     
            if (methods != null && methods.contains(method)) {        
                return interceptor.intercept(new Invocation(target, method, args));     
             }      
            // 调用 method 的 invoke 方法,进入下一层调用
            return method.invoke(target, args);   
         } catch (Exception e) {      
            throw ExceptionUtil.unwrapThrowable(e);    
         }  
     }  

    private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {    
        // 获取 @Intercepts 注解对应的字节码对象 interceptsAnnotation
        Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);    

        // 如果 interceptsAnnotation 为空,抛出插件定义错误的异常
        if (interceptsAnnotation == null) {     
            throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());          
        }    

        // 获取所有 @Intercepts 中的所有 Signature   
        Signature[] sigs = interceptsAnnotation.value();
 
        // 定义 type 和 method 的映射 map
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();    

        // 遍历 sigs ,初始化 signatureMap 
        for (Signature sig : sigs) {      
            Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());     
            try {       
                 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;  
    }  

    // 判断目标对象(Executor、StatementHandler、ParameterHandler和ResultSetHandler四个接口中的一种)是否有拦截器拦截 
    // 方法参数:  type:目标对象的字节码对象; signatureMap:是我们在 getSignatureMap 方法中得到的
    private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {    
        Set<Class<?>> interfaces = new HashSet<>();   
        while (type != null) {   
            // 获取 type 的父接口,并遍历  
            for (Class<?> c : type.getInterfaces()) {    
                 // 判断 signatureMap 中是否包含 type 的父接口   
                 if (signatureMap.containsKey(c)) {          
                    interfaces.add(c);       
                 }      
            }     
           // 获取 type 的父类,继续 while 循环
           type = type.getSuperclass();    
        }   

     // 返回匹配到的所有接口字节码对象
     return interfaces.toArray(new Class<?>[interfaces.size()]);  
   }
}
复制代码

Plugin 类实现了 JDK 中的 InvocationHandler 接口。包含 5 个方法和 3 个字段。上面的源码分析基本每一句都做了注释,看懂应该没有问题,毕竟大家都是老司机了。

其中 5 个方法作用:

(1)getSignatureMap(Interceptor interceptor){.......} 方法:

解析 interceptor 的 @Intercepts 注解中的值。返回接口字节码对象和拦截方法的映射集合 map。

比如上面的 ExamplePlugin,@Intercepts 注解中的内容是:

@Intercepts({
  @Signature(
  type= Executor.class,
  method = "query",  args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})复制代码

解析后得到的 Map 包含的值如下图:

MyBatis 的插件对象如何创建出来的

(2)wrap(Object target, Interceptor interceptor) {.....} 方法:

判断 interceptor 是佛支持 target 拦截,支持则创建 target 的代理对象,否则返回原 target。

(3)getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap){......}方法:

根据给定的 type 字节码对象,获取 signatureMap 中的 key 的值,组成集合返回。

MyBatis 的插件对象如何创建出来的

(4)invoke(Object proxy, Method method, Object[] args){......} 方法:

拦截器被 MyBatis 执行的入口就是这个 invoke 方法。具体的执行逻辑在上面的源码分析中已经添加了注释,老铁往上翻翻即可。不过这个方法中有一行代码我需要解释下,这行代码是这样的:

// 获取 method 所属类或接口的字节码对象
Set<Method> methods = signatureMap.get(method.getDeclaringClass());复制代码

这里的 method 想要从 signatureMap 中拿到值 methods,则 method 必须是拦截器作用的四大接口中的方法,即

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

中的方法。因为 signatureMap 中存储的 key 就是以上四大接口的字节码对象。可以看下图:

signatureMap 中存储的 key 和 value 示例:

MyBatis 的插件对象如何创建出来的

signatureMap.get(method.getDeclaringClass()) 想要获得返回值,method.getDeclaringClass() 的返回值必须是 MyBatis 四大接口中的任意一个的字节码对象,如下图:

MyBatis 的插件对象如何创建出来的

5. 总结

关于 MyBatis 的插件的创建就分析到这,对于插件具体的使用还是那几句话。插件会影响 MyBatis 的执行行为,务请慎之又慎。下一篇文章将会分析拦截器在 MyBatis 中作用点,即插件是在 MyBatis 的什么地方开始调用的。

关于 MyBatis 的插件化设计,请看  juejin.im/post/5cb614…

有分析不到位的地方,或者有疑惑的地方,欢迎留言分享,我们一起过过招。

还有,欢迎关注我的公众号,一起提升。

 “余生很长,莫要慌张”

MyBatis 的插件对象如何创建出来的


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Chinese Authoritarianism in the Information Age

Chinese Authoritarianism in the Information Age

Routledge / 2018-2-13 / GBP 115.00

This book examines information and public opinion control by the authoritarian state in response to popular access to information and upgraded political communication channels among the citizens in co......一起来看看 《Chinese Authoritarianism in the Information Age》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试