返回介绍

6.1 涉及的技术难点简介

发布于 2025-04-26 13:08:34 字数 6741 浏览 0 评论 0 收藏

来到原理章,我们有必要对一些常用的、基础的技术难点进行简介,否则读者可能难以理解本章的内容。

正如我们第 2 章描述的那样,Mapper 仅仅是一个接口,而不是一个包含逻辑的实现类。我们知道一个接口是没有办法去执行的,那么它是怎么运行的呢?这不是违反了教科书所说的接口不能运行的道理吗?相信不少的读者会对此产生极大的疑惑。

答案就是动态代理,我们不妨看看 Mapper 到底是一个什么东西,如图 6-1 所示。

138_0001

图 6-1 Mapper 动态代理

很显然 Mapper 产生了代理类,这个代理类是由 MyBatis 为我们创建的,为此我们不妨先来学习一下动态代理,这有利于后续的学习。

首先,什么是代理模式?所谓的代理模式就是在原有的服务上多加一个占位,通过这个占位去控制服务的访问。这句话不太容易理解,举例而言,假设你是一个公司的工程师,能提供一些技术服务,公司的客服是一个美女,她不懂技术。而我是一个客户,需要你们公司提供技术服务。显然,我只会找到你们公司的客服,和客服沟通,而不是找你沟通。客服会根据公司的规章制度和业务规则来决定找不找你服务。那么这个时候客服就等同于你的一个代理,她通过和我的交流来控制对你的访问,当然她也可以提供一些你们公司对外的服务。而我只能通过她的代理访问你。对我而言,根本不需要认识你,只需要认识客服就可以了。事实上,站在我的角度,我会认为客服就代表你们公司,而不管真正为我服务的你是怎么样的。

其次,为什么要使用代理模式?通过代理,一方面可以控制如何访问真正的服务对象,提供额外服务。另外一方面有机会通过重写一些类来满足特定的需要,正如客服也可以根据公司的业务规则,提供一些服务,这个时候就不需要劳你大驾了。下面给出动态代理示意图,如图 6-2 所示。

139_0001

图 6-2 动态代理示意图

一般而言,动态代理分为两种,一种是 JDK 反射机制提供的代理,另一种是 CGLIB 代理。在 JDK 提供的代理,我们必须要提供接口,而 CGLIB 则不需要提供接口,在 MyBatis 里面两种动态代理技术都已经使用了。但是在此之前我们需要学习的技术就是反射,让我们开始基础技术的学习。

6.1.1 反射技术

在 Java 中,反射技术已经大行其道,并且通过不断优化,Java 的可配置性等性能得到了巨大的提高。让我们来写一个服务打印 hello + 姓名 ,如代码清单 6-1 所示。

代码清单 6-1:ReflectService.java 反射示例

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectService {
    /**
     * 服务方法
     * @param name -- 姓名
     */
    public void sayHello(String name) {
        System.err.println("hello" + name);
    }

    /**
     * 测试入口
     * @param args
     */
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        //通过反射创建 ReflectService 对象
        Object service = Class.forName(ReflectService.class.getName()). newInstance();
        //获取服务方法 - sayHello
        Method method=service.getClass().getMethod("sayHello", String. class);
        //反射调用方法
        method.invoke(service, "zhangsan");
    }
}

这段代码通过反射技术去创建 ReflectService 对象,获取方法后通过反射调用。

反射调用的最大好处是配置性大大提高,就如同 Spring IOC 容器一样,我们可以给很多配置设置参数,使得 Java 应用程序能够顺利运行起来,大大提高了 Java 的灵活性和可配置性,降低模块之间的耦合。

6.1.2 JDK 动态代理

JDK 的动态代理,是由 JDK 的 java.lang.reflect.*包提供支持的,我们需要完成这么几个步骤。

  • 编写服务类和接口,这个是真正的服务提供者,在 JDK 代理中接口是必须的。

  • 编写代理类,提供绑定和代理方法。

