zoukankan      html  css  js  c++  java
  • MyBatis(十)插件开发 之 插件运行原理

    一、插件开发

      MyBatis在四大对象的创建过程中,都会有插件进行介入插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。

      MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。

      默认情况下, MyBatis 允许使用插件来拦截的方法调用包括:

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

      

      在四大对象创建的时候

      1、每个创建出来的对象不是直接返回的,而是通过 interceptorChain.pluginAll(parameterHandler);

      2、获取到所有的 Interceptor(拦截器)(插件需要实现的接口),调用 Interceptor.plugin(target):返回 target 包装后的对象;

      3、插件机制,可以使用插件为目标对象创建一个代理对象:AOP(面向切面)

        我们的插件可以为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行:

        核心代码:

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

    二、开发单个插件

      1、插件开发步骤

        ① 编写 Interceptor 的实现类;

        ② 使用 @Intercepts 注解完成插件签名;

        ③ 将写好的插件注册到全局配置文件中;

        案例:

    /**
     * 完成插件签名
     *      告诉MyBatis当前插件用用来拦截哪个对象的哪个方法
     */
    @Intercepts(
            {
                    @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
            }
    )
    public class MyFirstPlugin implements Interceptor {
    
        /**
         * intercept:拦截
         *      拦截目标对象的目标方法的执行。
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("MyFirstPlugin...intercept:" + invocation.getMethod());
    
            //metaObject.setValue("parameterHandler.parameterObject", "3");
    
            //执行目标方法
            Object proceed = invocation.proceed();
    
            //返回执行后的返回值
            return proceed;
        }
    
        /**
         * plugin:包装
         *      包装目标对象的;包装,为目标对象创建一个代理对象
         * @param target
         * @return
         */
        @Override
        public Object plugin(Object target) {
            System.out.println("MyFirstPlugin...plugin:MyBatis将要包装的对象" + target);
            //借助Plugin的 wrap 方法来使用当前Interceptor 包装我们目标对象
            Object wrap = Plugin.wrap(target, this);
            //返回当前 target 创建的动态代理
            return wrap;
        }
    
        /**
         * setProperties:
         *      将插件注册时 property 属性设置进来
         * @param properties
         */
        @Override
        public void setProperties(Properties properties) {
            System.out.println("插件配置的信息" + properties);
        }
    }

        配置:

        <!--
            plugins:注册插件
        -->
        <plugins>
            <plugin interceptor="com.njf.mybatis.plugin.MyFirstPlugin">
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </plugin>
        </plugins>

        测试:

         @Test
         public void testPlugin() throws IOException {
              //1、获取 sqlSessionFactory
              SqlSessionFactory sqlSessionFactory = getsqlSessionFactory();
    
              //2、获取 sqlSession 实例,能直接执行已经映射的 SQL 语句
              SqlSession sqlSession = sqlSessionFactory.openSession();
    
              try {
                   //3、获取接口的实现类对象
                   /**
                    * 推荐使用
                    * 会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
                    */
                   EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
                   //System.out.println(employeeMapper);
                   //System.out.println(employeeMapper.getClass());
                   Employee emp = employeeMapper.getEmpById(1);
                   System.out.println(emp);
              } finally {
                   sqlSession.close();
              }
         }

        运行结果:

      2、运行原理

        

         插件会产生目标对象的代理对象。

    三、多个插件

      如果我们此时又声明了一个插件,如下:

    /**
     * 完成插件签名
     *      告诉MyBatis当前插件用用来拦截哪个对象的哪个方法
     */
    @Intercepts(
            {
                    @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
            }
    )
    public class MySecondPlugin implements Interceptor {
    
        /**
         * intercept:拦截
         *      拦截目标对象的目标方法的执行。
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            System.out.println("MySecondPlugin...intercept:" + invocation.getMethod());
            //执行目标方法
            Object proceed = invocation.proceed();
    
            //返回执行后的返回值
            return proceed;
        }
    
        /**
         * plugin:包装
         *      包装目标对象的;包装,为目标对象创建一个代理对象
         * @param target
         * @return
         */
        @Override
        public Object plugin(Object target) {
            System.out.println("MySecondPlugin...plugin:MyBatis将要包装的对象" + target);
            //借助Plugin的 wrap 方法来使用当前Interceptor 包装我们目标对象
            Object wrap = Plugin.wrap(target, this);
            //返回当前 target 创建的动态代理
            return wrap;
        }
    
        /**
         * setProperties:
         *      将插件注册时 property 属性设置进来
         * @param properties
         */
        @Override
        public void setProperties(Properties properties) {
            System.out.println("插件配置的信息" + properties);
        }
    }

        配置信息:

        <!--
            plugins:注册插件
        -->
        <plugins>
            <plugin interceptor="com.njf.mybatis.plugin.MyFirstPlugin">
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </plugin>
            <plugin interceptor="com.njf.mybatis.plugin.MySecondPlugin"></plugin>
        </plugins>

      还是测试上面的方法,运行结果:

      原理分析:

        

         多个插件就会产生多层代理。

      多个插件原理:

        创建动态代理的时候,是按照插件配置顺序创建层层代理对象。

        执行目标方法的时候,是按照逆向顺序执行。

        

    四、插件原理

      1、按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理;

      2、多个插件依次生成目标对象的代理对象,层层包裹, 先声明的先包裹;形成代理链;

      3、目标方法执行时依次从外到内执行插件的intercept方法;

        

      4、多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值

    五、Interceptor 接口

      Intercept:拦截目标方法执行

      plugin:生成动态代理对象,可以使用MyBatis提供的Plugin类的wrap方法

      setProperties:注入插件配置时设置的属性

      

      常用代码:从代理链中分离真实被代理对象

            //1、分离代理对象。由于会形成多次代理,所以需要通过一个while 循环分离出最终被代理对象,从而方便提取信息
            MetaObject metaObject = SystemMetaObject.forObject(target);
            while (metaObject.hasGetter("h")) {
                Object h = metaObject.getValue("h");
                metaObject = SystemMetaObject.forObject(h);
            }
            //2、获取到代理对象中包含的被代理的真实对象
            Object obj = metaObject.getValue("target");
            //3、获取被代理对象的MetaObject方便进行信息提取
            MetaObject forObject = SystemMetaObject.forObject(obj);
  • 相关阅读:
    用confluence完成室项目管理网站的初步搭建
    初中英语单词词库 for supermemo
    用supermemo背单词4年了
    武汉一点印象
    借个iPad玩玩,越狱4.2.1成功
    TIOBE在2011年3月发布的编程语言排名表
    复杂的工作机构,一把手真是不易
    自己做的项目竟然与马拉松石油公司的数字油田思路惊人的相似!
    iPhone开发笔记[1/50]:初学iPhone上用Quartz 2D画图
    背单词的词条终于达到6500
  • 原文地址:https://www.cnblogs.com/niujifei/p/15312173.html
Copyright © 2011-2022 走看看