51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

带你了解 MyBatis 插件设计演化过程

之前写过一篇 《Mybatis 插件实现动态设置参数》 文章,介绍了 Mybatis 插件的扩展和使用。笔者在空闲时间梳理了一下 MyBatis 插件的工作原理,在此记录和分享其插件功能代码的演化过程。

一、原始代码 {#一、原始代码}

我们简略 MyBatis 执行 SQL 的步骤,下边的原始代码是依靠 Executor 执行 SQL 语句。

|------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | interface Executor { void execute(String sql); } class DefaultExecutor implements Executor { @Override public void execute(String sql) { System.out.println("执行:" + sql); } } public class Demo { public static void main(String[] args) { Executor executor = new DefaultExecutor(); executor.execute("select * from t_user"); } } |

假设,我们需要 Executor 在执行 SQL 语句的前后打印出当前时间戳(方法增强),那该如何操作?

针对方法增强的情况,有 3 个方案:

  1. 修改源码: 修改 execute 方法,在执行 SQL 前后加入日志打印方法。违背开源-封闭原则且维护繁琐(如扩展的是第三方jar,需修改源码再打包)。且作为通用组件开发也不合适此方案。

  2. 使用继承: 继承父类,重写方法,属于纵向方法增强。只对一个类产生作用,如要对一批类进行方法增强,需要创建多个子类,扩展性不好。

  3. 动态代理: 动态生成代理对象,代替目标对象执行操作,无需修改源码,易扩展和维护。

二、动态代理 {#二、动态代理}

接下来我们使用动态代理方案,创建 TargetProxyHandler 实现类和 TargetProxyFactory 工厂类。

|---------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class TargetProxyHandler implements InvocationHandler { private Object target; public TargetProxyHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("执行前:" + System.nanoTime()); Object result = method.invoke(target, args); System.out.println("执行后:" + System.nanoTime()); return result; } } class TargetProxyFactory { public static Object newProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxyHandler(target)); } } public class Demo { public static void main(String[] args) { Executor target = new DefaultExecutor(); Executor executor = (Executor) TargetProxyFactory.newProxy(target); executor.execute("select * from t_user"); } } |

执行结果:

|---------------|---------------------------------------------------------------------| | 1 2 3 | 执行前:5344823093500 执行:select * from t_user 执行后:5344823399900 |

TargetProxyHandler 实现类用于执行被代理对象的目标方法( execute ),TargetProxyFactory 负责创建代理对象。

这样,我们使用动态代理实现了日志打印的需求。但又产生新的问题:

现在执行被代理对象的 execute 方法前后都有日志打印,将来我们还想对其进行方法增强(如去掉日志或添加事务)。还是得修改 TargetProxyHandler 源码,但其作为代理对象的执行方法的通用组件,不应该参杂业务代码,那我们应该处理呢?

这个问题的根源在于 invoke 方法上。我们需要把其执行内容抽离出来封装到单独的组件中,组件提供方法调用即可。

这种解决方案就是我们熟知的拦截器。

三、拦截器 {#三、拦截器}

我们需要创建 Interceptor 接口与 LogInterceptor 实现类,将 TargetProxyHandlerinvoke 方法替换成拦截器的调用方法。

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | class Invocation { private Object target; private Method method; private Object[] args; public Invocation(Object target, Method method, Object[] args) { this.target = target; this.method = method; this.args = args; } public Object process() throws Exception { return method.invoke(target, args); } } interface Interceptor { Object intercept(Invocation invocation) throws Exception; } class LogInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Exception { System.out.println("执行前:" + System.nanoTime()); Object result = invocation.process(); System.out.println("执行后:" + System.nanoTime()); return result; } } class TargetProxyHandler implements InvocationHandler { private Object target; private Interceptor interceptor; public TargetProxyHandler(Object target, Interceptor interceptor) { this.target = target; this.interceptor = interceptor; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Invocation invocation = new Invocation(target, method, args); return interceptor.intercept(invocation); } } class TargetProxyFactory { public static Object newProxy(Object target, Interceptor interceptor) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxyHandler(target, interceptor)); } } public class Demo { public static void main(String[] args) { Executor target = new DefaultExecutor(); Interceptor logIntercetor = new LogInterceptor(); Executor executor = (Executor) TargetProxyFactory.newProxy(target, logIntercetor); executor.execute("select * from t_user"); } } |

