1. 动态代理  95

在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

1.1 在内存当中动态生成类的技术常见的包括:95

● JDK动态代理技术:只能代理接口。

● CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)

● Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

2. JDK动态代理  96

我们还是使用静态代理中的例子:一个接口和一个实现类。

2.1 OrderService接口

package com.powernode.proxy.service;

/**
 * 订单业务接口   96
 **/
public interface OrderService { // 代理对象和目标对象的公共接口。

    String getName();

    /**
     * 生成订单
     */
    void generate();

    /**
     * 修改订单信息
     */
    void modify();

    /**
     * 查看订单详情
     */
    void detail();

}

2.2 接口实现类OrderServiceImpl

package com.powernode.proxy.service;

/**
 * 接口实现类  96
 **/
public class OrderServiceImpl implements OrderService{ // 目标对象

    @Override
    public String getName() {
        System.out.println("getName()方法执行了");
        return "张三";
    }

    @Override
    public void generate() { // 目标方法
        // 模拟生成订单的耗时
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成.");
    }

    @Override
    public void modify() { // 目标方法
        // 模拟修改订单的耗时
        try {
            Thread.sleep(456);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改.");
    }

    @Override
    public void detail() { // 目标方法
        // 模拟查询订单的耗时
        try {
            Thread.sleep(111);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("请看订单详情.");
    }
}

2.3 Client客户端程序  96

我们在静态代理的时候,除了以上一个接口和一个实现类之外,是不是要写一个代理类UserServiceProxy呀!在动态代理中UserServiceProxy代理类是可以动态生成的。这个类不需要写。我们直接写客户端程序即可

package com.powernode.mall;

import com.powernode.mall.service.OrderService;
import com.powernode.mall.service.impl.OrderServiceImpl;

import java.lang.reflect.Proxy;

//jdk动态代理 客户端程序  96
public class Client {
    public static void main(String[] args) {
        // 第一步:创建目标对象
        OrderService target = new OrderServiceImpl();
        // 第二步:创建代理对象
        OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);
        // 第三步:调用代理对象的代理方法
        orderServiceProxy.detail();
        orderServiceProxy.modify();
        orderServiceProxy.generate();
    }
}

以上第二步创建代理对象是需要大家理解的:

OrderService orderServiceProxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), 调用处理器对象);

这行代码做了两件事:

● 第一件事:在内存中生成了代理类的字节码

● 第二件事:创建代理对象

2.3.1  newProxyInstance 翻译为:新建代理对象  96

    也就是说,通过调用这个方法可以创建代理对象。

    本质上,这个Proxy.newProxyInstance()方法的执行,做了两件事:

        第一件事:在内存中动态的生成了一个代理类的字节码class。

        第二件事:new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。

2.3.2 关于newProxyInstance()方法的三个重要的参数,每一个什么含义,有什么用?   96

    第一个参数:ClassLoader loader

        类加载器。这个类加载器有什么用呢?     96

            在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类就需要类加载器。所以这里需要指定类加载器。

            并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。

    第二个参数:Class[] interfaces      96

        代理类和目标类要实现同一个接口或同一些接口。

        在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的。

    第三个参数:InvocationHandler     h    97

        InvocationHandler 被翻译为:调用处理器。是一个接口。

        在调用处理器接口中编写的就是:增强代码。

        因为具体要增强什么代码,JDK动态代理技术它是猜不到的。没有那么神。

        既然是接口,就要写接口的实现类。

        可能会有疑问?

            自己还要动手写调用处理器接口的实现类,这不会类爆炸吗?不会。

            因为这种调用处理器写一次就好。

注意:代理对象和目标对象实现的接口一样,所以可以向下转型。

2.4 InvocationHandler接口的实现类   97

所以接下来我们要写一下java.lang.reflect.InvocationHandler接口的实现类,并且实现接口中的方法,代码如下:

package com.powernode.proxy.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 专门负责计时的一个调用处理器对象。   97
 * 在这个调用处理器当中编写计时相关的增强代码。
 * 这个调用处理器只需要写一个就行了。
 **/
public class TimerInvocationHandler implements InvocationHandler {

    // 目标对象
    private Object target;

    public TimerInvocationHandler(Object target) {
        // 赋值给成员变量。
        this.target = target;
    }

    /*
        1. 为什么强行要求你必须实现InvocationHandler接口?   98
            因为一个类实现接口就必须实现接口中的方法。
            以下这个方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。
            注意:invoke方法不是我们程序员负责调用的,是JDK负责调用的。
        2. invoke方法什么时候被调用呢?
            当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。

        3. invoke方法的三个参数:   99
            invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数。
            我们可以在invoke方法的大括号中直接使用。
            第一个参数:Object proxy 代理对象的引用。这个参数使用较少。
            第二个参数:Method method 目标对象上的目标方法。(要执行的目标方法就是它。)
            第三个参数:Object[] args 目标方法上的实参。

            invoke方法执行过程中,使用method来调用目标对象的目标方法。
         */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 这个接口的目的就是为了让你有地方写增强代码。   99-100
        //System.out.println("增强1");
        long begin = System.currentTimeMillis();

        // 调用目标对象上的目标方法
        // 方法四要素:哪个对象,哪个方法,传什么参数,返回什么值。
        Object retValue = method.invoke(target, args);

