Java中的动态代理原理及实现

 更新时间:2023年12月20日 09:30:03   作者:Brain_L  
这篇文章主要介绍了Java中的动态代理原理及实现,动态是相对于静态而言,何为静态,即编码时手动编写代理类、委托类,而动态呢,是不编写具体实现类,等到使用时,动态创建一个来实现代理的目的,需要的朋友可以参考下

前言

动态是相对于静态而言,何为静态,即编码时手动编写代理类、委托类。而动态呢,是不编写具体实现类,等到使用时,动态创建一个来实现代理的目的。

为什么有了静态代理还需要动态代理呢?静态代理毕竟是你手动编码的,如果需要对很多个方法进行一些公共处理(比如耗时,日志等),你需要在每个方法处修改代码,而且逻辑上都是相通的,为什么不能抽取出来呢。如果使用动态代理的话,你只需要指定规则,那么动态代理就可以根据你指定的规则进行处理。

本文主要研究动态代理的两种实现方式:JDK动态代理和CGLib动态代理

一、JDK动态代理

JDK动态代理的核心是JDK提供的Proxy类和Invocation接口,基于接口

看个例子

1、公共接口

public interface Hello {
    void sayHi(String name);
}

2、委托类

@Slf4j
public class HelloImpl implements Hello {
    @Override
    public void sayHi(String name) {
        log.info("hello, {}", name);
    }
}

3、实现InvocationHandler接口

@Slf4j
public class HelloInvocationHandler<T> implements InvocationHandler {
    private T target;
    public HelloInvocationHandler(T target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("执行了{}方法", method.getName());
        method.invoke(target, args);
        return null;
    }
}

为什么实现这个接口呢,主要是实现其invoke方法,该方法有三个参数

proxy——动态代理实例

method——被调用的方法

args——方法入参,如果是无参方法,则为null

在invoke里,我们就可以对方法进行一些特殊处理,这里只做了一个简单的演示,在执行委托类的方法之前,打印一行日志。实际可以在方法前、方法后、方法异常等等场景进行想要的处理。

4、创建代理类

@Slf4j
public class ProxyTest {
    public static void main(String[] args) {
        Hello hello = new HelloImpl();
        InvocationHandler handler = new HelloInvocationHandler<>(hello);
        Hello helloProxy = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), new Class<?>[]{Hello.class}, handler);
        helloProxy.sayHi("proxy");
    }
}
//输出
执行了sayHi方法
hello, proxy

根据3创建一个调用处理器handler,通过Proxy的newProxyInstance方法生成代理类的实例

入参分别为:classLoader,要代理的接口列表,调用分发器handler。

通过该代理实例调用方法,将会回调hanlder中的invoke,从而达到代理的目的。

上述例子是直接调用newProxyInstance来生成代理实例,还有一种方法是先生成代理类,然后再构造代理实例

@Slf4j
public class ProxyTest {
    public static void main(String[] args) throws Exception {
        Hello hello = new HelloImpl();
        InvocationHandler handler = new HelloInvocationHandler<>(hello);
//        Hello helloProxy = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), new Class<?>[]{Hello.class}, handler);
//        helloProxy.sayHi("proxy");
        Class<?> proxyClass = Proxy.getProxyClass(Hello.class.getClassLoader(), Hello.class);
        log.info("name:{}", proxyClass.getName());
        Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
        Hello helloInstance = (Hello) constructor.newInstance(handler);
        helloInstance.sayHi("proxy");
    }
}

//输出
name:com.sun.proxy.$Proxy0
执行了sayHi方法
hello, proxy

生成的代理类名称为$Proxy0,0为Proxy递增生成的编号,如果有多个代理类,则名称从$Proxy1依次类推。将生成的代理类proxyClass保存下来(默认保存到内存中,并不会保存成文件,此处只是为了研究),命名为Hello$Proxy0.class,打开看下

