JDK动态代理原理浅析(通俗易懂版)

众所周知,静态代理模式就是通过代理类和目标类实现相同的接口,代理类持有目标类的对象,然后在代理类实现的方法中通过在调用目标类的方法前后执行增强代码来实现。而JDK动态代理并没有手动创建代理类,原理是JVM在运行时动态创建了代理类,先上代码:

// 接口
public interface SayInterface {
    void sayHello(String name);

    void sayBye(String name);
}

// 实现
public class SayInterfaceImpl implements SayInterface {

    public SayInterfaceImpl() {
    }

    @Override
    public void sayHello(String name) {
        System.out.println(name);
    }

    @Override
    public void sayBye(String name) {
        System.out.println(name);
    }
}

那么接下来如何生成代理对象呢,有请Proxy登场

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

这个方法负责生成代理对象,接受三个参数,类加载器ClassLoader,接口的Class数组以及一个InvocationHandler的实现类,对于InvocationHandler接口

 public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;

InvocationHandler接口很简单,只接受三个参数,代理对象,目标接口的Method, 以及接口的入参,代理类的增强方法都要交给这个接口的实现类来处理,接下来写InvocationHandler的实现

public class InvocationHandlerImpl implements InvocationHandler {
    private SayInterfaceImpl target = new SayInterfaceImpl();;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 增强方法
        System.out.println("enhance");
        // 通过反射调用被代理对象的方法
        return method.invoke(target, args);
    }
}

InvocationHandlerImpl 持有一个目标类的实例,这是因为通过反射调用目标method的invoke方法时需要传入该实例(注意区分两个invoke方法, 一个是InvocationHandler接口定义的,一个是jdk反射包中Method的方法) 。对于InvocationHandlerImpl 的invoke方法,由于实例中没有用到代理对象的方法,因此这里不使用proxy参数,重点是method以及args。我们先执行增强方法,然后通过调用目标对象的方法来实现代理功能。注意:这里通常要通过 InvocationHandlerImpl 隐藏目标对象,提供对对象的间接访问,这也是代理模式和装饰者模式的主要区别之一。客户端调用如下:

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

        InvocationHandlerImpl invocationHandler = new InvocationHandlerImpl();

        // 代理对象
        SayInterface sayInterfaceProxy = (SayInterface) Proxy.newProxyInstance(SayInterfaceImpl.class.getClassLoader(), new Class[]{SayInterface.class}, invocationHandler);
        sayInterfaceProxy.sayHello("Hello");
        sayInterfaceProxy.sayBye("Bye");
    }

结果如下,确实实现了代理的功能。

这里你肯定要问了,invoke的参数是怎么生成的?InvocationHandlerImpl 的invoke方法又是什么时候被调用的呢?好戏登场,我们通过阿里巴巴的Arthas工具获取动态代理类的代码如下,为了方法观察,这里移除了toString和equals以及hashCode方法

/*
 * Decompiled with CFR.
 *
 * Could not load the following classes:
 *  bp.learn.SayInterface
 */
package com.sun.proxy;

import bp.learn.SayInterface;
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 SayInterface {
    private static Method m3;
    private static Method m4;

	 static {
        try {
            // 这里使用反射获取接口对应的Method对象
            m4 = Class.forName("bp.learn.SayInterface").getMethod("sayHello", Class.forName("java.lang.String"));
            m3 = Class.forName("bp.learn.SayInterface").getMethod("sayBye", Class.forName("java.lang.String"));
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

    public $Proxy0(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }

    public final void sayHello(String string) {
        try {
            // 实际调用的是invocationHandler的invoke方法
            // 三个参数分别是代理对象自身,接口的Method对象,接口入参
            this.h.invoke(this, m4, new Object[]{string});
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void sayBye(String string) {
        try {
            this.h.invoke(this, m3, new Object[]{string});
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    } 
}

代理对象的类型是JVM运行时动态生成的代理类$Proxy0,可以看到$Proxy0继承了Proxy并实现了SayInterface接口,通过构造器传入了invocationHandler,在代理对象调用sayHello方法时实际是调用了invocationHandler的invoke方法,而方法参数正是代理类自身、代理类通过反射获取目标接口的Method对象,以及接口参数这三个值。是不是豁然开朗了呢

总结一下:

代理类是运行时创建的,代理类可以获得接口方法的Method,但是对于具体如何增强,需要在编写代码时交给InvocationHandler来定义,而代理类只需要传入自身对象以及通过反射获得的接口的Method对象以及接口参数就可以了。本质上和静态代理很相似,只不过代理类是运行时创建的,而定义的增强方法也是通过调用InvocationHandler的invoke方法来实现的。与静态代理相比,动态代理只需要指定增强方法,运行时会应用于所有的目标类实现的接口方法上(如果需要指定方法增强,可以在invoke方法中判断method的name来指定增强的方法),这样减少了代码量,不用像静态代理一样需要重写每个方法,并且后续如果目标类要增加接口也无需更改代码,符合开闭原则

最后

提下CGLIB动态代理,需要依赖CGLIB的jar包,原理是通过ASM框架动态生成目标类的子类,适用于目标方法没有实现接口的情况,至于性能问题,JDK1.8以后JDK动态代理的性能高于CGLIB动态代理,原则上有接口的类都可以用JDK动态代理来实现

https://link.csdn.net/?target=https%3A%2F%2Frefactoring.guru%2Fdesign-patterns%2Fflyweight
设计模式总结文章(一边学习一边更新中)
建造者模式和工厂模式的区别-CSDN博客

代理模式和装饰器模式的区别-CSDN博客

JDK动态代理原理浅析(通俗易懂版)-CSDN博客

一个例子读懂享元模式-CSDN博客

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值