JDK 动态代理踩坑

最近阅读 Hadoop 的源码,看到各种动态代理,打算学习下。虽然以前也学过,但是感觉就是死记硬背的代码,有些地方根本不懂。温习了一遍网上 JDK 动态代理的教程后,自己尝试再一次死记硬背的写一写,结果错误百出。于是打算狠下心来系统的学习下动态代理。

在这篇博客中我不会具体的说明动态代理的实现,但是我会贴上我参考的博客供大家参考。

疑问

如果你和我一样,JAVA 基础不好,初次接触 JDK 动态代理,你一定会有下面的几个疑问。

我先贴上我的示例代码:

package org;

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

interface Person {
    void say();
}

class PersonImpl implements Person {
    public void say() {
        System.out.println("Hello World!");
    }
}

class CustomInvocation implements InvocationHandler {

    private Object target;

    public CustomInvocation(Object obj) {
        this.target = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Q3
        System.out.println("before invoke");
        Object result =  method.invoke(target, args);
        System.out.println("after invoke");
        return result;
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Person person = new PersonImpl();
        // Q1
        Person proxyPerson1 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));

        Person proxyPerson2 = (Person) Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));

        Person proxyPerson3 = (Person) Proxy.newProxyInstance(Main.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));
        // Q2
        PersonImpl proxyPerson4 = (PersonImpl) Proxy.newProxyInstance(Person.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));

        Person proxyPerson5 = (PersonImpl) Proxy.newProxyInstance(Person.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));

        Person proxyPerson6 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), Person.class.getInterfaces(), new CustomInvocation(person));

        Person proxyPerson7 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), PersonImpl.class.getInterfaces(), new CustomInvocation(person));

        Person proxyPerson8 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, new CustomInvocation(person));
  }
}

Q1:ClassLoader 之谜,仿佛无论哪个 ClassLoader 都不报错?

Q2:$Proxy0 cannot be cast to xxxx ,我怎么动不动就报这个这个错!

Q3:InvocationHandler 中的 invoke 方法中的第一个参数 Object proxy 究竟是何方神圣?

Q1 到底填哪个ClassLoader ?

在理清应该填哪个 ClassLoader 之前,我们先理清 a.getClass()A.class 的区别。

  • a.getClass() 返回运行过程中 a 的 class 类型。比如你有 A a = new B(); 然后执行 a.getClass() 会返回 B
  • A.class 则是静态的认为是 A

例如下面这个函数判断一个类的包名是不是默认的,我们需要使用 .getClass() 进行判断。

boolean isDeclaredInDefaultPkg(Object o) {
    // 在这里,o 的类型是动态的,我不可能提前预知,所以使用 getClass()。
    return o.getClass().getPackage() == null;
}

再比如这个,我们明确的知道我们需要的类是 DayOfWeek.class,就可以这么写

Set<DayOfWeek> allDays = EnumSet.allOf(DayOfWeek.class);

参考来源:https://programming.guide/java/difference-between-class-and-getclass.html

回到 Q1,所以我们可以推断出,其实在这里,Person.classperson.getClass() 都是可以的。但是如果我们要写一个通用性的动态代理,肯定还是用 getClass() 的办法,毕竟我们不可能提前知道我们要动态代理哪一个类。

接下来我们看看 ClassLoader,到底应该是哪个类的 ClassLoader 呢?事实上,随便你哪个类,都可以,反正都是 AppClassLoader。不信你可以打印看看。

System.out.println(Person.class.getClassLoader());
System.out.println(person.getClass().getClassLoader());
System.out.println(Main.class.getClassLoader());

// 输出结果:
// sun.misc.Launcher$AppClassLoader@18b4aac2
// sun.misc.Launcher$AppClassLoader@18b4aac2
// sun.misc.Launcher$AppClassLoader@18b4aac2

至于为什么,推荐你去看看 https://blog.csdn.net/briblue/article/details/54973413

Q2 $Proxy0 cannot be cast to xxxx

