【系列目录】
Mybatis源码阅读之一——工厂模式与SqlSessionFactory
Mybatis源码阅读之二——模板方法模式与Executor
Mybatis源码阅读之三——JDBC解析与Mybatis封装
Mybatis源码阅读之四——装饰器模式与Mybatis中的各种Cache
Mybatis源码阅读之六——数据库连接池实现与hikariCP简析
Mybatis源码阅读之八——代理模式与Mybatis插件(pagehelper为例)
【本文目录】
代理模式
代理类是一个“代替”另一个原始类,暴露给外部调用,从而避免外部直接截出原始类。
下面我们看一下代理模式的UML设计。
使用继承,不推荐:
使用组合:
代理模式的目的是隐藏原始类,暴露代理类,如果使用上述继承方式,显然外部调用仍然可以通过代理类.super来找到真正的原始类,而组合就可以很好的避免这种情况
。
由此可以总结代理模式的优点:
- 代理可以隐藏复杂的任务,例如进行网络通信、事务管理而不改变实现。
- 代理可用于在不更改实现对象的情况下插入自定义行为/代码。
- 代理模式的其他优点之一是安全性。
了解了代理模式的结构,那么什么是静态代理?什么是动态代理?
静态代理
静态动态的区别:手动实现就是静态代理,JVM/其他机制根据我们提供的一些信息自动生成就是动态代理。
静态代理的案例
在hikariCP的实现中,对数据库驱动提供的Connection进行代理,设计了一个静态代理对象。
如下图所示,ProxyConnection实现了JDBC的Connection接口,同时持有了一个原始Connection对象。此处代理的主要目的是提供一些对池化的支持。
动态代理
动态代理的实现方式有JDK动态地理,CGLib,Javassist等。
代理模式+工厂是一种很好的设计,代理类负责增强原始类,工厂则是负责提取/隐藏代理类自身的生成逻辑,如此我们就可以很方便在工厂中的替换/增加一些其他动态代理的方式。
JDK动态代理
- JDK动态代理的核心是InvocationHandler接口,如下我们实现了一个日志打印的增强Handler。
public class MethodInvocationCountHandler implements InvocationHandler {
private final Object implementation;
public MethodInvocationCountHandler(final Object implementation) {
this.implementation = implementation;
}
public Object invoke(Object proxy, Method meth, Object[] args)
throws Throwable {
try {
System.out.println("执行前日志");
Object result = meth.invoke(implementation, args);
System.out.println("执行后日志");
return result;
} catch (final InvocationTargetException ex) {
throw ex.getTargetException( );
}
}
}
- 有了InvocationHandler,下一步就是给出具体的代理类生成逻辑,见下方工厂类,我们对Customer对象进行代理,使用Proxy.newProxyInstance将Customer对象以及InvocationHandler绑定在一起,得到最终的代理对象。
public class CustomerClassFactory {
public static final Customer getDynamicSomeClassProxy( ) {
Customer customer = new Customer("张三","李四");
InvocationHandler handler = new MethodInvocationCountHandler(customer);
Class[] interfaces = new Class[] { Customer.class };
ClassLoader loader = CustomerClassFactory.class.getClassLoader( );
Customer proxy = (Customer)Proxy.newProxyInstance(loader, interfaces, handler);
return proxy;
}
}
public class DemoDynamicProxy {
public static final void main(final String[] args) {
Customer proxy = CustomerClassFactory.getDynamicSomeClassProxy( );
proxy.printFullName( );
InvocationHandler handler = Proxy.getInvocationHandler(proxy);
if (handler instanceof MethodCountingHandler) {
System.out.println(((MethodCountingHandler)handler).getInvocationCount());
}
}
}
注意事项:
JDK代理方式只能代理接口,这是因为最终生成的代理类会继承Proxy接口同时实现被代理接口,如果是被代理者是一个类,那么代理类就需要同时继承Proxy与被代理类,显然,JAVA中是做不到多继承的。
对于JDK的这种代理类会继承Proxy的设计,我觉得没什么毛病,在万物皆对象的原则里面,代理一词有必要拥有自己的具现化类。
其他代理
JAVA中除了JDK代理,最多被使用的就是Cglib代理方式,相比之下,Javassist更专注于字节码编辑/精简等技术,我们在之前Mybatis源码阅读之六——数据库连接池实现与hikariCP简析有提到,HikariCP快的原因之一就是使用了JavaAssist字节码精简。
Cglib代理demo
- 类似的,先指定一个方法处理器
public class LogInterceptor implements MethodInterceptor {
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.err.println("打印" + method.getName() + "方法的入参");
Object obj2 = proxy.invokeSuper(obj, args);
System.err.println("打印" + method.getName() + "方法的出参");
return obj2;
}
}
- 然后通过类与方法处理器生成代理对象。
@Test
public void testCGLib() throws InterruptedException {
// 设置输出代理类到指定路径,便于后面分析
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:/growUp/test");
// 创建Enhancer对象
Enhancer enhancer = new Enhancer();
// 设置哪个类需要代理
enhancer.setSuperclass(Customer.class);
// 设置怎么代理
enhancer.setCallback(new LogInterceptor());
// 获取代理类实例
Customer customer = (Customer) enhancer.create();
}
扩展问题
为什么在Spring AOP中,无论JDK代理还是Cglib代理,被代理类本身使用this,会调用不到代理类?
答:只是在spring AOP中,因为spring持有了原始类的实例和和代理类实例两者,而调用到实际方法的时候已经是原object了。
Mybatis中的应用——Plugin
Mybatis中Mapper的生成也使用了动态代理,但是前篇已经基本把这部分代码扒拉了个大概,不再赘述。
Mybatis提供的插件开发,实际上就是给我们外部开发者提供了一些拦截点,能够让我们实现自定义的拦截器。
支持拦截的方法
- 执行器Executor(update、query、commit、rollback等方法);
- 参数处理器ParameterHandler(getParameterObject、setParameters方法);
- 结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法);
- SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法);
案例-Pagehelper
如下图,Pagehelper(一个三方的Mybatis分页器)拦截的是Executor中的两个query方法,然后再拦截方法中对sql进行分页参数的拼接。
Mybatis插件源码
Mybatis的plugin包下,主要逻辑类有三个:Interceptor接口,InterceptorChain以及Plugin。
-
Interceptor以及@Intercepts是开放给开发者的,(如pagehelper)开发者需要实现一个Interceptor,并使用@Intercepts标注要拦截的方法。
-
InterceptorChain : 包含了所有开发者开发的interceptors,pluginAll即为对一个个的拦截点进行插件化(生成代理对象)。
从pluginAll向上反查,就是我们之前提的四个拦截点 -
Plugin : Plugin实际上就是Interceptor接口的代理类Handler。
通过wrap方法将interceptor转化成PluginHandler的代理类,实际的invoke方法则是针对不同的方法执行不同的拦截器。
欢迎关注微信公众号 【JAVA技术分享官】,公众号首发,持续输出原创高质量JAVA开发者知识点