深入理解静态代理与 JDK 动态代理

一、概述

  静态代理和动态代理都是代理模式的实现方式之一,代理模式是一种常用的设计模式,在 Spring AOP 和 PRC 等诸多框架中均使用到了代理的思想,其目的是为其他对象提供一个代理来控制对某个对象的访问。代理类负责为委托类 (真实角色) 预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。基本的结构如下所示,主要包括三种角色:

  • 抽象角色:声明待实现的方法。抽象角色可以是接口或抽象类。
  • 委托类角色或真实角色:业务逻辑的具体执行者,实现抽象角色中所声明的方法。
  • 代理类角色:内部含有对委托类对象的引用,同时实现抽象角色所声明的方法,并在实现的方法中调用委托类对象中相应的方法。

在这里插入图片描述

注:上图为静态代理的结构,动态代理与其类似。

  此外,根据代理类的创建时机和创建方式的不同,代理可以分为静态代理和动态代理,两者的区别如下:

  • 静态代理:在程序运行前,已存在编译好的代理类。
  • 动态代理:在程序运行期间,根据需要动态创建代理类 (即 $Proxy0.class$Proxy1.class 以此类推) 及其实例来完成具体的功能。

  使用代理对象具有如下作用:

  • 代理对象的价值主要是用于拦截对真实业务对象的访问,有利于隐藏和保护委托类对象。
  • 为了保持行为的一致性,代理对象和真实业务对象实现共同的接口。
  • 代理对象是对真实业务对象的增强,以便对消息进行预处理和后处理,为实施不同的控制策略预留了空间。

二、静态代理

  上文讲到,静态代理在程序运行前就已经存在编译好的代理类。因此,静态代理的实现需要以下几个步骤:

1) 定义业务接口,即抽象角色。
2) 实现业务接口,即委托类角色。
3) 设计代理类,并实现业务接口。

  示例如下所示:

/**
 * 抽象角色
 */
public interface HelloService {
    void hello(String name);
    void getPhone(String name, String num);
}

/**
 * 委托类角色
 */
public class HelloServiceImpl implements HelloService {
    @Override
    public void hello(String name) {
        System.out.println("hello, " + name);
    }

    @Override
    public void getPhone(String name, String num) {
        System.out.println("hello, " + name + ", my phone number is " + num);
    }
}

/**
 * 代理角色
 */
public class HelloServiceProxy implements HelloService {
	
	// 真实角色的引用
    private HelloService helloService;

    public HelloServiceProxy(HelloService helloService) {
        this.helloService = helloService;
    }

    @Override
    public void hello(String name) {
        System.out.println("前置处理");
        helloService.hello(name);
        System.out.println("后置处理");
    }

    @Override
    public void getPhone(String name, String num) {
        System.out.println("前置处理");
        helloService.getPhone(name, num);
        System.out.println("后置处理");
    }
}

  静态代理使用结果如下:

public class Main {
    public static void main(String[] args) {
        HelloService helloService = new HelloServiceImpl();
        HelloServiceProxy staticProxy = new HelloServiceProxy(helloService);

        staticProxy.hello("Judy");
        System.out.println("==========================");
        staticProxy.getPhone("Cindy", "12346789");
    }
}

// 结果如下:
前置处理
hello,Judy
后置处理
==========================
前置处理
hello, Cindy, my phone number is 12346789
后置处理

  通过上面的示例,静态代理存在以下缺点:

  • 代理类依赖于委托类。因为代理类最根本的业务功能是调用委托类定义的业务逻辑,然后才进一步添加自己的处理,所以需要事先知道委托类。
  • 一个委托类必须对应一个代理类,即若存在成百上千个真实类,那就需要有成百上千个代理类,这样的结果会导致类的急剧膨胀,且维护起来相当不便。

三、动态代理

  动态代理是在运行期利用 JVM 的反射机制生成代理类,这里是直接生成类的字节码,然后通过类加载器载入 JVM 虚拟机执行。现在主流的动态代理技术的实现有两种:一种是 JDK 动态代理,另一种是 CGLIB 动态代理。本文主要深入理解 JDK 动态代理。

  首先,先了解下 JDK 动态代理的结构图,如下图所示。$ProxyN 即为 JDK 动态代理生成的代理类,其类名的规则是 “$Proxy” 加上一个自然序列数 (1, 2, 3, …),该代理类继承 Proxy 类,并实现一系列由抽象角色声明的接口。
在这里插入图片描述

3.1 相关的类和接口

  Proxy 类的静态方法和变量。

// 该方法用于获取指定代理对象所关联的调用处理器
static InvocationHandler getInvocationHandler(Object proxy)

// 该方法用于获取关联于指定类加载器和一组接口的动态代理类的类对象
static Class getProxyClass(ClassLoader loader, Class[] interfaces)

// 该方法用于判断指定类对象是否是一个动态代理类
static boolean isProxyClass(Class cl)

// 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)

// ============================================================

// 代理类构造函数的参数类型
private static final Class<?>[] constructorParams = {InvocationHandler.class};

// 代理类缓存
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> 
		proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
		

  InvocationHandler 接口:调用处理器接口,它自定义了一个 invoke() 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

public interface InvocationHandler {   
	// 该方法负责集中处理动态代理类上的所有方法调用
	// 调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行
	// 参数1:代理类实例
	// 参数2:被调用的方法对象
	// 参数3:调用参数
	public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
3.2 动态代理的实现流程

