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

举报
苏州程序大白 发表于 2022/05/30 09:10:44 2022/05/30
【摘要】 带你了解 MyBatis 插件设计演化过程之前写过一篇 《Mybatis 插件实现动态设置参数》 文章,介绍了 Mybatis 插件的扩展和使用。笔者在空闲时间梳理了一下 MyBatis 插件的工作原理,在此记录和分享其插件功能代码的演化过程。 一、原始代码我们简略 MyBatis 执行 SQL 的步骤,下边的原始代码是依靠 Executor 执行 SQL 语句。interface Exe...

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

之前写过一篇 《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 插件实际的执行代码要复杂很多,但思想和原理是大致相同的。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。