jdk动态代理中的Proxy类是怎么玩的?

2021/04/10 Java高级知识 共 5288 字,约 16 分钟
闷骚的程序员

1. 回顾动态代理

  1. jdk的动态代理中关键核心就在于一个Proxy类。这个类是jdk提供的内置类。这个类能干嘛?
  2. 前面学习反射机制、和java中常见机制时,曾经反复强调两个动态性的概念:动态加载和静态加载、动态字节码和静态字节码。在这个我们首先通过Proxy类来直观的感受下到底什么叫做动态字节码机制?
  3. 学习动态字节码机制时,核心的一点就是字节码的生成过程是在一个正在运行当中的JVM中动态进行的,生成的字节码是在JVM内存中虚拟缓存的,而不是java编译器预编译后产生的*.class文件。那么具体怎么样呢?
    Proxy类我们分为两节来学习,本节先学习Proxy对外暴露的4个API。

2. Proxy类

Proxy类对外只暴露了4个业务方法:

Proxy.newProxyInstance();
Proxy.getInvocationHandler();
Proxy.isProxyClass();
Proxy.getProxyClass();

下面学习。 为了深入学习Proxy类的API,先编写几个类:
IUser

package zeh.test.demo.com.proxy;

// 2021-03-30
public interface IUser {
    void doSome();
    void doOther();
}

UserImpl

package zeh.test.demo.com.proxy;

// IUser的实现类
public class UserImpl implements IUser {
    @Override
    public void doSome() {
        System.out.println("执行doSome.");
    }
    @Override
    public void doOther() {
        System.out.println("执行doOther.");
    }
}

2.1 newProxyInstance方法

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);

该方法目的是创建一个动态字节码文件的对象。
第一个参数 loader:表示需要动态生成的目标字节码类型的类装载器。
第二个参数 interfaces:表示需要动态生成的目标字节码对应的接口,可以是多个接口。
第三个参数 h:一个回调器,是InvocationHandler回调接口的对象,目的是当外界通过动态生成的字节码对象去调用对应的目标方法时,需要回调执行的逻辑。

ProxyMain测试

package zeh.test.demo.com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyMain {
    public static void main(String[] args) {
        IUser proxy = (IUser) Proxy.newProxyInstance(UserImpl.class.getClassLoader(), UserImpl.class.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("执行动态字节码代理对象的前置逻辑...");
                Object returnValue = method.invoke(new UserImpl(), args);
                System.out.println("执行动态字节码代理对象的后置逻辑...");
                return returnValue;
            }
        });
        // 执行动态字节码代理对象的doSome和doOther方法,最终会回调到 InvocationHandler 的 invoke 方法去执行真正传入的对象上的指定方法逻辑
        proxy.doSome();
        proxy.doOther();
        // 打印生成的动态字节码代理对象的所属Class描述
        System.out.println("proxy:" + proxy.getClass());
    }
}

测试结果

执行动态字节码代理对象的前置逻辑...
执行doSome.
执行动态字节码代理对象的后置逻辑...
执行动态字节码代理对象的前置逻辑...
执行doOther.
执行动态字节码代理对象的后置逻辑...
proxy:class com.sun.proxy.$Proxy0
Process finished with exit code 0

结果分析
这个案例就是jdk动态代理的实现。
newProxyInstance方法是动态代理的核心方法。
此处重点关注,该方法生成的代理对象对应的class打印出来是:class com.sun.proxy.$Proxy0。这是一个class,然而在整个工程中未找到对应的class文件。
那这个class字节码到底在哪里呢?它在jvm的内存中存储,执行完毕便释放了。

2.2 getInvocationHandler方法

public static InvocationHandler getInvocationHandler(Object proxy);

参数 proxy:之前生成的某个动态字节码代理对象。

ProxyMain测试

