JDK 动态代理的原理

写在前面

未经允许不得转载!

  • 什么是代理?
    • (我的理解)在编程语言中,代理就是由于某些原因控制某个对象/方法的访问
  • 为什么要有静态代理?
    • 开闭原则,需求总是变化无常的,在不修改原有逻辑的情况下,对其进行拓展
  • 为什么要有动态代理?
    • 用静态代理的原因,动态代理也有
    • 方便维护,从静态代理的维护实现类到现在只需要维护拓展逻辑

这篇文章有好多隐藏的知识点,用心看用心挖掘,最好自己写一遍代码,走一遍完整的逻辑。

静态代理

静态代理感觉没啥好说的,实现方式可以是组合,也可以是继承,当然是推荐用组合的方式实现静态代理,理由是解耦,可以任意组装需要的组合,比如说你要对原逻辑代理3次(我的案例可以拆成两个代理,一个校验参数,一个打印信息),进行不同的业务逻辑,且执行的顺序可能会变化。

代码看附录,需要用到的类: Calculable, Calculator, CalculatorStaticProxy, CalculatorStaticProxyTest

动态代理

动态代理相对于静态代理来说,目标是一致的,就是控制目标方法的访问,但是代理类是动态生成的。
重点来了:动态生成代理类,生成方式也有好多种,比如说:生成新的一个代理类,将原类改造成代理类。

JDK 动态代理

(这里只说果,了解因看下面的原理解析),还有排除java.lang.Object(本来就是所有实现类的 super class)

这段很重要,结论:
代理类一定是java.lang.reflect.Proxy的子类(所以 JDK 动态代理只能代理有interface的类及其方法),并且implements了指定类的所有interface,并且一定有且只有一个带java.lang.reflect.InvocationHandler参数的Constructor方法,实例化对象的时候一定会将 InvocationHandler 带入。

还有个很重要的点: 为什么创建代理对象的时候,要指定 ClassLoder?
我觉得有两个原因:
* 可以实现安全机制,注释上有说明
* 跟随目标类进行同步热加载类

然后,当你调用某个interface的方法时,最终会转发给上面构造器带入的那个对象的invoke方法,搞定,收工。

代码看附录,需要用到的类: Calculable, Calculator, CalculatorJdkDynamicProxyHandler, CalculatorJdkDynamicProxyTest, $CalculatorJdkDynamicProxy(这个类就是生成的代理类)。
基于JDK 8u201

原理解析

想要动态生成对象的前提是:要有这个对象的 Class 对象。
想要有这个对象的 Class 对象的前提是:要有这个对象的 class (也就是字节码),然后通过 ClassLoader 加载到方法区的。
所以问题就在怎么动态生成字节码,并且怎么加载到方法区的。

那么来看看JDK 动态代理怎么实现的

解析基于JDK 8u201
先来看看代理类的结构:

  • 第1部分:定义 class 的那段
  • 第2部分:定义 fields 的那段
  • 第3部分:定义 constructor methods 的那段
  • 第4部分:定义 methods 的那段

然后来看看代理类的字节码是怎么生成的(只说关键部分):

  • 调用顺序:
    • java.lang.reflect.Proxy#newProxyInstance
    • java.lang.reflect.Proxy#getProxyClass0
    • proxyClassCache.get
    • private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
    • java.lang.reflect.WeakCache#get
    • subKeyFactory.apply(key, parameter)(不重要)
    • java.lang.reflect.Proxy.KeyFactory#apply(不重要)
    • factory = new Factory(key, parameter, subKey, valuesMap);
    • supplier = factory;
    • supplier.get()
    • java.lang.reflect.WeakCache.Factory#get
    • valueFactory.apply
    • java.lang.reflect.Proxy.ProxyClassFactory#apply
      走到这里开始最重要的那块了 !!!
  • class name 怎么生成的,怎么拿到全部的 interface自己看
  • 先来看下怎么加载动态生成的字节码的
    • return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
    • 不是通过双亲委派的形式加载的,但是这个动态生成的类加载器是AppClassLoader
  • 再来看怎么生成字节码的sun.misc.ProxyGenerator#generateProxyClass(java.lang.String, java.lang.Class<?>[], int)
  • 如果要保存生成的代理类,查看saveGeneratedFiles这个字段
  • 再来看生成字节码的逻辑
    • sun.misc.ProxyGenerator#generateClassFile
    • ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    • 进入sun.misc.ProxyGenerator#generateClassFile方法后,第一次看建议反着看,从结果往前推进
    • 从结果那可以看出,这字节码是 jdk 自己生成的
    • 然后跟着上面的代理类的结构一步步看上去
    • 解密上面的一些结论:
      • 有且只有一个带java.lang.reflect.InvocationHandler参数的Constructor方法
        • this.methods.add(this.generateConstructor());
      • 为什么父类是Proxy
        • var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
    • 下面附上了 ClassFile Structure,助你一臂之力,是不是豁然开朗了 ^^
    • var14.writeInt(-889275714);对应的是 magic: cafebabe
    • 一步步对过来