        //System.out.println("增强2");
        long end = System.currentTimeMillis();
        System.out.println("耗时"+(end - begin)+"毫秒");

        // 注意这个invoke方法的返回值,如果代理对象调用代理方法之后,需要返回结果的话,invoke方法必须将目标对象的目标方法执行结果继续返回。
        return retValue;
    }
}

2.4.1  为什么强行要求你必须实现InvocationHandler接口?   98

            因为一个类实现接口就必须实现接口中的方法。

            以下这个方法必须是invoke(),因为JDK在底层调用invoke()方法的程序已经提前写好了。

            注意:invoke方法不是我们程序员负责调用的,是JDK负责调用的。

2.4.2 invoke方法什么时候被调用呢?98

            当代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法被调用。

 2.4.3 invoke方法的三个参数:   99

            invoke方法是JDK负责调用的,所以JDK调用这个方法的时候会自动给我们传过来这三个参数。

            我们可以在invoke方法的大括号中直接使用。

            第一个参数:Object proxy 代理对象的引用。这个参数使用较少。

            第二个参数:Method method 目标对象上的目标方法。(要执行的目标方法就是它。)

            第三个参数:Object[] args 目标方法上的实参。

            invoke方法执行过程中,使用method来调用目标对象的目标方法。

2.5 客户端测试   100

package com.powernode.proxy.client;

import com.powernode.proxy.service.OrderService;
import com.powernode.proxy.service.OrderServiceImpl;
import com.powernode.proxy.service.TimerInvocationHandler;

import java.lang.reflect.Proxy;

//jdk动态代理 客户端程序  96
public class Client {
    public static void main(String[] args) {
        //创建目标对象
        OrderService target = new OrderServiceImpl();
        //创建代理对象
        /*
        1. newProxyInstance 翻译为:新建代理对象  96
            也就是说,通过调用这个方法可以创建代理对象。
            本质上,这个Proxy.newProxyInstance()方法的执行,做了两件事:
                第一件事:在内存中动态的生成了一个代理类的字节码class。
                第二件事:new对象了。通过内存中生成的代理类这个代码,实例化了代理对象。
        2. 关于newProxyInstance()方法的三个重要的参数,每一个什么含义,有什么用?   96
            第一个参数:ClassLoader loader
                类加载器。这个类加载器有什么用呢?     96
                    在内存当中生成的字节码也是class文件,要执行也得先加载到内存当中。加载类就需要类加载器。所以这里需要指定类加载器。
                    并且JDK要求,目标类的类加载器必须和代理类的类加载器使用同一个。

            第二个参数:Class[] interfaces      96
                代理类和目标类要实现同一个接口或同一些接口。
                在内存中生成代理类的时候,这个代理类是需要你告诉它实现哪些接口的。

            第三个参数:InvocationHandler h    97
                InvocationHandler 被翻译为:调用处理器。是一个接口。
                在调用处理器接口中编写的就是:增强代码。
                因为具体要增强什么代码,JDK动态代理技术它是猜不到的。没有那么神。
                既然是接口,就要写接口的实现类。

                可能会有疑问?
                    自己还要动手写调用处理器接口的实现类,这不会类爆炸吗?不会。
                    因为这种调用处理器写一次就好。

             注意:代理对象和目标对象实现的接口一样,所以可以向下转型。
         */
        OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
                        target.getClass().getInterfaces(),
                        new TimerInvocationHandler(target));
        //调用代理对象的代理方法
        // 注意:调用代理对象的代理方法的时候,如果你要做增强的话,目标对象的目标方法得保证执行。
        proxyObj.generate();
        proxyObj.modify();
        proxyObj.detail();

        String name =  proxyObj.getName();
        System.out.println(name);

    }
}

动态代理_类加载器

学到这里可能会感觉有点懵,折腾半天,到最后这不是还得写一个接口的实现类吗?没省劲儿呀?

你要这样想就错了!!!!

我们可以看到,不管你有多少个Service接口,多少个业务类,这个TimerInvocationHandler接口是不是只需要写一次就行了,代码是不是得到复用了!!!!

而且最重要的是,以后程序员只需要关注核心业务的编写了,像这种统计时间的代码根本不需要关注。因为这种统计时间的代码只需要在调用处理器中编写一次即可。

到这里,JDK动态代理的原理就结束了。

不过我们看以下这个代码确实有点繁琐,对于客户端来说,用起来不方便:

动态代理_目标对象_02

2.6 我们可以提供一个工具类:ProxyUtil,封装一个方法:101

package com.powernode.proxy.util;

import com.powernode.proxy.service.OrderService;
import com.powernode.proxy.service.TimerInvocationHandler;

import java.lang.reflect.Proxy;

/**
 * 工具类   101
 **/
public class ProxyUtil {

    /**
     * 封装一个工具方法,可以通过这个方法获取代理对象。
     * @param target
     * @return
     */
    public static Object newProxyInstance(Object target){
        // 底层是调用的还是JDK的动态代理。
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new TimerInvocationHandler(target));
    }

}
/*OrderService proxyObj = (OrderService)Proxy.newProxyInstance(target.getClass().getClassLoader(),
                        target.getClass().getInterfaces(),
                        new TimerInvocationHandler(target));*/
        // 上面代码通过一个工具类的封装,就简洁了。
        OrderService proxyObj = (OrderService) ProxyUtil.newProxyInstance(target);

动态代理_类加载器_03