public final class Hello$Proxy0 extends Proxy implements Hello {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;
    public Hello$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 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 void sayHi(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 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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.brain.demo.aop.Hello").getMethod("sayHi", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

代理类继承Proxy,实现了Hello接口。static代码块中,通过反射拿到接口中的方法(本例中为Hello中的sayHi方法),Object中的equals、toString、hashCode方法。

对这些方法分别进行代理,具体表现为通过代理实例调用方法时,回调InvocationHandler实例中的invoke方法,即3中所述

至此,JDK动态代理的流程就清楚了。Proxy生成代理类,持有InvocationHandler实例,而InvocationHandler实例又持有委托类实例。通过代理类实例调用接口方法,由InvocationHandler实例拦截,进行相应处理后再调用真正的实现方法。

二、CGLib动态代理

CGLib基于继承,通过继承代理类覆盖其中的方法来实现代理的功能。

1、委托类

@Slf4j
public class Teacher {
    public void sayHi() {
        log.info("大家好");
    }
}

2、方法拦截器

@Slf4j
public class CglibMethodInterceptor implements MethodInterceptor {
    public Object CglibProxyGeneratory(Class target) {
        // 创建加强器,用来创建动态代理类
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target);
        enhancer.setCallback(this);
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        log.info("调用了方法:{}", method.getName());
        methodProxy.invokeSuper(o, objects);
        return null;
    }
}

3、生成代理类实例

Teacher teacher = (Teacher) new CglibMethodInterceptor().CglibProxyGeneratory(Teacher.class);
        teacher.sayHi();

//输出
调用了方法:sayHi
大家好

将生成的委托类保存下来,发现会有三个class文件生成。

Teacher$$FastClassByCGLIB$$4e4ecf50(委托类fastclass)
Teacher$$EnhancerByCGLIB$$332f7724(代理类)
Teacher$$EnhancerByCGLIB$$332f7724$$FastClassByCGLIB$$3b18b46c(代理类fastclass)

看下生成的代理类

public class Teacher$$EnhancerByCGLIB$$332f7724 extends Teacher implements Factory {
    private boolean CGLIB$BOUND;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$sayHi$0$Method;
    private static final MethodProxy CGLIB$sayHi$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$finalize$1$Method;
    private static final MethodProxy CGLIB$finalize$1$Proxy;
    private static final Method CGLIB$equals$2$Method;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final Method CGLIB$toString$3$Method;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final Method CGLIB$hashCode$4$Method;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final Method CGLIB$clone$5$Method;
    private static final MethodProxy CGLIB$clone$5$Proxy;
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("com.brain.demo.aop.Teacher$$EnhancerByCGLIB$$332f7724");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$finalize$1$Method = var10000[0];
        CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
        CGLIB$equals$2$Method = var10000[1];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[2];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[3];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[4];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        CGLIB$sayHi$0$Method = ReflectUtils.findMethods(new String[]{"sayHi", "()V"}, (var1 = Class.forName("com.brain.demo.aop.Teacher")).getDeclaredMethods())[0];
        CGLIB$sayHi$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHi", "CGLIB$sayHi$0");
    }
    final void CGLIB$sayHi$0() {
        super.sayHi();
    }
    public final void sayHi() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        if (var10000 != null) {
            var10000.intercept(this, CGLIB$sayHi$0$Method, CGLIB$emptyArgs, CGLIB$sayHi$0$Proxy);
        } else {
            super.sayHi();
        }
    }
    public static MethodProxy CGLIB$findMethodProxy(Signature var0) {
        String var10000 = var0.toString();
        switch(var10000.hashCode()) {
        case -2012941911:
            if (var10000.equals("sayHi()V")) {
                return CGLIB$sayHi$0$Proxy;
            }
            break;
        case -1574182249:
            if (var10000.equals("finalize()V")) {
                return CGLIB$finalize$1$Proxy;
            }
            break;
        case -508378822:
            if (var10000.equals("clone()Ljava/lang/Object;")) {
                return CGLIB$clone$5$Proxy;
            }
            break;
        case 1826985398:
            if (var10000.equals("equals(Ljava/lang/Object;)Z")) {
                return CGLIB$equals$2$Proxy;
            }
            break;
        case 1913648695:
            if (var10000.equals("toString()Ljava/lang/String;")) {
                return CGLIB$toString$3$Proxy;
            }
            break;
        case 1984935277:
            if (var10000.equals("hashCode()I")) {
                return CGLIB$hashCode$4$Proxy;
            }
        }
        return null;
    }
    public Teacher$$EnhancerByCGLIB$$332f7724() {
        CGLIB$BIND_CALLBACKS(this);
    }
    private static final void CGLIB$BIND_CALLBACKS(Object var0) {
        Teacher$$EnhancerByCGLIB$$332f7724 var1 = (Teacher$$EnhancerByCGLIB$$332f7724)var0;
        if (!var1.CGLIB$BOUND) {
            var1.CGLIB$BOUND = true;
            Object var10000 = CGLIB$THREAD_CALLBACKS.get();
            if (var10000 == null) {
                var10000 = CGLIB$STATIC_CALLBACKS;
                if (var10000 == null) {
                    return;
                }
            }
            var1.CGLIB$CALLBACK_0 = (MethodInterceptor)((Callback[])var10000)[0];
        }
    }
    static {
        CGLIB$STATICHOOK1();
    }
}

初始化时,获得Object的finalize、equals、toString、hashCode、clone以及代理类Teacher的sayHi的方法和方法代理。

看下代理类中的sayHi方法,首先获取MethodInterceptor实例,如果创建时为空,则调用super.sayHi(),即代理类中的方法;如果不为空,进行方法拦截,调用interceptor即2中所示。

类中还有一个sayHi方法,即CGLIB$sayHi$0,里面就是单纯的调用Teacher中的sayHi。那么这个方法是干嘛的呢?看下初始化的最后有这一句

CGLIB$sayHi$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHi", "CGLIB$sayHi$0");

