程序员码农的世界SSM

MyBatis插件机制分析

2019-06-24  本文已影响25人  山东大葱哥

以下内容的公开课视频已经录制,需要的同学可留言

MyBatis这个框架具有强大的灵活性,MyBatis对持久层的操作就是借助于四大组件(ExecutorStatementHandlerParameterHandlerResultSetHandler),在四大组件处提供了简单易用的插件扩展机制。

MyBatis支持用插件对四大核心组件进行拦截,对MyBatis来说插件就是拦截器,用来增强核心组件的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象。

四大核心组件简介

MyBatis 四大核心组件:

MyBatis插件原理

  1. MyBatis的插件借助于责任链的模式进行对拦截的处理
  2. 使用动态代理对目标对象进行包装,达到拦截的目的

拦截

插件具体是如何拦截并附加额外的功能的呢?以ParameterHandler 来说

 public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object object, BoundSql sql, InterceptorChain interceptorChain){
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,object,sql);
        parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
    }
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

interceptorChain 保存了所有的拦截器(interceptors),是MyBatis初始化的时候创建的。

调用拦截器链中的拦截器依次的对目标进行拦截或增强。

interceptor.plugin(target)中的target就可以理解为MyBatis中的四大组件,返回的target是被重重代理后的对象。

插件接口

MyBatis插件接口-Interceptor

  1. intercept()方法,插件的核心方法
  2. plugin()方法,生成target的代理对象
  3. setProperties()方法,传递插件所需参数

插件实例

插件开发需要以下步骤

  1. 自定义插件需要实现上述接口
  2. 增加@Intercepts注解(声明是哪个核心组件的插件,以及对哪些方法进行扩展)
  3. 在xml文件中配置插件
**
 * 插件签名,告诉MyBatis插件用来拦截那个对象的哪个方法
 **/
@Intercepts(
        {
                @Signature(
                        type = StatementHandler.class,
                        method = "parameterize",
                        args = Statement.class
                )
        }
)
public class MyIntercepts implements Interceptor {
    /**
     * 拦截目标对象的目标方法
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("进入自定义的拦截器,拦截目标对象" + invocation + invocation.getMethod() + invocation.getTarget());
        return invocation.proceed();
    }

    /**
     * 包装目标对象 为目标对象创建代理对象
     *
     * @Param target为要拦截的对象
     * @Return 代理对象
     */
    @Override
    public Object plugin(Object target) {
        System.out.println("自定义plugin方法,将要包装的目标对象" + target.toString() + target.getClass());
        return Plugin.wrap(target, this);
    }

    /**
     * 获取配置文件的属性
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("自定义插件的初始化参数" + properties);
    }
}

在mybatis-config.xml中配置插件

    <!-- 自定义插件 -->
    <plugins>
        <plugin interceptor="com.boxuegu.javaee.mybatissourcelearn.MyIntercepts">
            <property name="test" value="testvalue"/>
        </plugin>
    </plugins>

调用查询方法,查询方法会返回ResultSet

public class Test {
    public static void main(String[] args) {
        //1.加载配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //2. 获取sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //3. 获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            //通过xml文件直接执行sql语句
            //Employee employee = sqlSession.selectOne("com.boxuegu.javaee.mybatissourcelearn.dao.EmployeeMapper.getEmployeeById", 1);
            //alt+shift+L introduce local variables;
            Thread thread = new Thread(()-> System.out.println("test"));
            //4. 获取mapper接口实现
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
            System.out.println("mapper::::" + mapper.getClass());

            //5. 执行sql语句
            Employee employee = mapper.getEmployeeById(1);
            System.out.println(employee);
        } finally {
            sqlSession.close();
        }
    }
}

输出结果

自定义插件的初始化参数{test=testvalue}
自定义plugin方法,将要包装的目标对象org.apache.ibatis.executor.CachingExecutor@46f5f779class org.apache.ibatis.executor.CachingExecutor
mapper::::class com.sun.proxy.$Proxy3
自定义plugin方法,将要包装的目标对象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@1bc6a36eclass org.apache.ibatis.scripting.defaults.DefaultParameterHandler
自定义plugin方法,将要包装的目标对象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@387c703bclass org.apache.ibatis.executor.resultset.DefaultResultSetHandler
自定义plugin方法,将要包装的目标对象org.apache.ibatis.executor.statement.RoutingStatementHandler@c39f790class org.apache.ibatis.executor.statement.RoutingStatementHandler
Wed Jun 19 18:14:24 CST 2019 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
进入自定义的拦截器,拦截目标对象org.apache.ibatis.plugin.Invocation@50f8360dpublic abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLExceptionorg.apache.ibatis.executor.statement.RoutingStatementHandler@c39f790
Employee{id=1, lastName='zhangsan', email='zhangsan@itcast.cn', gender='1'}

多插件开发

  1. 创建代理对象时,按照插件配置的顺序进行包装
  2. 执行目标方法后,是按照代理的逆向进行执行

小结

  1. 遵循插件尽量不使用的原则,因为会修改底层设计

  2. 插件是生成的层层代理对象的责任链模式,使用反射机制实现

  3. 插件的编写要考虑全面,特别是多个插件层层代理的时候

上一篇下一篇

猜你喜欢

热点阅读