Jdk动态代理为啥不能代理Class?

简介: 该文章主要介绍了JDK动态代理的原理以及为何JDK动态代理不能代理Class。

看完了SpringAop,明白了Spring底层封装了cglib和jdk动态代理,并且默认使用了Jdk动态代理来实现aop技术,可以说没有动态代理就没有SpringAop,这下好了,Spring必须叫Jdk动态代理为大哥!!

image.png

既然jdk动态代理地位如此重要,那么Jdk动态代理到底是如何实现的?代理类到底是如何生成的?

image.png

咱们还是先回顾下动态代理的使用姿势吧,先以LoginService接口的login方法为例

1、Jdk动态代理使用步骤一实现代理逻辑

实现InvocationHandler接口,实现相关代理逻辑

//实现invoke方法,实现代理逻辑
class ProxyInvocationHandler implements InvocationHandler {
   
   

    //原对象-被代理对象
    private Object source;

    public ProxyInvocationHandler(Object source) {
   
   
        this.source = source;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
   
        System.out.println("代理类:"+ proxy.getClass() + "被代理类:" + source.getClass());
        System.out.println("代理开始执行方法=====" + method.getName());
        Object result = method.invoke(source, args);
        System.out.println("代理结束执行方法=====" + method.getName());
        return result;
    }
}

2、Jdk动态代理使用步骤二创建代理对象

public static LoginService createProxy() {
   
   
    LoginService loginService = (LoginService) Proxy.newProxyInstance(LoginService.class.getClassLoader(), new Class[]{
   
   LoginService.class}, new ProxyInvocationHandler(new LoginServiceImpl()));
    System.out.println(loginService.getClass().toGenericString());
    return loginService;
}

3、Jdk动态代理使用步骤三使用代理对象

public static void main(String[] args) throws InterruptedException {
   
   
    //使用代理,和原对象一样的使用
    LoginService loginServiceProxy = createProxy();
    loginServiceProxy.login("周杰伦");
}

执行代理逻辑

image.png 上面完整演示了使用jdk动态代理过程。

image.png

Jdk动态代理使用起来还是很方便的,使用代理类和被代理类一样,生成代理类有两个重要的api,一个是InvocationHandler,一个是Proxy类

  • InvocationHandler接口比较简单,里面只有一个invoke方法,实现类需要处理代理逻辑。
  • 另外一个Proxy就是重点了,如果要弄明白代理到底是如何实现的,必须看看Proxy.newProxyInstance的源码。

image.png

Proxy.newProxyInstance的源码

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
    {
   
   
        //1、获取被代理类的接口
        final Class<?>[] intfs = interfaces.clone();
        //2、生成代理类
        Class<?> cl = getProxyClass0(loader, intfs);
        //3、获取构造函数
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        //4、代理逻辑类
        final InvocationHandler ih = h;
        //5、创建代理类的实例
        return cons.newInstance(new Object[]{
   
   h});   
    }

方法逻辑非常清晰,主要是通过java.lang.reflect.Proxy#getProxyClass0方法生成了一个类,这个就是代理类,但是我们看不到它生成的类具体是什么样子的, 在生成代理类的方法中,jdk开发者给我们留了一个开关,这个开关可以配置是否保存代理类的class文件。

生成代理class文件

//获取保存代理类class的开关
boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
   
   
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    final byte[] var4 = var3.generateClassFile();
    //开关打开了
    if (saveGeneratedFiles) {
   
   
        int var1 = var0.lastIndexOf(46);
        Path var2;
        //将代理类写到文件中
        Files.write(var2, var4, new OpenOption[0]);
        return null;
    }
    return var4;
}

那么我们可以配置系统参数sun.misc.ProxyGenerator.saveGeneratedFiles,打开这个开关

public static void main(String[] args) throws InterruptedException {
   
   

    //打开保存生成代理类开关
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
    LoginService loginServiceProxy = createProxy();
    loginServiceProxy.login("周杰伦");
}

这样运行我们的测试类后,会将代理类的class文件生成到com.sun.proxy包中: image.png

image.png

查看代理类class文件