ClassFile {
  u4 magic;
  u2 minor_version;
  u2 major_version;
  u2 constant_pool_count;
  cp_info constant_pool[constant_pool_count-1];
  u2 access_flags;
  u2 this_class;
  u2 super_class;
  u2 interfaces_count;
  u2 interfaces[interfaces_count];
  u2 fields_count;
  field_info fields[fields_count];
  u2 methods_count;
  method_info methods[methods_count];
  u2 attributes_count;
  attribute_info attributes[attributes_count];
}

知识补充

  • 除了 ClassLoder ,还可以用sun.misc.Unsafe#defineClass来把加载类信息

附录

public interface Calculable {
    Integer divide(int a, int b);
}
public class Calculator implements Calculable {
    @Override
    public Integer divide(int a, int b) {
        return a / b;
    }
}
public class CalculatorStaticProxy implements Calculable {
    private Calculable target;

    public CalculatorStaticProxy(Calculable target) {
        this.target = target;
    }

    @Override
    public Integer divide(int a, int b) {
        Integer result = null;

        boolean isPass = beforeDivideProxy(a, b);
        if (isPass) {
            result = target.divide(a, b);

            afterDivideProxy(a, b);
        }

        return result;
    }

    private boolean beforeDivideProxy(int a, int b) {
        System.out.printf("before exec %d / %d\n", a, b);
        if (b == 0) {
            return false;
        }
        return true;
    }

    private void afterDivideProxy(int a, int b) {
        System.out.printf("after divide method\n");
    }
}
import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

public class CalculatorStaticProxyTest {
    @Test
    public void testDivide_normal() {
        Calculable calc = new CalculatorStaticProxy(new Calculator());
        assertEquals(Integer.valueOf(2), calc.divide(6, 3));
    }

    @Test
    public void testDivide_exception() {
        Calculable calc = new CalculatorStaticProxy(new Calculator());
        assertNull(calc.divide(6, 0));
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class CalculatorJdkDynamicProxyHandler implements InvocationHandler {
    private Calculable target;

    public CalculatorJdkDynamicProxyHandler(Calculable target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        if ("divide".equals(methodName)) {
            return handleDivideMethod(method, args);
        }
        return method.invoke(target, args);
    }

    private Integer handleDivideMethod(Method method, Object[] args)
            throws InvocationTargetException, IllegalAccessException
    {
        Integer result = null;

        boolean isPass = beforeDivideProxy(method, args);
        if (isPass) {
            result = (Integer) method.invoke(target, args);
            afterDivideProxy(method, args);
        }

        return result;
    }

    private boolean beforeDivideProxy(Method method, Object[] args) {
        System.out.printf("before exec divide method\n", args[0], args[1]);
        if ((Integer) args[1] == 0) {
            return false;
        }
        return true;
    }

    private void afterDivideProxy(Method method, Object[] args) {
        System.out.printf("after divide method\n");
    }
}

import org.junit.Before;
import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;

public class CalculatorJdkDynamicProxyTest {
    private Calculable proxyCalc = null;

//    public static void main(String[] args) {
//        // 保存生成的代理类的字节码文件: jdk8 下的配置,默认生成的路径是项目根目录
//        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//
//        sun.misc.ProxyGenerator.generateProxyClass(
//                "$CalculatorJdkDynamicProxy",
//                Calculator.class.getInterfaces(),
//                java.lang.reflect.Modifier.PUBLIC);
//    }

    @Before
    public void beforeTest() {
        Calculable calc = new Calculator();
        ClassLoader loader = calc.getClass().getClassLoader();
        Class<?>[] interfaces = calc.getClass().getInterfaces();
        InvocationHandler h = new CalculatorJdkDynamicProxyHandler(calc);

        proxyCalc = (Calculable) Proxy.newProxyInstance(loader, interfaces, h);
        System.out.println(proxyCalc.getClass().getName());
        System.out.println(proxyCalc.getClass().getClassLoader());
    }

    @Test
    public void testDivide_normal() {
        assertEquals(Integer.valueOf(2), proxyCalc.divide(6, 3));
    }

    @Test
    public void testDivide_exception() {
        Calculable calc = new CalculatorStaticProxy(new Calculator());
        assertNull(calc.divide(6, 0));
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public class $CalculatorJdkDynamicProxy extends Proxy implements Calculable {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $CalculatorJdkDynamicProxy(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 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 Integer divide(int var1, int var2) throws  {
        try {
            return (Integer)super.h.invoke(this, m3, new Object[]{var1, var2});
        } catch (RuntimeException | Error var4) {
            throw var4;
        } catch (Throwable var5) {
            throw new UndeclaredThrowableException(var5);
        }
    }

    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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Calculable").getMethod("divide", Integer.TYPE, Integer.TYPE);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值