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、编写插件

/**
 * Description:
 *
 * @author 
 * @date 2021/4/22 12:58
 */
@SuppressWarnings("all")
/*
    完成插件签名: 告诉MyBatis当前插件用来拦截哪个对象哪个方法
 */
@Intercepts(
        {
                @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
        }
)
public class MyPlugin implements Interceptor {

    /**
     * 拦截对象目标方法的执行
     *
     * @param invocation 拦截
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("MyPlugin.intercept:" + invocation.getMethod());
        // 执行目标方法
        Object proceed = invocation.proceed();
        return proceed;
    }

    /**
     * 包装目标对象, 包装: 为目标对象创建一个代理对象
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        System.out.println("MyPlugin.plugin mybatis将要包装的对象" + target);
        // 该方法就是让当前的Interceptor拦截器来包装我们的对象
        Object wrap = Plugin.wrap(target, this);
        // wrap就是target的动态代理对象
        return wrap;
    }

    /**
     * 将插件注册时的property属性设置进来
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("插件配置的信息: " + properties);
    }
}

2、配置插件

<!-- mybatis-config.xml -->
    <plugins>
        <plugin interceptor="com.sunny.mapper.MyPlugin">
            <!-- 可选 -->
            <property name="username" value="root"/>
            <property name="password" value="8888"/>
        </plugin>
    </plugins>

3、测试插件

随便执行一个查询方法, 因为拦截的是StatementHandler的parameterize的方法, 因为在mapper.getUser的时候, 底层会调用这个预编译参数方法, 所以会拦截到该方法

@Test
public void testQueryOneUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    // 使用方式一: 来找到SQL并执行
    //User user = sqlSession.selectOne("com.sunny.dao.UserMapper.getUser", 1L);

    // 使用方式二: 来找到SQL并执行
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    // 实际上底层调用的还是sqlSession的方法,注意:sqlSession调用CRUD方法只能传递一个参数
    User user = mapper.getUser(12);

    System.out.println(user);
    sqlSession.close();
}

在这里插入图片描述

打印信息

// 打印了配置的信息
插件配置的信息: {password=8888, username=root}
// 这里在创建4大对象的时候, 都会调用PluginAll方法, 然后里面会拿到所有实现Interceptor的
// 拦截器进行拦截包装
MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.CachingExecutor@77ec78b9
MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@42607a4f
MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@25bbf683
MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.statement.RoutingStatementHandler@7276c8cd
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id = ?; 
// 只会为StatementHandler对象创建代理对象, 拦截到parameterize方法
// 因为Executor, parameterHandler, ResultSetHandler的签名不属于StatementHandler
// 所以就没有为它们创建代理对象
MyPlugin.intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
DEBUG [main] - ==> Parameters: 12(Integer)
TRACE [main] - <==    Columns: id, name, pwd
TRACE [main] - <==        Row: 12, lisi, 44
DEBUG [main] - <==      Total: 1
User{id=12, name='lisi', pwd='44'}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为StatementHandler对象创建代理对象
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在执行到设置参数预编译的方式时
在这里插入图片描述

4、多个插件同时拦截同一对象的方法的运行流程

在这里插入图片描述

5、开发插件

  • 我们自己编写的插件会为目标对象创建一个代理对象(通过动态代理), 当目标对象的目标方法要执行的时候, 都会来到代理对象的intercept方法, 可以在该方法中在执行目标方法的前后, 做增强/包装

  • 完成功能: 偷梁换柱, 传入用户id为1, 实际查出来的是id为2的员工

/**
 * Description:
 *
 * @author 
 * @date 2021/4/22 12:58
 */
@SuppressWarnings("all")
/*
    完成插件签名: 告诉MyBatis当前插件用来拦截哪个对象哪个方法
 */
@Intercepts(
        {
                @Signature(type = StatementHandler.class, method = "parameterize", args = java.sql.Statement.class)
        }
)
public class MyPlugin implements Interceptor {


