前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面试不再怕-说透动静态代理!

面试不再怕-说透动静态代理!

作者头像
洋仔聊编程
修改2020-05-26 20:11:50
4910
修改2020-05-26 20:11:50
举报

追溯

学一个技术,要知道技术因何而产生,才能有学下去的目标和动力,才能更好的理解

首先,要明确为什么要存在代理呢?

存在一个常见的需求:怎样在不修改类A代码的情况下,在调用类A的方法时进行一些功能的附加与增强呢?

先不考虑什么代理不代理的,我们设计一个简单的实现方案:

新创建一个类B,类B组合类A,在类B中创建一个方法b,方法b中调用类A中的方法a,在调用前和调用后都可以添加一些自定义的附加与增强代码。 当有需求需要调用类A的方法a并且想要添加一个附加功能时,就去调用类B的方法b即可实现上述需求;

下面为了便于理解,附上伪代码:

代码语言:javascript
复制
// 定义类A
public class  ClassA{
    public void methoda(){
       System.out.println("我是方法a!");
    }
}

// 定义类B
public class ClassB{
    // 组合ClassA
    ClassA  A;
    public ClassB(ClassA A){
        this.A = A;
    }
    
    @Override
    public void methodb(){
        System.out.println("我是方法a的附加功能代码,我执行啦~!");
        A.methoda();
        System.out.println("我是方法a的附加功能代码,我完成啦~!");
    }
}

下面,让我们来调用一下ClassB的methodb方法,则会产生以下输出:

代码语言:javascript
复制
我是方法a的附加功能代码,我执行啦~!
我是方法a!
我是方法a的附加功能代码,我完成啦~!

可以发现,方法a执行了,并且在没有修改类A代码的前提下,为方法a附加了其他的功能; 不难吧,其实上述的代码就是一个最简单的代理模式

代理存在的意义:使用代理模式可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强

代理种类

代理分为静态代理动态代理,其涉及的设计模式就是代理模式本尊了,代理模式一般包含几种元素,如下图:

在这里插入图片描述
在这里插入图片描述
  1. 主题接口(subject):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;
  2. 真实主题(RealSubject):真正实现业务逻辑的类;
  3. 代理类(Proxy):用来代理和封装真实主题;
  4. 客户端(Client):使用代理类和主题接口完成一些工作。

为了更好的理解,我们将上述实现的最简易版的代理完善一下,添加接口,代理类也实现相应的被代理类的接口,实现同一个方法,伪代码如下:

代码语言:javascript
复制
// 被代理类的接口(上图中subject)
public interface ImpA{
      void methoda();
}
// 定义类A(上图中RealSubject)
public class  ClassA implements ImpA{
    public void methoda(){
       System.out.println("我是方法a!");
    }
}

// 定义类B(上图中Proxy)
public class ClassB implements  ImpA {
    // 组合ClassA
    ImpA  A;
    public ClassB(ClassA A){
        this.A = A;
    }
    
    // 重写被代理类的方法
    @Override
    public void methoda(){
        System.out.println("我是方法a的附加功能代码,我执行啦~!");
        A.methoda();
        System.out.println("我是方法a的附加功能代码,我完成啦~!");
    }
}
// 客户端类(上图中Client)
public class Main{
    // 创建被代理对象实例
    ImpA A = new ClassA();
    // 构造器注入被代理对象实例
    ImpA B = new ClassB(A);
    // 调用代理方法
    B.methoda();
}

静态代理

所谓静态代理也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。 上面的代码就是实现了一个静态代理; 其实静态代理就已经能够满足上述需求了,为什么还需要动态代理呢? 这里就涉及到静态代理的两个缺点了

  1. 代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,在程序规模稍大时静态代理代理类就会过多会造成代码混乱
  2. 如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法,增加了代码维护的复杂度。 基于上述两个问题,动态代理诞生了~

动态代理

动态代理是在程序运行时,通过反射获取被代理类的字节码内容用来创建代理类