  动态代理大致的实现流程如下图所示:

在这里插入图片描述
  接下来从源码的层面具体分析其实现过程,首先进入的是 newProxyInstance 方法。

在这里插入图片描述
  从上图可以看到,首先是进行基本的校验过程,然后再调用 WeakCache#get() 来获得动态代理类。WeakCache 的构造函数和静态变量如下图所示:

在这里插入图片描述
  接下来看 proxyClassCache#get(ClassLoader loader, Class<?>... interfaces) 方法。

在这里插入图片描述
  首先说明 get() 方法的第一部分,该部分主要是计算通过类加载器计算一级键,然后通过接口数组计算二级键。因为通过一级键(类加载器)和二级键(接口数组)可以得到实现指定的一系列数组和由指定类加载器加载的动态代理类,所以,在初次情况下,一级键(类加载器)对应的 ConcurrentHashMap 不存在,则为其设置初始值。

  接下来是 get() 方法的第二部分,初次进入,实例化 factory 和 supplier。再一次进入 while 循环时,会调用 Supplier#get() 方法。此外,Factory 类和 CacheValue 类的继承关系图如下所示。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  接下来分析 ProxyClassFactory 类的源码,如下图所示。

在这里插入图片描述
  到这里为止,动态代理类的类对象创建完成,接下来是通过该类对象和反射获取动态代理类的构造函数,实例化动态代理对象。最后再回到 Proxy#newProxyInstance() 方法实例化动态代理类。

在这里插入图片描述

3.3 JDK 动态代理示例
// 抽象角色声明的接口
public interface HelloWorld {
    void sayHello(String name);
}

// 委托类角色
public class HelloWorldImpl implements HelloWorld {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello " + name);
    }
}

// 实现 InvocationHandler 接口,并实现其中的 invoke() 方法
public class CustomInvocationHandler implements InvocationHandler {
    private Object target;
 
    public CustomInvocationHandler(Object target) {
        this.target = target;
    }
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before invocation");
        
        // 调用委托类的实现的方法
        Object retVal = method.invoke(target, args);

        System.out.println("After invocation");

        return retVal;
    }
}

  动态代理使用结果如下:

// 测试类
public class ProxyTest {
    public static void main(String[] args) throws Exception {
    	// 设置 sun.misc.ProxyGenerator.saveGeneratedFiles 这个系统属性为 true 来把生成的 class 保存到本地文件来查看
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
 
        CustomInvocationHandler handler = new CustomInvocationHandler(new HelloWorldImpl());

        HelloWorld proxy = (HelloWorld) Proxy.newProxyInstance(
                ProxyTest.class.getClassLoader(),
                new Class[]{HelloWorld.class},
                handler);

        proxy.sayHello("Mikan");
    }
}

// 结果如下
Before invocation
Hello Mikan
After invocation

  最后,我们反编译下生成的 $Proxy0.class 动态代理类。

package com.sun.proxy;

import club.wadreamer.test.proxy.HelloWorld;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements HelloWorld {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void sayHello(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("club.wadreamer.test.proxy.HelloWorld").getMethod("sayHello", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

  从反编译的文件可知,生成的动态代理类具有如下特点:

  • 继承了 Proxy 类,实现了代理的接口,由于 Java 不能多继承,所以不能再继承其他的类。因此,JDK 动态代理不支持对实现类的代理,只支持接口的代理。
  • 提供了一个使用 InvocationHandler 作为参数的构造方法。
  • 生成静态代码块来初始化接口中方法的 Method 对象,以及 Object 类的 equals()hashCode()toString() 方法。
  • 重写了 Object 类的 equals()hashCode()toString() 方法,它们都只是简单的调用了 InvocationHandler#invoke() 方法,即可以对其进行特殊的操作,也就是说 JDK 动态代理还可以代理上述三个方法。

四、总结

4.1 代理机制
  • 通过实现 InvocationHandler 接口创建自己的调用处理器。
  • 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类。
  • 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型。
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
4.2 动态代理类的特点
  • 包:如果所代理的接口都是 public 的,那么它将被定义在顶层包(即包路径为空);如果所代理的接口中有非 public 的接口(因为接口不能被定义为 protectedprivate,所以除 public 之外就是默认的 package 访问级别),那么它将被定义在该接口所在包,否则包名是默认的 ReflectUtil.PROXY_PACKAGE(即 com.sun.proxy)。这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问;
  • 类修饰符:该代理类具有 finalpublic 修饰符,意味着它可以被所有的类访问,但是不能被再度继承。
  • 类名:格式是”$ProxyN”,其中 N 是一个逐一递增的阿拉伯数字,代表 Proxy 类第 N 次生成的动态代理类。此外,并不是每次调用 Proxy 的静态方法创建动态代理类都会使得 N 值增加,原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类,那么它会返回先前已经创建好的代理类的类对象,而不会再尝试去创建一个全新的代理类。
4.3 被代理接口的特点
  • 被代理的接口不能重复,以避免动态代理类代码生成时的编译错误。
  • 被代理的接口对于类加载器必须可见,否则会导致类加载器无法链接它们,从而造成代理类生成失败。
  • 被代理的所有非 public 接口必须位于同一个包中,否则也会造成代理类生成失败。
  • 被代理的接口数量不能超过 65535 个,这是 JVM 的限制。

五、参考资料

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值