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


    原文地址:带你了解 MyBatis 插件设计演化过程
    博客地址:http://www.extlight.com

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

    一、原始代码

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

    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 工厂类。

    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");
    	}
    }
    

    执行结果:

    执行前:5344823093500
    执行:select * from t_user
    执行后:5344823399900
    

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

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

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

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

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

    三、拦截器

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

    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 类封装拦截器。

    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");
    	}
    }
    

    执行结果:

    事务提交前
    执行前:5467232073200
    执行:select * from t_user
    执行后:5467232129200
    事务提交后
    

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

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

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

  • 相关阅读:
    LocalDateTime方法plusHours()
    windows查看所有端口, 查看端口是否占用
    maven编译引起excel文件损坏问题解决
    Java BigDecimal 添加数据
    postgresql数据库设置联合主键的语句
    Linux为普通用户挂载磁盘
    Glog不打印错误方法
    关闭书莓派3B所有指示灯
    巧用Python自带http服务实现文件共享
    VIM高阶使用技巧
  • 原文地址:https://www.cnblogs.com/moonlightL/p/14982350.html
Copyright © 2020-2023  润新知