具体什么是动态代理呢? 名词:动态,动态在程序中就是表达在程序运行时就根据配置自动的生成代理类并且代理类和被代理类是在运行时才确定相互之间的关系;

在JDK中包含两种动态代理的实现机制:JDK ProxyCGLib

下面我们以JDK Proxy为例,讲解一下动态代理和根据源码分析并简单说一下应用场景

JDK Proxy

JDK Proxy动态代理,api在包java.lang.reflect下,大家可能发现了,为什么在反射的包下呢?这个问题我们下面的源码分析会解决;

其核心api包含两个重要的核心接口和类:一个是 InvocationHandler(Interface)、另一个则是 Proxy(Class),简单说就这两个简单的很,这两个是我们实现动态代理所必需的用到的,下面简单介绍一下两个类: java.lang.reflect.Proxy(Class) :Proxy是 Java 动态代理机制的主类,提供一组静态方法来为一组接口动态地生成代理类及其对象。包含以下四个静态方法:

  • 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) 该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例,包含下面的参数:
    • loader 指定代理类的ClassLoader加载器
    • interfaces 指定代理类要实现的所有接口
    • h: 表示的是当这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上

该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例

java.lang.reflect.InvocationHandler(interface)InvocationHandler是上述newProxyInstance方法的InvocationHandler h参数传入,负责连接代理类和委托类的中间类必须实现的接口 它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。

上述就是动态代理两个核心的方法,不太明白?先别急,我们先用上述实现一个动态代理,你先看一下

还是以上述的案例从静态代理来改造为动态代理,实现动态代理主要就两步,假设还是存在上述的ImplA、ClassA

1:创建一个处理器类实现InvocationHandler接口,重写invoke方法,伪代码如下:

代码语言:javascript
复制
public class MyHandler implements InvocationHandler{
    // 标识被代理类的实例对象
    private Object delegate;   
    // 构造器注入被代理对象
    public MyHandler(Object delegate){
       this.delegate = delegate;
    }
    
    // 重写invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("被代理方法调用前的附加代码执行~ ");
        // 真实的被代理方法调用
        method.invoke(delegate, args);
        System.out.println("被代理方法调用后的附加代码执行~ ");
    } 
}

好了,这样一个处理器就搞定了,当我们在调用被代理类的方法时,就是去执行上述重写的invoke方法,下面创建一个ClassA的代理类

2:创建代理类,并调用被代理方法

代码语言:javascript
复制
public class MainTest{
    public static void main(String[] args) {    
        // 创建被代理对象
        ImplA A = new ClassA();
        // 创建处理器类实现
        InvocationHandler myHandler = new MyHandler(A);
        // 重点! 生成代理类, 其中proxyA就是A的代理类了
        ImplA proxyA = (ImplA)Proxy.newProxyInstance(A.getClass().getClassLoader(), A.getClass().getInterfaces(), myHandler);
        // 调用代理类的代理的methoda方法, 在此处就会去调用上述myHandler的invoke方法区执行,至于为什么,先留着疑问,下面会说清楚~
        proxyA.methoda();
    }
}

好了,至此一个动态代理就构建完成了,执行代码,会发现输出:

代码语言:javascript
复制
被代理方法调用前的附加代码执行~
我是方法a!
被代理方法调用后的附加代码执行~

太简单了有木有,这里总结一下动态代理的优缺点:

优点:

  1. 动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。
  2. 动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。
  3. 接口增加一个方法,除了所有实现类需要实现这个方法外,动态代理类会直接自动生成对应的代理方法。

缺点: JDK proxy只能对有实现接口的类才能代理,也就是说没有接口实现的类,jdk proxy是无法代理的,为什么呢?下面会解答.

有什么解决方案吗? 当然有,还有一种动态代理的方案:CGLib,它是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的,和jdk proxy基本思想是相似的,毕竟都是动态代理的实现方案嘛,在这篇文章就不做详解了,博主会在其他的博文单独介绍这个nb的框架

