Mybatis中的反射和动态代理

  • Post author:
  • Post category:其他


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技术原理与实战》一书整理编写,感谢作者的辛勤劳作。



版权声明:本文为xubaifu原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。