总的来说是通过动态代理。动态代理的功能就是通过拦截器方法回调(invokeHandler),达到增强目标对象的目的。看下面代码,很关键一点就是InvocationHandler包含target对象。在invoke方法中会调用target的方法。
public class HelloWordProxy extends InvokeHandler{
// 真正的本体
private Object target;
public Object bind(Object target) {
this.target= target;
return Proxy.newInstance(taget.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Overrride
public Object invoke(Object proxy, Method method, Object[] args) {
System.out.println("before execute real target method");
result = method.invoke(target, args);
System.out.println("after execute real target method");
return result;
}
}
在Mybatis里面,会定义一个MapperProxy(实现InvocationHandler),先看MapperProxyFactory:
public class MapperProxyFactory<T> {
...
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T)Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInteface, methodCache);
return newInstance(mapperProxy);
}
}
重点关注上面的public方法,可以看出MapperProxy是用sqlSession创建的,并且Proxy.newInstance()方法的第三个参数就是这个MapperProxy对象本身(这个符合动态代理的基本创建方法)。
再看MapperProxy的执行代码:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MapperProxy implements InvocationHandler {
@SuppressWarnings("unchecked")
public <T> T newInstance(Class<T> clz) {
return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[] { clz }, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
// 诸如hashCode()、toString()、equals()等方法,将target指向当前对象this
return method.invoke(this, args);
} catch (Throwable t) {
}
}
MapperMethod mapperMethod = cachedMapperMethod(method);
return mepperMethod.execute(sqlSession, args);
}
}
method.invoke(this, args) 第一个参数往往是target, 而这里的target就是MapperProxy自己。所里这里起到的并不是增强target的功能,而是“取代”target的功能。而事实上,在Mybatis里面,从来就没出现target,target只是个占位符。
还要注意:最后mapperMethod.execute(sqlSesison, args)也很有意思,他是后序执行sql语句的入口,它会调用sqlSession的各种数据库操作方法,而这些方法就会去调用sqlSession的四大组件。这个会在后序的sql执行过程中详细介绍。