JDK 的代理最大的缺点是需要提供接口,而 MyBatis 的 Mapper 就是一个接口,它采用的就是 JDK 的动态代理。我们先给一个服务接口,如代码清单 6-2 所示。

代码清单 6-2:HelloService.java

    public interface HelloService {
    public void sayHello(String name);
}

然后,写一个实现类,如代码清单 6-3 所示。

代码清单 6-3:HelloServiceImpl.java

public class HelloServiceImpl implements HelloService{
    @Override
    public void sayHello(String name) {
        System.err.println("hello " + name);
    }
}

现在我们写一个代理类,提供真实对象的绑定和代理方法。代理类的要求是实现 InvocationHandler 接口的代理方法,当一个对象被绑定后,执行其方法的时候就会进入到代理方法里,如代码清单 6-4 所示。

代码清单 6-4:HelloServiceProxy.java

public class HelloServiceProxy implements InvocationHandler {
/**
* 真实服务对象
*/
    private Object target;  
    /** 
     * 绑定委托对象并返回一个代理类 
     * @param target 
     * @return 
     */  
    public Object bind(Object target) {  
        this.target = target;  
        //取得代理对象  
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),  
                target.getClass().getInterfaces(), this);   //jdk 代理需要提供接口
    }  
  
    @Override  
    /**
     * 通过代理对象调用方法首先进入这个方法
     * @param proxy --代理对象
     * @param method -- 被调用方法
     * @param args -- 方法的参数
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        System.err.println("############我是 JDK 动态代理################");
        Object result = null;  
        //反射方法前调用
        System.err.println("我准备说 hello。");  
        //执行方法,相当于调用  HelloServiceImpl 类的 sayHello 方法
        result=method.invoke(target, args);
        //反射方法后调用
        System.err.println("我说过 hello 了");  
        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.sayHello("张三"),那么它就会进入到 HelloServiceProxy 的 invoke() 方法。而 invoke 参数中第一个便是代理对象 proxy,方法便是 sayHello,参数是张三。

我们已经用 HelloServiceProxy 类的属性 target 保存了真实的服务对象,那么我们可以通过反射技术调度真实对象的方法。

result=method.invoke(target, args);

这里我们演示了 JDK 动态代理的实现,并且在调用方法前后都可以加入我们想要的东西。MyBatis 在使用 Mapper 的时候也是这样做的。

让我们测试一下动态代理,如代码清单 6-5 所示。

代码清单 6-5:HelloServiceMain.java

public class HelloServiceMain {
    public static void main(String[] args) {
        HelloServiceProxy HelloHandler  = new HelloServiceProxy();
        HelloService proxy = (HelloService)HelloHandler.bind(new HelloServiceImpl());
        proxy.sayHello("张三");
    }
}

我们运行它,就可以看到如下运行结果。

############我是 JDK 动态代理################
我准备说 hello
hello 张三
我说过 hello 了

6.1.3 CGLIB 动态代理

JDK 提供的动态代理存在一个缺陷,就是你必须提供接口才可以使用,为了克服这个缺陷,我们可以使用开源框架 - CGLIB,它是一种流行的动态代理。

让我们看看如何使用 CGLIB 动态代理。HelloService.java 和 HelloServiceImpl.java 都不需要改变,但是我们要实现 CGLIB 的代理类。它的实现 MethodInterceptor 的代理方法如代码清单 6-6 所示。

代码清单 6-6:HelloServiceCgLib.java

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 method, Object[] args, MethodProxy proxy) throws Throwable {  
        System.err.println("##############我是 CGLIB 的动态代理###### ######");
        //反射方法前调用
        System.err.println("我准备说 hello");  
        Object returnObj = proxy.invokeSuper(obj, args);  
        //反射方法后调用
        System.err.println("我说过 hello 了"); 
        return returnObj;  
    }  
}

这样便能够实现 CGLIB 的动态代理。在 MyBatis 中通常在延迟加载的时候才会用到 CGLIB 的动态代理。有了这些基础,我们就可以更好地论述 MyBatis 的解析和运行过程了。

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。