java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java动态代理

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

作者: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动态代理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文