上述带大家搞了一遍动态代理和静态代理的应用;在这过程中,你有没有想过,动态代理是怎么实现的呢?

下面我们就从源码的角度分析一下,解决大家的疑问。

源码分析

在开始分析的时候,我希望大家带着几个问题去阅读,可以帮助大家更好的理解:

  • 问题1:代理类为什么可以在运行的时候自动生成呢?如何生成的呢?
  • 问题2:为什么调用代理类的相应的代理方法就可以调用到InvocationHandler实现类的invoke方法呢?
  • 问题3:为什么jdk proxy只支持代理有接口实现的类呢?

ps :为了提升阅读体验,让大家有一个更清晰的认知,以下源码会将一些异常处理和日志打印代码删除,只保留主干代码,请知悉~

我们就从两个核心:InvocationHandlerProxy来进行分析整个脉络,他们都在java.lang.reflect包下

InvocationHandler源码
代码语言:javascript
复制
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

上述就是InvocationHandler的源码,没什么其他的就是一个接口,里面有一个待实现方法invoke,处理类实现此接口重写invoke方法

Proxy源码
代码语言:javascript
复制
public class Proxy implements java.io.Serializable {
    // 处理类实例 变量
    protected InvocationHandler h;
    // 用于存储 已经通过动态代理获取过的代理类缓存
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>  proxyClassCache = new WeakCache<>(new KeyFactory(),new ProxyClassFactory());
    // 私有无参构造,使得只能通过传入InvocationHandler参数来创建该对象
    private Proxy() {}
    // 保护 构造函数,入参InvocationHandler处理类
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces) throws IllegalArgumentException{
        ...
    }
    public static boolean isProxyClass(Class<?> cl) {
        ...
    }
    public static InvocationHandler getInvocationHandler(Object proxy)  throws IllegalArgumentException {
        ...
    }
    // 生成代理类的实现方法
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException{
        ...
	}
    // 各种私有方法
    private ... ...
}

Proxy类的整体的架构就类似于上述,InvocationHandler h参数和两个构造函数四个上述已经介绍过的共有方法,还有一系列的私有方法,getProxyClass、isProxyClass、getInvocationHandler功能就和上面介绍的一样,就不再详细介绍了

我们下面来主要看一下newProxyInstance方法 newProxyInstance方法,我在方法内添加上了对应的注释:

代码语言:javascript
复制
    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException{
        // 1. 克隆对应的接口,用于代理类实现的接口数组
        final Class<?>[] intfs = interfaces.clone();
        ...
        /*
         * Look up or generate the designated proxy class.  源码中的介绍
         * 2. 查找或者生成指定的代理类, 下面会详细介绍
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        /*
         * Invoke its constructor with the designated invocation handler.
         * 3. 上面代码已经生成了代理类 cl,cl其中包含一个参数为传入的InvocationHandler h的构造函数, 获取该构造函数并通过该构造函数创建一个类的实例对象并返回
         */
        try {
            // 4. 通过《反射》获取参数为InvocationHandler的构造函数
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            // 5. 判断构造函数是否为私有的,如果为私有的则需要设置私有可访问权限
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 6. 通过上述获取的构造函数创建对应的 实例对象,并返回!over~
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
           // 各种异常处理
        }
    }

在上面的代码中,我简单的标注了一下每行代码的作用,下面我们来详细分析一下;

代理类的字节码生成逻辑

我们知道,在加载jvm前,java文件都已经被编译成了class字节码文件, 然后jvm通过类加载器将字节码文件加载到jvm中;

我们的代理类也是这样,不同的是动态代理的类是在程序运行时产生的,我们要做的就是如何在程序运行的时候,通过被代理类的字节码生成代理类的字节码!

我们接下来详细分析newProxyInstance方法:

在newProxyInstance中调用了Class<?> cl = getProxyClass0(loader, intfs);语句生成了代理类的字节码,此处调用了getProxyClass0方法,传入了指定的类加载器和对应要实现的接口

