跟小白领悟JDK动态代理——理解和学会使用(上)

31 阅读7分钟

1. 概述

JDK 动态代理是 Java 反射机制的一个重要应用,它允许在运行时动态创建代理类,实现对目标对象的方法调用进行拦截和增强。

2. 示例实现

2.1 项目结构

cn.pug.springLab.aop
├── Main.java
├── ProxyInvocationHandler.java
├── Service.java
└── ServiceImpl.java

2.2 核心代码实现

2.2.1 目标接口

public interface Service {
    String say(String word);
}

2.2.2 目标实现类

public class ServiceImpl implements Service{
    @Override
    public String say(String word) {
        System.out.println(word);
        return word;
    }
}

2.2.3 代理调用处理器

定义一个实现java.lang.reflect.InvocationHandler接口的实现类,即代理调用处理器:ProxyHandler

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler {
    //目标对象(将要被代理的对象,为了统一描述,以下均以目标对象进行描述)
    private Object target;

    /**
     * 组合关系为强关联关系,以构造函数的形式写入目标对象
     * @param target 目标对象
     */
    public ProxyInvocationHandler(Object target){
        this.target = target;
    }

    /**
     * 提供代理对象的静态方法,封装获取代理对象的代码
     * @param target 目标对象
     * @return
     */
    public static Object getProxy(Object target){
        //1、初始化当前调用处理器的实例
        InvocationHandler handler=new ProxyInvocationHandler(target);
        //2、调用Proxy的newProxyInstance方法以获取动态生成的代理对象
        return Proxy.newProxyInstance(
                //传入目标对象的类加载器
                target.getClass().getClassLoader(),
                //传入目标对象所有实现的接口对应的类对象
                target.getClass().getInterfaces(),
                //传入当前调用处理器的
                handler
        );
    }

    /**
     * 处理代理实例上的方法调用并返回结果。当与代理实例相关联的调用处理器上调用方法时,将调用此方法。
     * 
     * @param proxy 该方法被调用时的代理实例。
     *              
     * @param method 与在代理实例上调用的接口方法相对应的Method实例。
     *               该{@code Method}对象的声明类将是该方法被声明的接口,这可能代理类继承该方法的代理接口的超接口。
     *
     * @param args 一个对象数组,其中包含在代理实例上调用方法时传递的参数值;如果接口方法没有参数,则为{@code null}。
     *             基本类型的参数会被包装在相应的基本包装类实例中,例如{@code java.lang.Integer}或{@code java.lang.Boolean}。
     *
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //前置
        System.out.println("before invoked...");
        //运行目标对象的方法,并接收结果
        Object o=method.invoke(this.target,args);
        //后置
        System.out.println("after invoked...");
        //回传目标对象原始方法的运行结果
        return o;
    }

}

2.2.4 测试类

public class Main {
    public static void main(String[] args) {
        //设置将JDK动态生成的代理类字节码留存到磁盘,路径为项目根目录下的com/sun/proxy
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        //此处初始化一个目标对象
        Service service=new ServiceImpl();
        Service proxy=(Service) ProxyInvocationHandler.getProxy(service);
        service.say(service.getClass().getName()+":hi,i'm the origin!");
        System.out.println("=======================");
        proxy.say(proxy.getClass().getName()+":hi,but i'm the proxy!");
    }
}

运行结果:

cn.pug.springLab.aop.ServiceImpl:hi,i'm the origin!
=======================
before invoked...
com.sun.proxy.$Proxy0:hi,but i'm the proxy!
after invoked...

3. 代理类分析

3.1 生成的代理类

JDK 动态代理会在运行时生成代理类,通过设置 sun.misc.ProxyGenerator.saveGeneratedFiles 属性,我们可以将生成的代理类保存到磁盘。以下是生成的代理类代码: 生成com/sun/proxy/$Proxy0.class文件,反编译内容:

package com.sun.proxy;

import cn.pug.springLab.aop.Service;
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 Service {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    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 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 String say(String var1) throws  {
        try {
            return (String)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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("cn.pug.springLab.aop.Service").getMethod("say", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(((Throwable)var2).getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(((Throwable)var3).getMessage());
        }
    }
}

3.2 代理类特点分析

由jdk动态代理生成的字节码反编译内容可以看到:

  • 当动态生成的代理类被加载到内存后,首先执行的静态代码块会获取目标类实现接口的所有抽象方法的具体实现方法(也包括继承自Objects的方法:toString、hashCode和equals)。
  • 随后可以观察到代理对象实现了目标对象实现的所有接口并对接口的抽象方法进行了模板化的实现(即最终返回super.h.invoke),即对上一步获取到的目标类实现接口的所有方法塞进super.h.invoke方法。
  • 代理类继承了java.lang.reflect.Proxy

3.3 动态代理生成类的研究

以上对k动态代理生成的字节码反编译内容观察到的现象会有几个问题,要知道答案需要将之前的代码和线索内容串起来:

3.3.1 super.h是什么?为什么调用invoke方法?

代理类继承了java.lang.reflect.Proxy,super.h的内容即java.lang.reflect.Proxy.h属性

public class Proxy implements java.io.Serializable {

    /**
     * the invocation handler for this proxy instance.
     * @serial
     */
    protected InvocationHandler h;
}

