Mybatis的运行分为两个部分,第一部分是读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程。相对而言,SqlSessionFactory的创建比较容易理解,而SqlSession的执行过程远远不是那么简单了,它将包括许多复杂的技术,我们需要先讨论反射技术和动态代理技术,这是揭示mybatis底层架构的基础。
反射技术:
在java中,反射技术已经大行其道,并且通过不断优化,java的可配置性等性能得到了巨大的提升。
首先,我们来写一个打印“hello:world”的服务:代码如下:
public class Test {
public void sayHello (String str) {
System.err.println("hello:" + str);
}
}
通过反射调用该方法,代码如下:
import java.lang.reflect.Method;
public class InvokeTest {
public static void main(String[] args) throws Exception {
//通过反射创建test对象
Object test = Class.forName(Test.class.getName()).newInstance();
//获取服务方法:sayHello
Method method = test.getClass().getMethod("sayHello", String.class);
//反射调用方法
method.invoke(test, "world");
}
}
运行main行数,结果如下:
上述例子就是java反射机制的简单应用,反射调用的最大好处就是配置性大大提高,就如同Spring IOC容器一样,我们可以给很多配置参数,使得java应用程序能够顺利运行起来,大大提高java的灵活性和可配置性,降低模块之间的耦合。
JDK动态代理:
JDK的动态代理,是由JDK的java.lang.reflect.*包提供支持的,我们需要完成这么几个步骤:
1、 编写服务类和接口,这个是真正的服务提供者,在JDK代理中接口是必须的
2、 编写代理类,提供绑定和代理方法
JDK代理最大的缺点就是需要接口,而mybatis的Mapper就是一个接口,它采用的就是JDK的动态代理。我们先给一个服务接口,代码如下:
public interface HelloService {
void sayHello(String str);
}
然后写一个实现类,代码如下:
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String str) {
// TODO Auto-generated method stub
System.out.println("Hello" + str);
}
}
现在写一个代理类,提供真实的绑定和代理方法。代理类的要求是实现InvocationHandler接口的代理方法,当一个对象被绑定后,执行其方法的时候就会进入到代理方法里,代码如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class HelloServiceProxy implements InvocationHandler{
/**
* 真实的服务对象
*/
private Object target;
/**
* 绑定一个委托对象并返回一个代理类
* @param target
* @return
*/
public Object bind(Object target){
this.target = target;
Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
return proxy;
}
/**
* 通过代理对象调用方法首先进入这个方法
* @param procy 代理对象
* @param method 被调用方法
* @param agrs 方法参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] arg) throws Throwable {
// TODO Auto-generated method stub
System.out.println("我是JDK代理");
Object result = null;
//反射方法调用前
System.out.println("开始调用方法");
//执行方法,相当于调用HelloServiceImpl的sayHello方法
result = method.invoke(target, "world");
//反射方法调用后
System.out.println("调用方法完成");
return result;
}
}
Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
这段代码让JDK产生一个代理对象,这个代理对象有三个参数:第一个参数target.getClass().getClassLoader()是类加载器,第二个参数target.getClass().getInterfaces()是接口(代理对象挂载哪个接口下),第三个参数this代表当前HelloServiceProxy类,换句话说是使用HelloServiceProxy的代理方法作为对象的代理执行者。
一旦绑定后,在进入代理对象方法调用的时候就会到HelloServiceProxy的代理方法上,代理方法有三个参数:第一个proxy是代理对象,第二个是当前调用的那个方法,第三个是方法参数。比方说,现在HelloServiceImpl对象(obj)用bind方法绑定后,返回其占位,我们在调用proxy.sayHelo(“hello”),那么它就会进入到HelloServiceProxy的invoke()方法。而invoke参数中第一个便是代理对象proxy,方法是sayHello,参数是world。
让我们测试一下动态代理,代码如下:
public class ProxyTest {
public static void main(String[] args) {
HelloServiceProxy HelloHandler = new HelloServiceProxy();
HelloService procy = (HelloService) HelloHandler.bind(new HelloServiceImpl());
procy.sayHello("World");
}
}
运行结果如下:
CGLIB动态代理:
JDK提供的动态代理存在一个缺陷,就是必须提供接口才可以实现,为了克服这个缺陷,我们可以采用开源框架—CGLIB,它是一种流行的动态代理。
让我们看看如何使用CGLIB动态代理。HelloService.java和HelloServiceImpl.java不需要改变,我们只需要实现CGLIB的代理类。实现MethodInterceptor的代理方法入下:
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class HelloServiceCgLib implements MethodInterceptor{
private Object target;
/**
* 创建代理对象
* @param target
* @return
*/
public Object getInstance(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
//回调方法
enhancer.setCallback(this);
//创建代理对象
return enhancer.create();
}
@Override
//回调方法
public Object intercept(Object obj, Method methoc, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("我是cglib动态代理");
//反射方法调用前
System.out.println("开始调用");
Object returnObj = proxy.invokeSuper(obj, args);
//反射方法调用之后
System.out.println("电泳完成");
return returnObj;
}
}
我们的调用形式如下:
HelloServiceCgLib hello = new HelloServiceCgLib();
HelloServiceImpl proxyImpl = (HelloServiceImpl)hello.getInstance(HelloServiceImpl.class);
proxyImpl.sayHello("hello");
这样便能实现CGLIB的动态代理。在mybatis中通常延时加载的时候才会用到CGLIB的动态代理。
以上便是mybatis用到的代理模式,有了这些基础就可以更好的理解mybatis的运行了。
注:以上内容根据《深入浅出mybatis技术原理与实战》一书整理编写,感谢作者的辛勤劳作。