首页 > 其他分享 >Mybatis 之 插件原理

Mybatis 之 插件原理

时间:2022-10-28 22:01:07浏览次数:70  
标签:插件 return target 对象 Object Mybatis 原理 class

核心代码简介

MyBatis 插件原理:责任链模式  +  JDK 动态代理 ( 接口、代理对象、代理类:实现 jdk 的 InvocationHandler )

Mybatis  插件核心接口:Interceptor

/**
 * MyBatis 插件的和心接口
 */
public interface Interceptor {
 
    /**
     * 这个方法是插件的核心方法,插件必须实现此方法
     * invocation 这个对象通过发生调用原来的对象方法
     * 插件的核心是执行是拦截四个接   口的子对象,拦截以后进入 intercept() 函数执行相关的业务
     * invocation 对象可以获取四个接口的具体实现类
     *
     * @param invocation 这个对象包含了目标对象(四大接口),代理方法,参数
     * @return
     * @throws Throwable
     */
    Object intercept(Invocation invocation) throws Throwable;
 
    /**
     * 作用是: 把拦截的对象编程一个代理对象,并返回他(目标的代理对象)
     *
     * @param target 被拦截的对象
     * @return
     */
    default Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }
 
    /**
     * 允许插件在注册的时候,配置插件需要的参数,这个参数可以下 Mybatis 的核心配置文件中祖册插件的时候一起配置
     *
     * @param properties
     */
    default void setProperties(Properties properties) {
        
    }
}

核心注解:

@Intercepts、

@Signature 签名注解,

type:申明此插件拦截器具体需要拦截那个接口的实现类对象,(四大接口对象)

method:申明此插件拦截器具体需要拦截处理那个 方法 (type 中的方法名称)

args : 此拦截器拦截到的方法中,参数的类型,(type 中的方法对应的具体参数类型)

代码解析如下:

使用示例:

@Intercepts(
        {
                @Signature(type = StatementHandler.class, method = "prepare",
                        args = {Connection.class, Integer.class}),
                @Signature(type = StatementHandler.class, method = "getBoundSql",
                        args = {}),
                @Signature(type = Executor.class, method = "update",
                        args = {MappedStatement.class, Object.class}),
                @Signature(type = Executor.class, method = "query",
                        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
                @Signature(type = Executor.class, method = "query",
                        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class,
                                CacheKey.class, BoundSql.class}),
        }
)

注解代码:@Intercepts 

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
    /**
     * 签名注解
     *
     * @return
     */
    Signature[] value();
}

注解代码:@Signature 

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
    /**
     * 申明此插件拦截器具体需要拦截那个接口的实现类对象,
     *
     * @return
     */
    Class<?> type();
 
    /**
     * 申明此插件拦截器具体需要拦截处理那个 方法
     *
     * @return
     */
    String method();
 
    /**
     * 拦截的方法中,参数的数据类型
     * 如:Executor 中的 int update(MappedStatement ms, Object parameter);
     * args = {MappedStatement.class, Object.class}
     *
     * @return
     */
    Class<?>[] args();
}

 


四大接口介绍:

1、org.apache.ibatis.executor.Executor

(Update、query、flushStatements、commit、rollback、getTransaction、close、isClosed)拦截执行的方法

参看: https://www.cnblogs.com/virgosnail/p/10067964.html

2、org.apache.ibatis.executor.parameter.ParameterHandler  拦截参数的处理

请参看: https://www.cnblogs.com/virgosnail/p/10068355.html

 

3、org.apache.ibatis.executor.resultset.ResultSetHandler  拦截结果集的处理(映射成POJO对象的处理)

请参看: https://www.cnblogs.com/virgosnail/p/10079712.html

 

4、org.apache.ibatis.executor.statement.StatementHandler  拦截 SQL 语句的构建处理(修改SQL脚本的处理)

请参看: https://www.cnblogs.com/virgosnail/p/10073235.html

 


源码简介

1、插件注册阶段:

插件初始化:插件初始化是在 Mybatis 初始化的时候完成的,如: 通过 XMLConfigBuilder 的解析方法从 xml 文件中解析,生产 Configuration 对象进行的初始化,
插件对象创建 InterceptorChain :

public class InterceptorChain {
 
    /**
     * 作用是: 把解析的插件进行注册和容器的收集
     */
    private final List<Interceptor> interceptors = new ArrayList<>();
 
 
    /**
     * 把具体的四大接口的具体实现类生成代理对象
     *
     * @param target 四大接口的实现类对象
     * @return
     */
    public Object pluginAll(Object target) {
        for (Interceptor interceptor : interceptors) {
            target = interceptor.plugin(target);
        }
        return target;
    }
 
    /**
     * 插件注册的具体方法,调佣此方法会将插件添加的集合中
     *
     * @param interceptor
     */
    public void addInterceptor(Interceptor interceptor) {
        interceptors.add(interceptor);
    }
 
    /**
     * 获取已注册的插件
     *
     * @return
     */
    public List<Interceptor> getInterceptors() {
        return Collections.unmodifiableList(interceptors);
    }
 
}

XMLConfigBuilder 

private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        // 1、循环配置中的所有自定义插件
        for (XNode child : parent.getChildren()) {
            String interceptor = child.getStringAttribute("interceptor");
            // 2、如果插件中有配置属性,将属性获取,并注册到 Properties 对象中
            Properties properties = child.getChildrenAsProperties();
            // 3、同时获取插件注册的具体对象
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
            // 4、调用插件的属性赋值方法,将属性赋值给插件中使用
            interceptorInstance.setProperties(properties);
            // 5、注册插件到集合中
            configuration.addInterceptor(interceptorInstance);
        }
    }
}
2、插件执行阶段:

