mybatis接口式编程原理
众所周知,mybatis实现了接口式编程,简化了我们Dao层的编写,通过定义一个接口和XMl文件即可实现对数据库sql的执行。那它是怎么现实的呢,其实是利用了java的动态代理。
我们先聊聊动态代理模式:
代理模式:对原有功能,进行增强
一、静态代理:构成--->1.抽象接口2.目标对象3.代理对象
重新定义一个类继承这接口,并定义此接口的引用,用来引用目标对象,调用原方法
public abstract class AbstractObject { public abstract void operation(); }
目标对象角色
public class RealObject extends AbstractObject { @Override public void operation() { //一些操作 System.out.println("一些操作"); } }
代理对象角色
public class ProxyObject extends AbstractObject{ RealObject realObject = new RealObject(); @Override public void operation() { //调用目标对象之前可以做相关操作 System.out.println("before"); realObject.operation(); //调用目标对象之后可以做相关操作 System.out.println("after"); } }
客户端
public class Client { public static void main(String[] args) { AbstractObject obj = new ProxyObject(); obj.operation(); } }
二、动态代理:每个类都存在虚拟的class代理类,只存在于内存中
public class ProxyDemo { public static void main(String[] args) { //目标对象 final Dao demoDao = new DemoDao(); //接口Dao的代理类的代理对象 Dao dao = (Dao)Proxy.newProxyInstance(ProxyDemo.class.getClassLoader(), new Class[]{Dao.class}, //指定代理接口 new InvocationHandler() {//拦截代理对象的所有方法。 @Override //proxy代表代理对象本身 method 调用哪个方法,引用哪个方法对象 //args调用方法的参数封装成对象装进数组 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //System.out.println(method.getName()+" "+Arrays.toString(args)); //1.增强的功能 System.out.println("-----有方法进入,拦截到的方法为-----"); //2.调用目标对象原有的方法 //method.invoke(demoDao, args); return null; } }); //System.out.println(dao); tostring方法被拦截,一直返回null //System.out.println(dao.getClass()); //dao.add(10, 20); dao.equals("abc"); } }
下面说明下mybatis是怎么利用动态代理来现实接口式编程的,首先在mybatis中只定义了接口,如果需要调用接口中的方法必须要有现实类对象,利用动态代理来创建实现类对象:
MapperProxyimplementsInvocationHandler覆写类里invoke()方法-->Proxy.newProxyInstance(类加载器,接口.class数组,MapperProxy对象)-->sqlSession.getMapper(接口.class)==Proxy.newProxyInstance()-->IMessageimessage=Proxy.newProxyInstance()
imessage.queryMessageList==MapperProxy.invoke();
根据上面的流程,Proxy.newProxyInstance()创建了代理对象,可以把它看做接口的实现类对象,MapperProxy拦截了代理对象的所有方法,就是说代理对象执行什么方法都会进入到invoke()方法中,这就有个实现类对象
下面说说代理对象执行接口中的方法,为什么可以执行xml中配置的sql,这个就涉及接口式编程的规则了,也是为什么要设置这些规则的原因。
加载mybatis配置信息configuratioon,也就加载了Mapper的xml信息-->因为接口全名称.方法名=namespace.id-->所以MapperProxy.invoke()中有这样的代码:sqlSession.selectList(namespace.id,parameter)根据返回值类型决定用selectOne还是selectList
这样当我们用代理对象调用接口中方法时,就会在Mapper.invoke()方法中,执行sqlSession.selectList方法,也就是imessage.queryMessageList(parameter)=sqlSession.selectList(namespace.id,parameter),也就执行我们xml中的sql,这也是为什么我们namespace必须要是接口的完全限定名,而sql的id必须和接口中方法名一致的原因。