这个问题就是我自己尝试写动态代理时最常遇到的问题。出现这个问题意味着你动态代理里面的操作姿势不对。

PersonImpl proxyPerson4 = (PersonImpl) Proxy.newProxyInstance(Person.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));

Person proxyPerson5 = (PersonImpl) Proxy.newProxyInstance(Person.class.getClassLoader(), person.getClass().getInterfaces(), new CustomInvocation(person));

Person proxyPerson6 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), Person.class.getInterfaces(), new CustomInvocation(person));

Person proxyPerson7 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), PersonImpl.class.getInterfaces(), new CustomInvocation(person));

Person proxyPerson8 = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class[]{Person.class}, new CustomInvocation(person));

上面所有的示例中,只有 proxyPerson7 和 proxyPerson8 是正确的。

proxyPerson4 错在我转型成了 PersonImpl,因为 JDK 动态代理构造的时候,是根据我传入的 interface 进行构造的,而不是 interface 的实现类,而 PersonImpl 是一个实现类。其实这个操作其实有点类似于把一个 interface 强制转换成一个他的实现类,这怎么可能呢?

proxyPerson5 犯得和 proxyPerson4 是一样的错误。

proxyPerson6 是错在 Proxy.newProxyInstance(xxx) 的第二个参数传入的是 Person 的 interface,问题是 Person 的 interface 是空的,Person 自己本身并没有实现哪一个接口,所以这里返回的是 null,所以构造出来的代理类肯定也是有问题的。

proxyPerson7 和 proxyPerson8 其实就是变了一下写法,两个实质上是一模一样的,

Q3 Invoke 方法中那个 Object proxy 是谁?

public Object invoke(Object proxy, Method method, Object[] args) ,据我们所知,如果要实现动态代理,我们需要实现自己的 InvocationHandler 类。在 InvocationHandler 中,我们需要重写原来的 invoke 方法。最初我有一个疑问,就是 invoke 方法中第一个参数 Object proxy 是谁,因为感觉在动态代理中根本用不到。

经过查找资料,实际上那个 Object proxy 就是代理后的类。下面给大家贴段代码:

public final void sayHello() throws {
  try {
    this.h.invoke(this, m3, null);
    return;
  } catch (RuntimeException localRuntimeException) {
    throw localRuntimeException;
  } catch (Throwable localThrowable) {
    throw new UndeclaredThrowableException(localThrowable);
  }
}

这段代码就是 JDK 动态代理生成后的类,sayHello() 就是被代理类中的一个方法,其中 this.h 就是我们自己实现的 InvocationHandler,我们看到第一个参数传递的是 this,也就是自己(通过动态代理生成后的类)。

更多具体信息可看这篇文章:https://blog.csdn.net/yhl_jxy/article/details/80586785

原创文章,作者:Smith,如若转载,请注明出处:https://www.inlighting.org/archives/jdk-dynamic-proxy

打赏 微信扫一扫 微信扫一扫
SmithSmith
上一篇 2020年1月30日
下一篇 2021年3月7日

相关推荐

  • 用 Java 实现一个可用的布隆过滤器(Bloom Filter)

    布隆过滤器可以使用极少的空间来判断一个元素是否存在某一个集合中,本文不具体讨论布隆过滤器的原理,而是探讨如何实现一个可用的布隆过滤器。 本文代码提取自 Apache ORC 项目。…

    2021年3月7日
    1.7K0
  • Spring Boot2 + Spring Security + Thymeleaf 简单教程

    因为有一个项目需采用MVC构架,所以学习了Spring Security并记录下来,希望大家一起学习提供意见 如果有疑问,请在 GitHub 中发布 issue,我有空会为大家解答…

    2019年8月2日
    1.4K0
  • 有限状态机( Finite State Machine )JAVA 版

    介绍 有限状态机通常用于模拟序列逻辑,换句话说,就是用于代表和控制执行流程。 有限状态机所需条件: 一个物体只有固定的几种状态(例如交通灯只有绿灯、黄灯和红灯三个状态)。 有固定的…

    2020年1月30日
    1.6K0

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注