通过动态代理组织多个插件(拦截器),通过这些插件可以改变 Mybatis 的默认行为,Mybatis 允许在已映射的语句执行过程中的某一个点进行拦截调用处理,默认情况下 Mybatis 允许拦截调用的方法包括:Update、query、flushStatements、commit、rollback、getTransaction、close、 isClosed

以 insert 为例:  DefaultSqlSession 类中

@Override
public int insert(String statement, Object parameter) {
    // 参数往下传调用 update()
    return update(statement, parameter);
}
 
@Override
public int update(String statement) {
    return update(statement, null);
}
 
@Override
public int update(String statement, Object parameter) {
    try {
        dirty = true;
        MappedStatement ms = configuration.getMappedStatement(statement);
        // 开始调用执行器执行方法
        return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
        throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

执行器创建:

org.apache.ibatis.session.defaults.DefaultSqlSessionFactory:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        // 初始化一个执行器
        final Executor executor = configuration.newExecutor(tx, execType);
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

org.apache.ibatis.session.Configuration 中进行了执行器的初始化

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);
    }
    // 调用 InterceptorChain 实例 的 pluginAll() 方法执行到 插件的 plugin() 方法
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}

类: org.apache.ibatis.plugin.InterceptorChain,  (执行update时,target是执行器 Executor 的对象)

public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  return target;
}

将 target(执行update时,target是执行器 Executor)的对象进行变换为代理对象 

public interface Interceptor {
 
  Object intercept(Invocation invocation) throws Throwable;
 
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

 

org.apache.ibatis.plugin.Plugin 是一个代理工具类,最终代理执行  intercept( Invocation invocation )

/**
 * 这就是一个代理工具类,实现了 JDK 反射中的 InvocationHandler 动态代理
 */
public class Plugin implements InvocationHandler {
 
    // 代理对象中的目标对象
    private final Object target;
    // 插件
    private final Interceptor interceptor;
    // @Signature 注解中生命的方法,初始化阶段会解析注解 @Intercepts 进行加载进这个 Map
    private final Map<Class<?>, Set<Method>> 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) {
            // new 了一个 代理对象(InvocationHandler)进行代理
            return Proxy.newProxyInstance(
                    // 指定当前目标对象使用的类加载器
                    type.getClassLoader(),
                    // 目标对象实现的接口类型
                    interfaces,
                    new Plugin(target, interceptor, signatureMap));
        }
        return target;
    }
 
 
    /**
     * JDK 动态代理的执行代理的方法
     *
     * @param proxy 代理对象
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @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)) {
                // 代理对象执行方法,插件执行,Invocation 对象包含了 目标对象,被代理的方法,参数
                return interceptor.intercept(new Invocation(target, method, args));
            }
            // 如果执行的方法不属于签名中的方法,那么,不需要通过代理插件执行
            return method.invoke(target, args);
        } catch (Exception e) {
            throw ExceptionUtil.unwrapThrowable(e);
        }
    }
}

 

标签:插件,return,target,对象,Object,Mybatis,原理,class
From: https://www.cnblogs.com/Alay/p/16837652.html

相关文章

  • 利用Kong 的 request-transformer 插件重写 URL
    1.背景介绍需求是将URL:www.abc.com/api/item/111 重写成 www.xyz.com/open/item/itemdetail?id=111。且域名不变,不能发生302跳转。2.request-transformerrequest-transf......
  • 【SpringBoot】引入mybatis及连接Mysql数据库
    创建一个SpringBoot项目其他不赘叙了,引入MyBaties、MySql依赖编辑 创建mysql表CREATETABLEsp_users(`id`INTPRIMARYKEY,`username`VARCHAR(30),`age`INT);刚......
  • 【视频】Copula算法原理和R语言股市收益率相依性可视化分析|附代码数据
    阅读全文:http://tecdat.cn/?p=6193copula是将多变量分布函数与其边缘分布函数耦合的函数,通常称为边缘。在本视频中,我们通过可视化的方式直观地介绍了Copula函数,并通过R软......
  • Chrome 浏览器运行机制原理解析 All In One
    Chrome浏览器运行机制原理解析AllInOne性能优化Insidelookatmodernwebbrowser深入了解现代网络浏览器Blog-ChromeDevelopershttps://developer.chrome.......
  • 16_Vue列表渲染中key的工作原理和虚拟DOM对比算法
    key的作用粗略的讲,key的作用就是给节点设置一个唯一的标识就像我们人类社会中,每个人的身份证号一样在大部分对key要求不是很严格的场景下,使用index作为key是没问......
  • 云小课|MRS基础原理之Hudi介绍
    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说)、深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云。更多精彩内容请单击......
  • Mybatis出现java.io.IOException: Could not find resource XXX.xml异常
    //使用MyBatis提供的Resources类加载mybatis的配置文件Readerreader=Resources.getResourceAsReader("MenuConfig.xml");这条语句中不会写文件路径解决办......
  • 02-MyBatis-Plus
    MyBatis-Plus实现数据库crud操作1.mp是什么MyBatis-Plus(opensnewwindow)(简称MP)是一个MyBatis(opensnewwindow)的增强工具,在MyBatis的基础上只做增强不做改变,......
  • 云小课|MRS基础原理之Hudi介绍
    阅识风云是华为云信息大咖,擅长将复杂信息多元化呈现,其出品的一张图(云图说)、深入浅出的博文(云小课)或短视频(云视厅)总有一款能让您快速上手华为云。更多精彩内容​​请单......
  • Kubenetes 安装网络插件(calico)
    简介Calico是Kubernetes生态系统中另一种流行的网络选择。虽然Flannel被公认为是最简单的选择,但Calico以其性能、灵活性而闻名。Calico的功能更为全面,不仅提供主机和pod之......