执行结果同上。

上述代码中,我们将方法增强的代码从 TargetProxyHandler.invoke 剥离抽取到 LogInterceptor.intercept 中。TargetProxyHandler 得到释放,LogInterceptor 作为业务代码可由业务决定其实现逻辑。

注意,我们还有一个问题没解决,正如上述描述的,如果我们还要新增一个事务开启,提交的功能,代码如何实现呢?

在上边的代码中,我们定义了 Interceptor 接口,LogInterceptor 实现该接口用于处理日志方法增强的业务。依瓢画葫芦,我们可以创建 TransactionInterceptor 类实现 Interceptor 接口用于处理事务。

问题出现了,现在有两个拦截器,而 TargetProxyFactory 工厂类只能接受一个拦截器对象,我们如何同时使用这两个拦截器呢?

当然是使用拦截器链!

四、拦截器链 {#四、拦截器链}

创建 InterceptorChain 类封装拦截器。

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | interface Interceptor { Object intercept(Invocation invocation) throws Exception; Object plugin(Object target); } class LogInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Exception { System.out.println("执行前:" + System.nanoTime()); Object result = invocation.process(); System.out.println("执行后:" + System.nanoTime()); return result; } @Override public Object plugin(Object target) { return TargetProxyFactory.newProxy(target, this); } } class TransactionInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Exception { System.out.println("事务提交前"); Object result = invocation.process(); System.out.println("事务提交后"); return result; } @Override public Object plugin(Object target) { return TargetProxyFactory.newProxy(target, this); } } class InterceptorChain { private List<Interceptor> interceptors = new ArrayList<>(); public void addInterceptor(Interceptor interceptor) { this.interceptors.add(interceptor); } public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } } class TargetProxyHandler implements InvocationHandler { private Object target; private Interceptor interceptor; public TargetProxyHandler(Object target, Interceptor interceptor) { this.target = target; this.interceptor = interceptor; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Invocation invocation = new Invocation(target, method, args); return interceptor.intercept(invocation); } } class TargetProxyFactory { public static Object newProxy(Object target, Interceptor interceptor) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxyHandler(target, interceptor)); } } public class Demo { public static void main(String[] args) { Executor target = new DefaultExecutor(); Interceptor logIntercetor = new LogInterceptor(); Interceptor transactionIntercetor = new TransactionInterceptor(); InterceptorChain interceptorChain = new InterceptorChain(); interceptorChain.addInterceptor(logIntercetor); interceptorChain.addInterceptor(transactionIntercetor); Executor executor = (Executor) interceptorChain.pluginAll(target); executor.execute("select * from t_user"); } } |

执行结果:

|-------------------|---------------------------------------------------------------------------------| | 1 2 3 4 5 | 事务提交前 执行前:5467232073200 执行:select * from t_user 执行后:5467232129200 事务提交后 |

除了新增了 InterceptorChain ,我们还修改 Interceptor 接口,为其定义了 plugin 方法,由拦截器自己维护创建代理对象。

InterceptorChain 中定义 pluginAll 方法,用于遍历创建代理对象(第一次遍历,被代理对象是 target,创建出代理对象为A;第二次遍历,被代理对象是A,创建出代理对象是B)。

至此,MyBatis 插件设计演化过程结束。当然,笔者是指简单的梳理演变过程,MyBatis 插件实际的执行代码要复杂很多,但思想和原理是大致相同的。

赞(0)
未经允许不得转载:工具盒子 » 带你了解 MyBatis 插件设计演化过程