那么, 我们看看getProxyClass0方法的实现:

代码语言:javascript
复制
    private static Class<?> getProxyClass0(ClassLoader loader, Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        // proxyClassCache是WeakCache弱引用缓存类,如果之前就生成过对应的代理类就从缓存中取,如果没生成过就重新生成
        return proxyClassCache.get(loader, interfaces);
    }

proxyClassCache是Proxy类中的静态变量,是WeakCache类,里面封装了两个类KeyFactory、ProxyClassFactory,都是BiFunction函数式接口(如果不清楚函数式接口,请自行google);

将其拿过来看一下proxyClassCache = new WeakCache<>(new KeyFactory(),new ProxyClassFactory()); 其中调用了proxyClassCache.get(loader, interfaces)方法的实现

未避免代码过长,只粘贴了核心代码:

代码语言:javascript
复制
   public V get(K key, P parameter) {
        ...
        // 这部分主要是获取对应的 函数式接口,如果不明白函数式接口,google一下吧~
        Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
        Supplier<V> supplier = valuesMap.get(subKey);
        Factory factory = null;
        while (true) {  // 此处为什么是while循环呢, 主要是supplier不为空的话,则执行下面的语句赋值后,再循环执行下一次则supplier不为空
            if (supplier != null) {
                // 如果存在对应的函数式接口,  调用函数式接口对应的代码
                // 重点!!!调用函数式接口!!
                V value = supplier.get();
                if (value != null) {
                    return value;
                }
            }
            if (factory == null) {
                // 创建一个 专门创建代理类字节码的工厂类,实现类是ProxyClassFactory
                factory = new Factory(key, parameter, subKey, valuesMap);
            }
            if (supplier == null) {
                supplier = valuesMap.putIfAbsent(subKey, factory);
                if (supplier == null) {
                    // 将supplier赋值factory
                    supplier = factory;
                }}}} }

总结一下上述方法的流程:

缓存中获取代理类字节码

是否存在

代理中获取并返回

调用ProxyClassFactory的apply方法生成代理类字节码

接着ProxyClassFactory.apply方法看一下:

代码语言:javascript
复制
       public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            // 获取接口对应的接口class对象
            for (Class<?> intf : interfaces) {
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } 
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) { }
            }

            String proxyPkg = null;
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
            // 判断是否包含公有的接口对象,判断是否可以通过jdk proxy的方式进行生成代理类
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                    }
                }
            }
            // 如果没有公有接口类,需要使用CGLib来实现。。。
            if (proxyPkg == null) {
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }
            // 组装代理类的类名称
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            // 重点!! 此处生成代理类的字节码数组
            byte[] proxyClassFile = ProxyGenerator.generateProxyClassproxyName, interfaces, accessFlags);
            try {
               // 通过类加载器将字节码数组加载到JVm的方法区中生成Class对象!
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
            }
        }

上述的byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags); 为生成代理类字节码数组的方法,调用的方法中调用了generateClassFile方法;

代码语言:javascript
复制
private byte[] generateClassFile() {
        // 首先,默认代理的三个方法:hashCode\equals\toString
        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
        // 获取所有的要被代理类实现的接口
        Class[] var1 = this.interfaces;
        int var2 = var1.length;
        int var3;
        Class var4;
        // 遍历上述获取的接口
        for(var3 = 0; var3 < var2; ++var3) {
            // 赋值: 将接口的Class对象赋值!
            var4 = var1[var3];
            // 通过“反射”获取所有方法
            Method[] var5 = var4.getMethods();
            int var6 = var5.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                Method var8 = var5[var7];
                // 将方法添加到 要被代理的方法中
                this.addProxyMethod(var8, var4);
            }
        }
        // 获取要代理方法后,开始组装字节码
        var14.writeInt(-889275714);
        var14.writeShort(0);
        var14.writeShort(this.accessFlags);
        var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
        // 注意!!!  .... 此处省略了绝大部分 字节码的组装过程,只给出了几行代码展示一下字节码的组装
        // 最终返回组装好的字节码文件
        return var13.toByteArray();
        }
    }

