原理

Mybatis插件的实现是基于四大核心对象来实现的,本质都是拦截器,基于动态代理实现。

  • Executor对象:基于执行器对象
  • StatementHandler对象:预处理Sql对象
  • ParamterHandler对象:参数处理器对象
  • ResultSetHandler对象:结果处理器对象

具体的代码是怎么的呢?其实是借助了 InterceptorChain 的pluginAll方法,在创建上面的四大对象的时候都会调用这个方法,如图:
mybatis_plugins.png
这个时候,其实创建出来的四大对象已经是代理对象了,这样在执行四大对象的方法的时候就会被代理到invoke方法。
具体的插件如何定义呢?首先需要实现 Interceptor接口,然后在类上使用注解声明被代理的类、方法以及方法的参数,至于加参数的目的是防止方法的重构。
类定义完成之后最后将类配置在configuration配置文件中,使用plugin标签进行配置就ok了。这样启动的时候就会加载插件,具体的操作我们放在下面。

自定义插件

手首先自定义类,实现Interceptor接口:

  1. @Intercepts({
  2. @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
  3. })
  4. public class MyPlugins implements Interceptor {
  5. /**
  6. * 代理方法执行时要执行的方法,在代理方法执行前执行
  7. * @param invocation
  8. * @return
  9. * @throws Throwable
  10. */
  11. @Override
  12. public Object intercept(Invocation invocation) throws Throwable {
  13. System.out.println("进行方法增强:" + invocation);
  14. // 执行业务逻辑
  15. return invocation.proceed();
  16. }
  17. /**
  18. * 将当前插件保存到mybatis的插件链中
  19. * @param target
  20. * @return
  21. */
  22. @Override
  23. public Object plugin(Object target) {
  24. System.out.println("保存插件链路中:" + target);
  25. Object wrap = Plugin.wrap(target, this);
  26. return wrap;
  27. }
  28. /**
  29. * 设置插件的属性
  30. * @param properties
  31. */
  32. @Override
  33. public void setProperties(Properties properties) {
  34. System.out.println("读取插件的属性为:" + properties);
  35. }
  36. }

之后在configuration配置文件中配置:

  1. <plugins>
  2. <plugin interceptor="com.wangzhi.plugin.MyPlugins">
  3. <property name="name" value="芊儿"/>
  4. </plugin>
  5. </plugins>
  6. // 注意配置标签的一个顺序问题

最后可以查看执行结果,设置插件属性的方法最先执行,之后会执行四次plugin方法,为什么?因为我们插件可以代理的对象就是四大对象,所以这里会将插件分别保存到四大对象的链路中,最后才会执行intercept方法。

至于具体是如何实现的呢? 我们都知道使用的四大对象其实都是代理对象,所以执行任何方法都会执行invoke方法,invoke方法就在于Plugin类中,如下:

  1. @Override
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3. try {
  4. // 获取类的所有方法列表,至于 signatureMap 是所有插件设置代理的类,也就是上面注解中的type属性
  5. Set<Method> methods = signatureMap.get(method.getDeclaringClass());
  6. // 判读被增强方法是否包含了当前执行的方法
  7. if (methods != null && methods.contains(method)) {
  8. return interceptor.intercept(new Invocation(target, method, args));
  9. }
  10. return method.invoke(target, args);
  11. } catch (Exception e) {
  12. throw ExceptionUtil.unwrapThrowable(e);
  13. }
  14. }

PageHelper插件

原理一样,实现也是先实现接口Interceptor,重写方法,配置文件配置,使用就好。

  1. <!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
  2. <dependency>
  3. <groupId>com.github.pagehelper</groupId>
  4. <artifactId>pagehelper</artifactId>
  5. <version>5.2.0</version>
  6. </dependency>
  7. // 配置文件进行配置
  8. <plugin interceptor="com.github.pagehelper.PageInterceptor">
  9. <!--<property name="dialect" value="mysql"/>-->
  10. </plugin>
  11. // 注意版本差别,当前笔者用着5.2,如果是5.0之下,可以看源码,看到是 PageHelper 类实现的接口,从5.0开始,是PageInterceptor实现的接口,所以5.0一下的配置:
  12. <plugin interceptor="com.github.pagehelper.PageHelper">
  13. <!—指定方言 —>
  14. <property name="dialect" value="mysql"/>
  15. </plugin>

测试类:

  1. @Test
  2. public void testPageHelper() {
  3. PageHelper.startPage(1, 1);
  4. List<User> allByAnno = mapper.findAllByAnno();
  5. PageInfo<User> userPageInfo = new PageInfo<>(allByAnno);
  6. System.out.println("总条数:" + userPageInfo.getTotal());
  7. System.out.println("总页数:" + userPageInfo.getPages());
  8. System.out.println("当前页:" + userPageInfo.getPageNum());
  9. System.out.println("每页条数:" + userPageInfo.getPageSize());
  10. userPageInfo.getList().forEach(System.out::println);
  11. }