小言_互联网的博客

Mybatis学习笔记(三)- Mybatis插件原理

426人阅读  评论(0)

通过之前的分析和代码跟踪,我们基本上了解了mybatis的一些大概情况。但在文中结尾的时候,我们说对于mybatis的插件的原理还不足够的清晰。当时我们通过分析得出的结论是pagehelper插件的starter会将拦截器放到ioc容器中,然后在sqlsessionfactory创建的时候会将拦截器设置到executor中。但是我们executor的显示调用上我们并没有发现走了拦截器相关的逻辑。而我们知道拦截器是一个jdk代理,所以我们将重点放到jdk代理上。希望据此能够学到jdk代理的内涵。

首先我们实现一个简单的jdk代理。


   
  1. public interface MybatisPluginTest {
  2. void execute();
  3. }

   
  1. public class MybatisPluginTestImpl implements MybatisPluginTest{
  2. @Override
  3. public void execute() {
  4. System.out.println( "---------执行中------------");
  5. }
  6. }

   
  1. public class MybatisPluginsProxy implements InvocationHandler {
  2. private Object object;
  3. MybatisPluginsProxy( Object o){
  4. this.object=o;
  5. }
  6. @Override
  7. public Object invoke( Object proxy, Method method, Object[] args) throws Throwable {
  8. System.out.println( "-----执行前");
  9. Object object1=method.invoke( object,args);
  10. System.out.println( "-----执行后");
  11. return object1;
  12. }
  13. public static Object wrap( Object target){
  14. return Proxy.newProxyInstance(target.getClass().getClassLoader(),
  15. target.getClass().getInterfaces(), new MybatisPluginsProxy(target));
  16. }
  17. }

   
  1. public class MybatisPluginMain {
  2. public static void main(String[] args) {
  3. MybatisPluginTest test= new MybatisPluginTestImpl();
  4. MybatisPluginTest proxy= (MybatisPluginTest) MybatisPluginsProxy.wrap(test);
  5. proxy.execute();
  6. }
  7. }

执行的结果

通过jdk代理我们在代理类中做了一些操作,但是现实的状况是我们的业务可能会比较多,难道每次都都要改代码,这样好麻烦,而且扩容性并不好。但是我们的业务扩展其实都是可以通过接口进行抽象。我们只需要将接口的实现添加进来,然后代理就好,也就是说我们要代理的不在是一个,而是一批符合规范的所有。

据此,我们继续开始改进


   
  1. public interface MyPlugin {
  2. void interceptor();
  3. }

   
  1. public class MyPluginOne implements MyPlugin {
  2. @Override
  3. public void interceptor() {
  4. System.out.println( "--插件1");
  5. }
  6. }

   
  1. public class MyPluginTwo implements MyPlugin {
  2. @Override
  3. public void interceptor() {
  4. System.out.println( "--插件2");
  5. }
  6. }

   
  1. public class MybatisPluginsProxy implements InvocationHandler {
  2. private Object object;
  3. private Listinterceptors;
  4. MybatisPluginsProxy( Object o,Listo1){
  5. this.object=o;
  6. this.interceptors=o1;
  7. }
  8. @Override
  9. public Object invoke( Object proxy, Method method, Object[] args) throws Throwable {
  10. System.out.println( "-----执行前");
  11. //先执行插件
  12. interceptors.forEach(MyPlugin::interceptor);
  13. Object object1=method.invoke( object,args);
  14. System.out.println( "-----执行后");
  15. return object1;
  16. }
  17. public static Object wrap( Object o,Listtarget){
  18. return Proxy.newProxyInstance(o.getClass().getClassLoader(),
  19. o.getClass().getInterfaces(), new MybatisPluginsProxy(o,target));
  20. }
  21. }

   
  1. public class MybatisPluginMain {
  2. public static void main(String[] args) {
  3. MybatisPluginTest test= new MybatisPluginTestImpl();
  4. //添加两个插件
  5. MyPlugin test1= new MyPluginOne();
  6. MyPlugin test2= new MyPluginOne();
  7. List list= new ArrayList<>();
  8. list.add(test1);
  9. list.add(test2);
  10. MybatisPluginTest proxy= (MybatisPluginTest) MybatisPluginsProxy.wrap(test, list);
  11. proxy.execute();
  12. }
  13. }

