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日 下午1:01
下一篇 2021年3月7日 下午1:37

相关推荐

  • 有限状态机( Finite State Machine )JAVA 版

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

    2020年1月30日
    1.6K0
  • Spring Boot 2 + Spring Security 5 + JWT 的单页应用

    此前我已经写过一篇类似的教程,但那时候使用了投机的方法,没有尊重 Spring Security 的官方设计,自己并不感到满意。这段时间比较空,故重新研究了一遍。 老版本:http…

    2019年8月3日
    1.7K0
  • Shiro + JWT + Spring Boot Restful 简易教程

    GitHub 地址:https://github.com/Smith-Cruise/Spring-Boot-Shiro 序言 我也是半路出家的人,如果大家有什么好的意见或批评,请务…

    2019年8月1日
    1.6K0

发表回复

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