从java.lang.reflect.Proxy源码中可以看到所谓的super.h属性即我们自己实现了java.lang.reflect.InvocationHandler接口的实现类,在之前生成代理时以入参形式注入。

Proxy.newProxyInstance(
                //传入目标对象的类加载器
                target.getClass().getClassLoader(),
                //传入目标对象所有实现的接口对应的类对象
                target.getClass().getInterfaces(),
                //传入当前调用处理器的
                handler
        );

那么调用的invoke方法即我们自己重写的方法:

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //前置
        System.out.println("before invoked...");
        //运行目标对象的方法,并接收结果
        Object o=method.invoke(this.target,args);
        //后置
        System.out.println("after invoked...");
        //回传目标对象原始方法的运行结果
        return o;
    }

$Proxy0对象持有InvocationHandler对象,InvocationHandler对象持有目标对象,方法调用栈:$Proxy0.say(String var1)->ProxyInvocationHandler.invoke(Object proxy, Method method, Object[] args)->ServiceImpl.say(String word);所以这个代理是通过两层组合的方式实现的。

3.3.2 代理类$Proxy0是如何被动态生成的?

想想我们是怎么生成这个代理类对象的:

Proxy.newProxyInstance(
                //传入目标对象的类加载器
                target.getClass().getClassLoader(),
                //传入目标对象所有实现的接口对应的类对象
                target.getClass().getInterfaces(),
                //传入当前调用处理器的
                handler
        );

进入Proxy类观察newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)源码:

/**
 * 动态生成并实例化一个代理类,该类实现指定的接口,并将方法调用委托给给定的InvocationHandler。
 *
 * @param loader 用于加载代理类的类加载器
 * @param interfaces 代理类需要实现的接口列表,不能为空或包含null元素
 * @param h 处理代理实例方法调用的InvocationHandler对象,不能为空
 * @return 生成的代理类的实例
 * @throws IllegalArgumentException 如果interfaces为空、包含null元素,或包含非接口类型
 */
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        /*
         * 执行安全检查,确保调用者有权限创建指定接口的代理类
         */
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            /*
             * 通过特权操作将非公共构造器设置为可访问,以允许实例化
             */
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        /*
         * 将构造器访问或实例化错误包装为InternalError
         */
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        /*
         * 处理构造器调用时抛出的目标异常,将其转换为RuntimeException或InternalError
         */
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        /*
         * 当代理类的构造器不存在时抛出InternalError
         */
        throw new InternalError(e.toString(), e);
    }
}

其中关键的步骤是如何获取代理类的类对象getProxyClass0(loader, intfs),查看源码:

/**
 * 生成代理类。调用此方法前必须先调用 checkProxyAccess 方法进行权限检查。
 *
 * @param loader     用于定义代理类的类加载器。
 * @param interfaces 代理类实现的接口数组。
 * @return 生成的代理类。
 */
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    // 检查接口数量是否超过 JVM 限制(65535)
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }

    // 从缓存中获取代理类,若不存在则创建新类
    return proxyClassCache.get(loader, interfaces);
}

getProxyClass0(loader, intfs)方法为什么以数字0结尾?主要是为了实现公开接口与核心逻辑分离,一般这种以数字结尾的方法都是私有方法,实现实际的核心逻辑,而对应不带数字后缀的方法是公开方法,仅负责参数校验与异常处理等外围逻辑。

为什么jvm限制接口数量是65535?这个数目是怎么来的?根据《Java虚拟机规范》,interfaces_count字段使用u2类型(无符号短整型)存储类实现的接口数量。其最大值为: 2^16 - 1 = 65535

# 跟小白领悟JDK动态代理——核心类库里的缓存优化WeakCache(中)