package zeh.test.demo.com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// zWX5331241 2021-03-30
public class ProxyMain {
    public static void main(String[] args) throws Throwable {
        IUser proxy = (IUser) Proxy.newProxyInstance(UserImpl.class.getClassLoader(), UserImpl.class.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("执行动态字节码代理对象的前置逻辑...");
                Object returnValue = method.invoke(new UserImpl(), args);
                System.out.println("执行动态字节码代理对象的后置逻辑...");
                return returnValue;
            }
        });
        // 执行动态字节码对象的doSome和doOther方法,最终会回调到 InvocationHandler 的 invoke 方法去执行真正传入的对象上的指定方法逻辑
        proxy.doSome();
        proxy.doOther();
        // 打印生成的动态字节码对象的所属Class描述
        System.out.println("proxy:" + proxy.getClass());
        // 获取动态字节码对象的回调器
        InvocationHandler invocationHandler = Proxy.getInvocationHandler(proxy);
        System.out.println("invocationHandler:" + invocationHandler.getClass());
        // 通过获取到的回调器,手动执行回调器中的回调方法
        invocationHandler.invoke(proxy, IUser.class.getMethod("doSome"), null);
    }
}

测试结果

执行动态字节码代理对象的前置逻辑...
执行doSome.
执行动态字节码代理对象的后置逻辑...
执行动态字节码代理对象的前置逻辑...
执行doOther.
执行动态字节码代理对象的后置逻辑...
proxy:class com.sun.proxy.$Proxy0
invocationHandler:class zeh.test.demo.com.proxy.ProxyMain$1
执行动态字节码代理对象的前置逻辑...
执行doSome.
执行动态字节码代理对象的后置逻辑...
Process finished with exit code 0

结果分析
在上一个案例的基础上,测试了Proxy类的getInvocationHandler方法。
目的是获取动态字节码代理对象的回调器,获取之后可以自己手动调用回调器中的回调方法,只需要传入对应的参数即可。

2.3 isProxyClass方法

public static boolean isProxyClass(Class<?> cl);

参数 cl:一个目标class对象。

ProxyMain测试

package zeh.test.demo.com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyMain {
    public static void main(String[] args) throws Throwable {
        IUser proxy = (IUser) Proxy.newProxyInstance(UserImpl.class.getClassLoader(), UserImpl.class.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("执行动态字节码代理对象的前置逻辑...");
                Object returnValue = method.invoke(new UserImpl(), args);
                System.out.println("执行动态字节码代理对象的后置逻辑...");
                return returnValue;
            }
        });
        // 用于判断目标class是否是动态字节码代理类
        System.out.println(Proxy.isProxyClass(proxy.getClass()));
        System.out.println(Proxy.isProxyClass(IUser.class));
    }
}

测试结果

true
false
Process finished with exit code 0

结果分析
isProxyClass方法用于判断目标class对象是否是一个动态字节码,如果传入的目标class就是动态生成的字节码的对象,则返回true;如果不是,则返回false。

2.4 getProxyClass方法

public static Class&lt;?&gt; getProxyClass(ClassLoader loader, Class&lt;?&gt;... interfaces);

第一个参数 loader:表示需要动态生成的目标字节码类型的类装载器。
第二个参数 interfaces:表示需要动态生成的目标字节码对应的接口,可以是多个接口。

ProxyMain测试

package zeh.test.demo.com.proxy;
import java.lang.reflect.Proxy;

public class ProxyMain {
    public static void main(String[] args) {
        // getProxyClass 方法用于获取一个目标接口的动态字节码的class对象,如果动态字节码文件不存在,则自动生成并保存在JVM内存中
        Class&lt;?&gt; proxyClass = Proxy.getProxyClass(UserImpl.class.getClassLoader(), UserImpl.class.getInterfaces());
        System.out.println("proxyClass:" + proxyClass);
    }
}

测试结果

proxyClass:class com.sun.proxy.$Proxy0
Process finished with exit code 0

结果分析
getProxyClass 方法用于获取一个目标接口的动态字节码的class对象,如果动态字节码文件不存在,则自动生成并保存在JVM内存中

文档信息

Search

    Table of Contents