但是我们发现问题是我们的拦截器还不够成熟,我们的拦截器只能在要调用的方法之前或者之后执行。那么如何让插件真正的像个代理类一样工作。既然如此,那么我们就可以偷梁换柱一下,将我们的代理类放到拦截器里,然后执行方法的时候先走拦截器,然后在拦截器中调用真正的代理类方法。

1.代理包装接口


   
  1. public interface MyInvocation {
  2. Object Testinterceptor() throws InvocationTargetException, IllegalAccessException;
  3. }

2.代理包装实现


   
  1. public class MyInvocationTest implements MyInvocation{
  2. private Object object;
  3. private Object[] plugin;
  4. private Method method;
  5. MyInvocationTest(Object o, Method method, Object[] myPlugin){
  6. this. object=o;
  7. this.plugin=myPlugin;
  8. this.method=method;
  9. }
  10. @Override
  11. public Object Testinterceptor() throws InvocationTargetException, IllegalAccessException {
  12. //真正调用的代理
  13. return method.invoke( object,plugin);
  14. }
  15. }

3.定义插件


   
  1. public class MyPluginOne implements MyPlugin {
  2. @Override
  3. public Object interceptor(MyInvocation myInvocation) throws InvocationTargetException, IllegalAccessException {
  4. System.out.println( "--插件1");
  5. Object o= myInvocation.Testinterceptor();
  6. System.out.println( "插件1结束");
  7. return o;
  8. }
  9. }

4.封装插件


   
  1. public class MybatisPluginsProxy implements InvocationHandler {
  2. private Object object;
  3. private MyPlugin interceptors;
  4. MybatisPluginsProxy( Object o,MyPlugin o1){
  5. this.object=o;
  6. this.interceptors=o1;
  7. }
  8. @Override
  9. public Object invoke( Object proxy, Method method, Object[] args) throws Throwable {
  10. System.out.println( "-----执行前");
  11. MyInvocation myInvocation= new MyInvocationTest( object,method,args);
  12. return interceptors.interceptor(myInvocation);
  13. // Object object1=method.invoke(object,args);
  14. // System.out.println("-----执行后");
  15. // return object1;
  16. }
  17. public static Object wrap( Object o,MyPlugin target){
  18. return Proxy.newProxyInstance(o.getClass().getClassLoader(),
  19. o.getClass().getInterfaces(), new MybatisPluginsProxy(o,target));
  20. }
  21. }

5.代码执行


   
  1. public class MybatisPluginMain {
  2. public static void main(String[] args) {
  3. MybatisPluginTest test= new MybatisPluginTestImpl();
  4. MyPlugin test1= new MyPluginOne();
  5. MyPlugin test2= new MyPluginOne();
  6. List list= new ArrayList<>();
  7. list.add(test1);
  8. list.add(test2);
  9. MybatisPluginTest proxy= (MybatisPluginTest) MybatisPluginsProxy.wrap(test,test2);
  10. proxy.execute();
  11. }
  12. }

执行的结果:

通过上述改造,我们的插件就可以在真正代理类之外执行了。但是如果我们有多个插件,那么我们怎么做?我们观察到代理其实只是对最初的对象的代理。所以拦截器的方法和真正代理类的方法没有任何关系。据此拦截器也是一样的道理,如果我们对拦截器进行代理,如果有多个拦截器那么就对拦截器的代理再进行代理。这样我们的真正要执行的代理类就被影藏到最深处,这样就实现了我们的目标。这里我们要明确的是我们的真正代理类因为要被影藏的最深所以要最早定义,因为我们要对拦截器进行代理,所以我们的拦截器要新增代理的功能。