generateClassFile中,你会发现里面全部是重组字节码的代码, 主要是获取被代理类字节码和操作类InvocationHandler字节码组装出代理类的字节码,在重组的过程因为是在运行时进行了代理类的创建,无法像往常一样new一个被代理类的实例获取他的方法,让代理类进行调用。

获取字节码后,接下来就要将代理类的字节码加载进JVM中了,这里调用的是一个return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length) 其中的defineClass0是一个本地native 方法,传入了代理类名称、类加载器、代理类的字节码文件、文件长度参数,从而将字节码加载进JVM中! 代码如下:

代码语言:javascript
复制
private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);

将代理类的字节码加载进JVM后,会在方法区内生成一个Class对象,标识这个代理类;

代理类的实例生成逻辑 上面,我们知道了通过字节码技术生成了代理类字节码,并通过类加载器将字节码文件加载到了JVM的方法区中生成了一个Class对象,我们如何在运行时获取这个Class对象的实例呢? 只有获取了对象实例才可以使用不是~ 还是回到newProxyInstance方法中,上面我们分析了Class<?> cl = getProxyClass0(loader, intfs)这部分逻辑,生成了Class对象cl,下面生辰该实例代码,过程很简单,相关逻辑我就直接在代码中注释了

代码语言:javascript
复制
    // 定义构造函数的参数类型,下面的一个语句使用
    private static final Class<?>[] constructorParams =    { InvocationHandler.class };

    // 通过反射获取上述获取的Class对象的带参构造函数,参数必须是上述定义的 InvocationHandler.class类型
    final Constructor<?> cons = cl.getConstructor(constructorParams);
    // 检查权限,如果是私有权限,设为可被访问
    if (!Modifier.isPublic(cl.getModifiers())) {
         AccessController.doPrivileged(new PrivilegedAction<Void>() {
               public Void run() {
                   cons.setAccessible(true);
                    return null;
                }
          });
     }
     // 通过构造函数传入对应 处理类h 参数,生成实例!
    return cons.newInstance(new Object[]{h});

上述就是生成实例的代码,生成实例后newProxyInstance就返回该实例了,就可以使用了~

反射:在运行时获取被代理类的字节码

那如何才能在运行时获取到被代理类的构造函数、方法、属性等字节码呢? 此时“反射!”登场了!我们通过反射可以在运行时获取到类的所有信息,所有哦。 定义: JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制

比如,在上述所说的组装代理类字节码时,在获取被代理类的所有方法时,就调用了Method[] var5 = var4.getMethods(); 反射中的getMethods方法,通过反射获取到了被代理类的所有方法,这样我们就可以在运行时获取到任何类的所有的字节码信息了! 从而可以组装出我们想要的代理类字节码!

所以说,反射也为动态代理的实现提供了理论支持!!因为只有在运行时能获取到对应类的信息,才可以通过信息创造出对应的我们所需要的代理类;

源码分析总结

总而言之,动态代理的理论支持是可以通过反射机制运行时获取到类的所有信息,如果运行时获取不到被代理类的信息,那还咋生成代理类。 动态代理的大致流程:

反射获取被代理字节码

反射获取InvocationHandler实现类字节码

依据被代理类字节码和InvocationHandler实现类字节码 通过操作字节码组装 代理类 字节码

通过给定的classLoad将 代理类字节码 加载到JVM中

调用native方法 加载到JVM的方法区 生成Class对象

反射获取代理类的Class对象的构造函数

通过反射获取的构造函数new一个代理类的实例A

使用代理类实例A

通过上述流程。我们就获得了一个代理类对象了,调用代理类对应的方法,就会执行我们规定的执行逻辑,实现对被代理类的运行时动态增强和扩展!