public final class $Proxy0 extends Proxy implements LoginService {
   
   
    private static Method m1;
    private static Method m3;
    private static Method m2;
    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 void login(String var1) throws  {
   
   
        try {
   
   
            super.h.invoke(this, m3, 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 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"));
            m3 = Class.forName("cn.supfox.proxy.service.LoginService").getMethod("login", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
   
   
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
   
   
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

代理类总结

  1. 代理类被final修饰,类名称是$Proxy开头,数字结尾
  2. 代理类都继承了Proxy类
  3. 实现了被代理类的所有方法并且都是final修饰
  4. 实现的被代理类的方法,里面都是通过invocation.invoke方法触发方法调用,再看看咱们写的ProxyInvocationHandler类,这就能明白了代理类会通过invoke方法来代理具体的方法
//实现invoke方法,实现代理逻辑
class ProxyInvocationHandler implements InvocationHandler {
   
   

    //原对象-被代理对象
    private Object source;

    public ProxyInvocationHandler(Object source) {
   
   
        this.source = source;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
   
        System.out.println("代理类:"+ proxy.getClass() + "被代理类:" + source.getClass());
        System.out.println("代理开始执行方法=====" + method.getName());
        Object result = method.invoke(source, args);
        System.out.println("代理结束执行方法=====" + method.getName());
        return result;
    }
}

代理类思考总结

  1. jdk动态代理为啥只能代理接口不能代理普通class类?,因为代理对象已经实现了Proxy类,没法实现其他类了。
  2. 代理类通过持有ProxyInvocationHandler的引用,间接实现代理逻辑的功能。

image.png

相关文章
|
4月前
|
Android开发
AS错误:Duplicate class kotlin.xxx.jdk8.DurationConversionsJDK8Kt found in modules kotlin-stdlib-1.8.22
本文描述了Android Studio中遇到的"Duplicate class found in modules"错误的解决方法,通过在`app/build.gradle`文件中使用`constraints`来排除过时的kotlin-stdlib-jdk7和kotlin-stdlib-jdk8依赖,解决了依赖冲突问题。
284 1
|
4月前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
27天前
|
安全 Java 开发者
AOP中的JDK动态代理与CGLIB动态代理:深度解析与实战模拟
【11月更文挑战第21天】面向切面编程(AOP,Aspect-Oriented Programming)是一种编程范式,它通过将横切关注点(cross-cutting concerns)与业务逻辑分离,以提高代码的可维护性和可重用性。在Java开发中,AOP的实现离不开动态代理技术,其中JDK动态代理和CGLIB动态代理是两种常用的方式。本文将从背景、历史、功能点、业务场景、底层逻辑等多个维度,深度解析这两种代理方式的区别,并通过Java示例进行模拟和比较。
43 4
|
2月前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
31 0
[Java]静态代理与动态代理(基于JDK1.8)
|
2月前
|
Java
【编程进阶知识】静态代理、JDK动态代理及Cglib动态代理各自存在的缺点及代码示例
本文介绍了三种Java代理模式:静态代理、JDK动态代理和Cglib动态代理。静态代理针对特定接口或对象,需手动编码实现;JDK动态代理通过反射机制实现,适用于所有接口;Cglib动态代理则基于字节码技术,无需接口支持,但需引入外部库。每种方法各有优缺点,选择时应根据具体需求考虑。
23 1
|
4月前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
276 0
|
4月前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
201 0
|
3月前
|
Java
安装JDK18没有JRE环境的解决办法
安装JDK18没有JRE环境的解决办法
372 3
|
4月前
|
Oracle Java 关系型数据库
Mac安装JDK1.8
Mac安装JDK1.8
764 4
|
4月前
|
Java 关系型数据库 MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【8月更文挑战第19天】在Linux上搭建Java Web应用环境,需安装JDK 1.8、Tomcat及MariaDB。本指南详述了使用apt-get安装OpenJDK 1.8的方法,并验证其版本。接着下载与解压Tomcat至`/usr/local/`目录,并启动服务。最后,通过apt-get安装MariaDB,设置基本安全配置。完成这些步骤后,即可验证各组件的状态,为部署Java Web应用打下基础。
62 1