插件开发

插件的运行原理

  • 在创建四大对象的时候每个创建出来的对象不是直接返回的,而是执行了 interceptorChain.pluginAll(parameterHandler);

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

  • 插件机制,我们可以使用插件为目标对象创建一个代理对象(AOP思想)

    • 我们的插件可以为四大对象创建出代理对象

    • 代理对象就可以拦截到四大对象的每一个执行

编写插件的步骤

  • 编写Interceptor的实现类

  • 继承Interceptor实现其中的方法

    • intercept(Invocation invocation):拦截目标对象的目标方法的执行

      • invocation.proceed(),放行,执行目标方法

    • plugin(Object target):包装目标对象,为目标对象创建一个代理对象

    •  setProperties(Properties properties):将插件运行时的属性设置进来

  • 在自定义的插件类上使用@Intercepts告诉mybatis拦截的方法,完成插件的签名

需要填写一个Signature数组,继续点击

例如要拦截StatementHandler,它的里面有下列的方法:

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

<!-- 注册插件 -->
<plugins>
    <plugin interceptor="com.jx.mbg.dao.MyPlugin">
        <!-- 还可以传一些值 -->
        <!--
            这些properties会包装到Properties中,可以在重写的setProperties()方法中看到
            @Override
            public void setProperties(Properties properties) {
                System.out.println("插件配置的信息" + properties);
            }
        -->
        <property name="username" value="root"/>
        <property name="password" value="123"/>
    </plugin>
</plugins>

完整的信息

package com.jx.mbg.dao;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;

import java.sql.Statement;
import java.util.Properties;

/**
 * 完成插件签名,告诉mybatis当前拦截用来拦截哪个对象的哪个方法
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "parameterize", args = Statement.class)
})
public class MyPlugin implements Interceptor {
    /**
     * 拦截目标方法
     * @param invocation
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获取要拦截的方法
        System.out.println("MyPlugin...interceptor:" + invocation.getMethod());
        // 放行,执行目标方法
        Object proceed = invocation.proceed();
        return proceed;
    }

    /**
     * 包装目标对象,为目标对象创建一个代理对象
     * @param target
     * @return
     */
    @Override
    public Object plugin(Object target) {
        System.out.println("mybatis将要包装的对象:" + target);
        // 可以借助Plugin的wrap方法来使用我们当前的Interceptor包装我们的目标对象
        Object wrap = Plugin.wrap(target, this);
        // 返回为当前target创建放入动态代理
        return wrap;
    }

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

多插件运行

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

在拦截StatementHandler的时候,第一个插件会先拦截,产生一个代理对象。而这个代理对象又会交给第二个插件拦截,又产生一个代理对象。会产生多层代理

因此在执行目标方法的时候会先进最外层的代理对象,也就是先执行第二个插件的interceptor()方法,再执行第一个插件的interceptor()方法,最后执行真正目标对象的方法

举个栗子

动态改变sql运行参数,例:以前查询1号员工,在插件中更改为3号员工

@Override
public Object intercept(Invocation invocation) throws Throwable {
    // 获取要拦截的对象,StatementHandler
    Object target = invocation.getTarget();
    // 拿到:StatementHandler ==> ParameterHandler ==> parameterObject的值
    // 拿到target的元数据
    MetaObject metaObject = SystemMetaObject.forObject(target);
    // 获取参数值
    Object value = metaObject.getValue("parameterHandler.parameterObject");
    System.out.println("SQL语句用的参数是:" + value);
    // 修改参数值
    metaObject.setValue("parameterHandler.parameterObject", 3);
    // 放行,执行目标方法
    Object proceed = invocation.proceed();
    return proceed;
}

 使用PageHelper

在知道了插件的运行原理后,就可以尝试着自定义一些插件来完成一些功能,有一款比较出色的插件,叫做PageHelper,使用它就可以帮我们完成分页功能

1、导入jar包或者maven依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.8</version>
</dependency>

2、在全局配置文件中配置插件

<!-- 配置pageHelper -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
</plugin>

参数设置参考: 

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

3、使用

只需要在查询方法前加上PageHelper.startPage(int start, int size); 即可

注意:这一方法会返回分页的所有信息

@Test
public void test3() throws IOException {
    String configPath = "mybatis/mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(configPath);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        /**
         * 在查询前加上该方法,startPage的参数
         * 参数一:起始的页数,第一页就是1
         * 参数二:要查询的记录数
         * 该方法会返回一个分页对象,可以查看分页的信息
         */
        Page<Object> objects = PageHelper.startPage(1, 2);
        List<Employee> employees = mapper.selectAll();
        employees.forEach(System.out::println);
        // 打印分页信息
        System.out.println("当前页码:" + objects.getPageNum());
        System.out.println("总记录数:" + objects.getTotal());
        System.out.println("每页的记录数:" + objects.getPageSize());
        System.out.println("当前页码:" + objects.getPages());
    }
}

当然,也可以使用PageInfo获取分页信息,只需要把查询后的list传入即可

//获取第1页,10条内容,默认查询总数countPageHelper.startPage(1, 10);List<Country> list = countryMapper.selectAll();//用PageInfo对结果进行包装PageInfo page = new PageInfo(list);//测试PageInfo全部属性//PageInfo包含了非常全面的分页属性
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
assertEquals(1, page.getStartRow());
assertEquals(10, page.getEndRow());
assertEquals(183, page.getTotal());
assertEquals(19, page.getPages());
assertEquals(1, page.getFirstPage());
assertEquals(8, page.getLastPage());
assertEquals(true, page.isFirstPage());
assertEquals(false, page.isLastPage());
assertEquals(false, page.isHasPreviousPage());
assertEquals(true, page.isHasNextPage());

PageInfo还有一个更加强大的用法,,它提供了第二个参数,可以设置连续显示几页

什么意思?如果我们需要在前端做显示,经常会在下面显示几个按钮,上面是页码,比如连续显示5页,那么PageInfo就会实时的显示这些页码,例如:

当页面是第一页时:返回一个数组  [1, 2, 3, 4, 5]

第二页:[1, 2, 3, 4, 5]

第三页:[1, 2, 3, 4, 5]

第四页:[2, 3, 4, 5, 6]    // 可以看到这里页面已经变化了

PageHelper.startPage(1, 2);
List<Employee> employees = mapper.selectAll();
employees.forEach(System.out::println);
// 这里连续显示5页
PageInfo<Employee> employeePageInfo = new PageInfo<>(employees, 5);
// 拿到连续显示的页码
int[] navigatepageNums = employeePageInfo.getNavigatepageNums();
System.out.println(navigatepageNums);
System.out.println(employeePageInfo.getNavigateFirstPage());
System.out.println(employeePageInfo.getNavigateLastPage());

 有了这样一个数组,在前端做分页显示就非常方便了

posted @ 2018-11-19 21:28  Jin同学  阅读(170)  评论(0)    收藏  举报