此时,我们再拿出刚开始我们用JDK proxy实现的动态代理代码中的生成代理类的代码:ImplA proxyA = (ImplA)Proxy.newProxyInstance(A.getClass().getClassLoader(), A.getClass().getInterfaces(), myHandler) 每个参数的作用,是不是就很清晰了

问题解答

上面动态代理实现流程,我们可以回答上述的第一个代理类为什么可以在运行的时候自动生成呢?如何生成的呢? 问题了

对于第二个为什么调用代理类的相应的代理方法就可以调用到InvocationHandler实现类的invoke方法呢?和第三个为什么jdk proxy只支持代理有接口实现的类呢?问题,我们需要反编译一下我们通过字节码技术产生的代理类,如下:

代码语言:javascript
复制
final class $Proxy0 extends Proxy implements ImplA {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;
    
    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.test.ImplA").getMethod("methoda");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
           // ..
        }
    }
   
    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    // 需要被加强的方法methoda
    public final void methoda() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) throws  {
        // 省略部分代码。。。
        return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
    }
    public final String toString() throws  {
        // 省略部分代码。。。
        return (String)super.h.invoke(this, m2, (Object[])null);
    }
    public final int hashCode() throws  {
        // 省略部分代码。。。
        return (Integer)super.h.invoke(this, m0, (Object[])null);
    }
}

上述代码包含几个关键点:

  1. 方法为final类型,不可再被继承
  2. 代理名称为 $Proxy 代理类前缀 + 递增数字
  3. 继承动态代理的核心类Proxy, 实现了我们指定的接口ImplA
  4. 一个带参构造方法$Proxy0(InvocationHandler var1) 传入InvocationHandler内部调用了父类的Proxy的构造函数
  5. methoda、toString、hashCode、equals全部调用的传入的InvocationHandler参数的 invoke方法!!!

现在回答第二个问题为什么调用代理类的相应的代理方法就可以调用到InvocationHandler实现类的invoke方法呢?

显而易见,代理类内部的代理方法全部显式调用的InvocationHandler实现类的invoke方法

第三个问题为什么jdk proxy只支持代理有接口实现的类呢?

因为代理类在使用JDK proxy方式生成代理类时,默认继承Proxy类,又因为java语言是单继承不支持多继承,那怎样才能标识我要代理什么类型的类或是代理什么方法呢? 接口呗,java支持接口的多继承,多少个都ok~

好了,上述将动态代理的使用方式 和 实现原理统一过了一遍,也回答了几个容易疑惑的问题,下面我们简单说下动态代理在现实的java框架大家庭中的一些典型应用

动态代理的应用

spring aop : 这可以说是spring框架中最典型的应用了,通过动态代理在运行时产生代理类,完成对被代理类的增强和功能附加 RPC框架的实现 : 远程过程调用,RPC使得调用远程方法和调用本地方法一样,这是怎么搞的呢?服务方对外放出服务的接口api,调用方拿到接口api,通过动态代理的方式生成一个代理类,代理类的处理类的invoke方法可以通过websocket连接远程服务器调用对应的远程接口; 这样我们再用代理对象进行调用对应方法时时,就像调用本地方法一样了 mybatis框架中 : mapper.xml中编写sql语句,mapper.java接口写上对应的方法签名;我们直接调用mapper.java中的方法就可以执行对应的sql语句,有没有想过为什么? 框架使用动态代理创建一个mapper.java的代理对象,代理对象的处理类invoke中执行sql,就ok了

总结

代理分为静态代理动态代理,动态代理的两种实现方式:JDK ProxyCGLib,动态代理的核心反射机制,通过反射在运行时获取被代理类字节码和处理类字节码,动态代理代理类的生成通过重组字节码的方式。

参考:JDK源码,https://www.jianshu.com/p/861223789d53

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-05-21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 追溯
  • 代理种类
    • 静态代理
      • 动态代理
        • JDK Proxy
        • 源码分析
        • 源码分析总结
        • 问题解答
        • 动态代理的应用
      • 总结
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档