改造之后的代码如下:


   
  1. public interface MyPlugin {
  2. Object interceptor(MyInvocation myInvocation) throws InvocationTargetException, IllegalAccessException;
  3. //插件
  4. Object plugin( Object o);
  5. }

   
  1. public class MyPluginOne implements MyPlugin {
  2. @Override
  3. public Object interceptor(MyInvocation myInvocation) throws InvocationTargetException, IllegalAccessException {
  4. System.out.println( "--插件1");
  5. Object o= myInvocation.Testinterceptor();
  6. System.out.println( "插件1结束");
  7. return o;
  8. }
  9. @Override
  10. public Object plugin(Object o) {
  11. //将当前插件整合到上层代理中,并返回整合之后的代理类
  12. return MybatisPluginsProxy.wrap(o, this);
  13.     }
  14. }

   
  1. public  class  MybatisPluginMain {
  2. public static void main(String[] args) {
  3. MybatisPluginTest test= new MybatisPluginTestImpl();
  4. MyPlugin test1= new MyPluginOne();
  5. MyPlugin test2= new MyPluginTwo();
  6. test= (MybatisPluginTest) test1.plugin(test);
  7. test= (MybatisPluginTest) test2.plugin(test);
  8. test.execute();
  9. }
  10. }


代码改造到这里,我们基本已经完成了所需要的功能。但是我们发现在添加插件这件事情上,我们可以提取公因式。于是就有了另类的改造。


   
  1. public class InterceptorChain {
  2. private Listinterceptor= new ArrayList<>( 10);
  3. public Object pluginAll(Object o){
  4. for ( int i = 0; i<interceptor.size() ; i++) {
  5. o= interceptor. get(i).plugin(o);
  6. }
  7. return o;
  8. }
  9. public void addInterceptor(MyPlugin interceptor){
  10. this.interceptor. add(interceptor);
  11. }
  12. }

   
  1. public  class  MybatisPluginMain {
  2. public static void main(String[] args) {
  3. MybatisPluginTest test= new MybatisPluginTestImpl();
  4. MyPlugin test1= new MyPluginOne();
  5. MyPlugin test2= new MyPluginTwo();
  6. InterceptorChain interceptorChain= new InterceptorChain();
  7. interceptorChain.addInterceptor(test1);
  8. interceptorChain.addInterceptor(test2);
  9. test= (MybatisPluginTest) interceptorChain.pluginAll(test);
  10. test.execute();
  11. }
  12. }

 至此我们大概学习了mybatis插件的核心原理。那么我就去看看mybatis插件的代码。我们继续找到我们代码执行sql的地方

如图所示,在executor中创建了sql处理器。


   
  1. public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
  2. StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
  3. //使用责任链模式创建代理
  4. statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
  5. return statementHandler;
  6. }

同样的,mybatis的四大处理器都有安装插件的功能。也就是说在在启动的时候executor的所有插件都已经设置进去。当我们调用executor的方法的时候,其实就已经开始插件的执行。

需要注意的是这里的的拦截器的递归调用的最后就是我们最原始的代理类simpleExecutor或者cacheExecutor。

这里可能有人要问我们分页插件的拦截器看样子会在sql处理前执行,那么他这个sql是怎么做的。其实这样想就已经变成了刻舟求剑,我们的代码自从有了代理,他就仅仅由代理调用,而我们将插件和代理整合不断的嵌套。所以我们只需要保证我们最后执行的插件调用了真正代理类的相应方法即可。如图所示就是mybatis的查库操作。

而至于真正代理类所要执行的方法之后的其他代理那就一个新的代理插件嵌套的过程。所以说执行的流程还是相当的复杂,所以我们还是少写无用的插件的最好。不过这种插件的设计是非常值得我们学习的。

参考文献:

https://www.cnblogs.com/qdhxhz/p/11390778.html


转载:https://blog.csdn.net/tianjingle_blog/article/details/111602707
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场