    /**
     * 拦截对象目标方法的执行
     *
     * @param invocation 拦截
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("MyPlugin.intercept:" + invocation.getMethod());
        // 因为该插件拦截的是parameterize方法, 所以就动态改变一下sql运行的参数,来测试一下
        // 偷梁换柱: 我要原来要查1号员工信息, 实际从数据库查2号员工
        Object target = invocation.getTarget();
        System.out.println("当前拦截到的对象: " + target);
        // 通过原理, 我们只要修改setParameters中的parameterObject的值即可
        // 要拿到StatementHandler -> ParameterHandler -> parameterObject
        MetaObject metaObject = SystemMetaObject.forObject(target); // 通过该方法拿到target的元数据
        Object value = metaObject.getValue("parameterHandler.parameterObject");
        System.out.println("sql语句传入的参数为:" + value);
        // 修改sql语句传进来的参数为2
        metaObject.setValue("parameterHandler.parameterObject", 2);
        // 执行目标方法
        Object proceed = invocation.proceed();
        return proceed;
    }

    /**
     * 包装目标对象, 包装: 为目标对象创建一个代理对象
     *
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        System.out.println("MyPlugin.plugin mybatis将要包装的对象" + target);
        // 该方法就是让当前的Interceptor拦截器来包装我们的对象
        Object wrap = Plugin.wrap(target, this);
        // wrap就是target的动态代理对象
        return wrap;
    }

    /**
     * 将插件注册时的property属性设置进来
     *
     * @param properties
     */
    @Override
    public void setProperties(Properties properties) {
        System.out.println("插件配置的信息: " + properties);
    }
}
插件配置的信息: {password=8888, username=root}
MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.CachingExecutor@77ec78b9
MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.scripting.defaults.DefaultParameterHandler@42607a4f
MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.resultset.DefaultResultSetHandler@25bbf683
MyPlugin.plugin mybatis将要包装的对象org.apache.ibatis.executor.statement.RoutingStatementHandler@7276c8cd
DEBUG [main] - ==>  Preparing: SELECT * FROM user WHERE id = ?; 
MyPlugin.intercept:public abstract void org.apache.ibatis.executor.statement.StatementHandler.parameterize(java.sql.Statement) throws java.sql.SQLException
当前拦截到的对象: org.apache.ibatis.executor.statement.RoutingStatementHandler@7276c8cd
sql语句传入的参数为:1
DEBUG [main] - ==> Parameters: 2(Integer)
TRACE [main] - <==    Columns: id, name, pwd
TRACE [main] - <==        Row: 2, lisi, 44
DEBUG [main] - <==      Total: 1
User{id=2, name='lisi', pwd='44'}
补充pageHelper分页插件的使用
@Test
public void test01() throws IOException {
	// 1、获取sqlSessionFactory对象
	SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
	// 2、获取sqlSession对象
	SqlSession openSession = sqlSessionFactory.openSession();
	try {
		EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
		Page<Object> page = PageHelper.startPage(5, 1);
		
		List<Employee> emps = mapper.getEmps();
		//传入要连续显示多少页
		PageInfo<Employee> info = new PageInfo<>(emps, 5);
		for (Employee employee : emps) {
			System.out.println(employee);
		}
		/*System.out.println("当前页码:"+page.getPageNum());
		System.out.println("总记录数:"+page.getTotal());
		System.out.println("每页的记录数:"+page.getPageSize());
		System.out.println("总页码:"+page.getPages());*/
		///xxx
		System.out.println("当前页码:"+info.getPageNum());
		System.out.println("总记录数:"+info.getTotal());
		System.out.println("每页的记录数:"+info.getPageSize());
		System.out.println("总页码:"+info.getPages());
		System.out.println("是否第一页:"+info.isIsFirstPage());
		// 就是页面右下角可以连续显示的页码数
		// 点击第1页, 1 2 3 4 5 
		// 点击第2页, 1 2 3 4 5 
		// 点击第3页, 1 2 3 4 5 
		// 点击第4页, 2 3 4 5 6 
		// 点击第5页, 3 4 5 6 7 这样的显示规则
		System.out.println("连续显示的页码:");
		int[] nums = info.getNavigatepageNums();
		for (int i = 0; i < nums.length; i++) {
			System.out.println(nums[i]);
		}
		//xxxx
	} finally {
		openSession.close();
	}
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

white camel

感谢支持~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值