它创建了一个方法代理,并指明了Teacher的sayHi的代理方法是CGLIB$sayHi$0。调用sayHi并进行方法拦截时,会将CGLIB$sayHi$0$Proxy做为入参传入。在2的interceptor中,调用methodProxy.invokeSuper完成方法调用。

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f2.invoke(fci.i2, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        }
    }
//DCL单例模式
private void init() {
        if (this.fastClassInfo == null) {
            synchronized(this.initLock) {
                if (this.fastClassInfo == null) {
                    MethodProxy.CreateInfo ci = this.createInfo;
                    MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                    fci.f1 = helper(ci, ci.c1);
                    fci.f2 = helper(ci, ci.c2);
                    fci.i1 = fci.f1.getIndex(this.sig1);
                    fci.i2 = fci.f2.getIndex(this.sig2);
                    this.fastClassInfo = fci;
                    this.createInfo = null;
                }
            }
        }
    }

init方法通过DCL加载fastClassInfo,其中fci.f1为Teacher FastClass,fci.f2为Teacher Enhancer FastClass,也即前面提到的生成三个class文件的另外两个。

getIndex是根据方法签名的hashCode给出的索引值。init完成之后,通过调用Teacher Enhancer FastClass中的invoke方法,将刚才计算的索引值和入参传入,这里根据索引值查到要调用代理类的CGLIB$sayHi$0,最终调用了Teacher当中的sayHi。这一套流程下来才算完成。

MethodInterceptor中的invoke和invokeSuper流程上一致,只是它调用的是Teacher FastClass中的invoke方法,然后调用sayHi。整个过程比JDK动态代理要绕,画个图总结下

cglib是基于继承的,所以委托类中的static、private、final方法因为无法继承所以无法代理

三、总结

JDK动态代理基于接口,如果委托类没有实现接口或者有自定义方法,则无法完成代理。CGLib基于继承,不受接口的限制,但是不能代理static、private、final方法。

JDK动态代理是通过反射完成方法调用,比较消耗性能。CGLib通过建立方法索引,不会有反射带来的性能问题。

JDK动态代理只会生成一个代理类。CGLib会生成三个代理类。

两者都可以用来实现AOP。Spring中两者均有使用。

到此这篇关于Java中的动态代理原理及实现的文章就介绍到这了,更多相关Java动态代理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • SpringBoot自动配置原理及案例源码解析

    SpringBoot自动配置原理及案例源码解析

    这篇文章主要为大家介绍了SpringBoot自动配置原理及自动配置案例源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-11-11
  • jpa使用uuid策略后无法手动设置id的问题及解决

    jpa使用uuid策略后无法手动设置id的问题及解决

    这篇文章主要介绍了jpa使用uuid策略后无法手动设置id的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08
  • Java中的自旋锁spinlock详解

    Java中的自旋锁spinlock详解

    这篇文章主要介绍了Java中的自旋锁spinlock详解,自旋锁就是循环尝试获取锁,不会放弃CPU时间片,减伤cup上下文切换,缺点是循环会消耗cpu,需要的朋友可以参考下
    2024-01-01
  • Java 数据结构与算法系列精讲之时间复杂度与空间复杂度

    Java 数据结构与算法系列精讲之时间复杂度与空间复杂度

    对于一个算法,其时间复杂度和空间复杂度往往是相互影响的,当追求一个较好的时间复杂度时,可能会使空间复杂度的性能变差,即可能导致占用较多的存储空间,这篇文章主要给大家介绍了关于Java时间复杂度、空间复杂度的相关资料,需要的朋友可以参考下
    2022-02-02
  • 谈谈Java 线程池

    谈谈Java 线程池

    这篇文章主要介绍了Java 线程池的相关资料,帮助大家更好的理解和学习Java,感兴趣的朋友可以了解下
    2020-08-08
  • Mybatis中${param}与#{param}的区别说明

    Mybatis中${param}与#{param}的区别说明

    这篇文章主要介绍了Mybatis中${param}与#{param}的区别说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • JAVA数组练习题实例讲解

    JAVA数组练习题实例讲解

    这篇文章主要给大家介绍了关于JAVA数组练习题的相关资料,这是个人总结的一些关于java数组的练习题,文中通过代码实例介绍的非常详细,需要的朋友可以参考下
    2023-08-08
  • 详解Java中Comparable和Comparator接口的区别

    详解Java中Comparable和Comparator接口的区别

    这篇文章主要介绍了详解Java中Comparable和Comparator接口的区别的相关资料,希望通过本文大家能彻底掌握这部分内容,需要的朋友可以参考下
    2017-09-09
  • java中对字符串每个字符统计的方法

    java中对字符串每个字符统计的方法

    java中对字符串每个字符统计的方法,需要的朋友可以参考一下
    2013-03-03
  • JMeter自定义日志与日志分析的实现

    JMeter自定义日志与日志分析的实现

    JMeter与Java程序一样,会记录事件日志,本文就介绍一下JMeter自定义日志与日志分析的实现,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12

最新评论