浅谈JAVA设计模式之代理模式

先来谈谈JDK动态代理

​ 需求背景:

​ 一电商项目的程序猿辛辛苦苦写完支付接口并测试成功且上线。这个时候项目经理跑过来提了一个需求,需要在客户支付之前记录客户支付日志,方便日后进行统计支付数据。

使用JDK动态代理解决上面的需求

为了不影响到之前写好的支付接口,不破坏接口的开闭原则。我们通过PayProxy绑定原先的支付接口,来解决这问题。
pay.java

public interface Pay {

    public void doPay();
}

AliPay.java

public class AliPay implements Pay{

    @Override
    public void doPay() {
        System.out.println("do AliPay");
    }
}

PayProxy.java

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class PayProxy implements InvocationHandler {

    private Object target;

    public Object bind(Object target){
        this.target = target;
        return Proxy.
                newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    public void before() {
        System.out.println("do something before pay");
    }

    public void after() {
        System.out.println("do something after pay");
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object result = method.invoke(target, args);
        after();
        return result;
    }
}

PayCenter.java

public class PayCenter {

    public static void main(String[] args) {
        Pay aliPay = (Pay)new PayProxy().bind(new AliPay());
        aliPay.doPay();
    }
}

输出

do something before pay
do AliPay
do something after pay

简单阐述JDK动态代理原理

思考:

​ 为什么现在的Pay接口调用的doPay()会打印before和after方法?现在Pay还是原先的Pay接口吗?

aliPay = {$Proxy0@528} "com.zjqx.proxy.jdk.AliPay@6979e8cb"

通过调试可以发现,现在的Pay已经不是我们原先创建的Pay接口了;而是 P r o x y 0 @ 528 这 个 对 象 ; 为 了 更 好 的 理 解 J D K 动 态 代 理 的 原 理 , 我 们 可 以 打 印 出 Proxy0@528这个对象;为了更好的理解JDK动态代理的原理,我们可以打印出 Proxy0@528JDKProxy0@528对象的反编译代码;

$Proxy0.class反编译后的文件

import com.sun.proxy.$Proxy0;
import java.lang.reflect.*;

public final class $Proxy extends Proxy
    implements $Proxy0
{

    public $Proxy(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }

    public final boolean equals(Object obj)
    {
        try
        {
            return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final InvocationHandler getInvocationHandler(Object obj)
        throws IllegalArgumentException
    {
        try
        {
            return (InvocationHandler)super.h.invoke(this, m5, new Object[] {
                obj
            });
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString()
    {
        try
        {
            return (String)super.h.invoke(this, m2, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final Class getProxyClass(ClassLoader classloader, Class aclass[])
        throws IllegalArgumentException
    {
        try
        {
            return (Class)super.h.invoke(this, m6, new Object[] {
                classloader, aclass
            });
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final Class getClass()
    {
        try
        {
            return (Class)super.h.invoke(this, m11, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void notifyAll()
    {
        try
        {
            super.h.invoke(this, m13, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode()
    {
        try
        {
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void wait()
        throws InterruptedException
    {
        try
        {
            super.h.invoke(this, m8, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void doPay()
    {
        try
        {
            super.h.invoke(this, m3, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void notify()
    {
        try
        {
            super.h.invoke(this, m12, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final Object newProxyInstance(ClassLoader classloader, Class aclass[], InvocationHandler invocationhandler)
        throws IllegalArgumentException
    {
        try
        {
            return (Object)super.h.invoke(this, m7, new Object[] {
                classloader, aclass, invocationhandler
            });
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void wait(long l)
        throws InterruptedException
    {
        try
        {
            super.h.invoke(this, m10, new Object[] {
                Long.valueOf(l)
            });
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final boolean isProxyClass(Class class1)
    {
        try
        {
            return ((Boolean)super.h.invoke(this, m4, new Object[] {
                class1
            })).booleanValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void wait(long l, int i)
        throws InterruptedException
    {
        try
        {
            super.h.invoke(this, m9, new Object[] {
                Long.valueOf(l), Integer.valueOf(i)
            });
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    private static Method m1;
    private static Method m5;
    private static Method m2;
    private static Method m6;
    private static Method m11;
    private static Method m13;
    private static Method m0;
    private static Method m8;
    private static Method m3;
    private static Method m12;
    private static Method m7;
    private static Method m10;
    private static Method m4;
    private static Method m9;

    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            m5 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getInvocationHandler", new Class[] {
                Class.forName("java.lang.Object")
            });
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m6 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getProxyClass", new Class[] {
                Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;")
            });
            m11 = Class.forName("com.sun.proxy.$Proxy0").getMethod("getClass", new Class[0]);
            m13 = Class.forName("com.sun.proxy.$Proxy0").getMethod("notifyAll", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m8 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[0]);
            m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("doPay", new Class[0]);
            m12 = Class.forName("com.sun.proxy.$Proxy0").getMethod("notify", new Class[0]);
            m7 = Class.forName("com.sun.proxy.$Proxy0").getMethod("newProxyInstance", new Class[] {
                Class.forName("java.lang.ClassLoader"), Class.forName("[Ljava.lang.Class;"), Class.forName("java.lang.reflect.InvocationHandler")
            });
            m10 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[] {
                Long.TYPE
            });
            m4 = Class.forName("com.sun.proxy.$Proxy0").getMethod("isProxyClass", new Class[] {
                Class.forName("java.lang.Class")
            });
            m9 = Class.forName("com.sun.proxy.$Proxy0").getMethod("wait", new Class[] {
                Long.TYPE, Integer.TYPE
            });
        }
        catch(NoSuchMethodException nosuchmethodexception)
        {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception)
        {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

提取关键代码

public $Proxy(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }

public final void doPay()
    {
        try
        {
            super.h.invoke(this, m3, null);
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

m3 = Class.forName("com.sun.proxy.$Proxy0").getMethod("doPay", new Class[0]);

通过上述的代码可以看出,$Proxy对象是JVM自动生成的对象,实现了Pay接口,并重写了接口里的所有方法;

​ 我们提取dopay方法可以看出,实际调用的是invocationhandler.invoke方法,这正是我们之前PayProxy类中的invoke方法,通过JDK的反射技术调用实际Object target即AliPay的dopay方法。

最后给出JDK动态代理简单的时序图(有点丑,见谅,哈哈哈)

在这里插入图片描述
JDK动态代理的简单总结:

  • JDK动态代理需要被代理类实现接口,才能实现动态代理;
  • JDK动态代理每次调用均是通过反射技术实现,在一定程度上影响性能;

CGLib动态代理

区别于JDK动态代理,CGLib动态代理的被代理类则不需要实现固定的接口;CGLib的动态代理会自动生成被代理类的子类,并重写父类的方法,从而实现代理;

实现代码

CglibProxy.java

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {

    public Object getInstance(Object target){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        //回调intercept方法
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
    }

    public void before(){
        System.out.println("Before");
    }

    public void after(){
        System.out.println("After");
    }
}

AliPay.java

public class AliPay {

    public void doPay(){
        System.out.println("alipay");
    }
}

CglibTest.java

import net.sf.cglib.core.DebuggingClassWriter;

public class CglibTest {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "G://cglib_folder");

        AliPay alipay = (AliPay)new CglibProxy().getInstance(new AliPay());
        alipay.doPay();
    }
}

输出

before
doPay
after

简单阐述CgLib动态代理原理

我们通过以下代码输出CgLib的class文件

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "G://cglib_folder");

进入G://cglib_folder会发现有三个类
在这里插入图片描述
通过调试我们发现:

alipay = {AliPay$$EnhancerByCGLIB$$e2467acf@671} "com.tomi.proxy.cglib.AliPay$$EnhancerByCGLIB$$e2467acf@3c0ecd4b"

AliPayEnhancerByCGLIBe2467acf.class文件正是Cglib动态获取的代理类,反编译该class文件;

public class AliPay$$EnhancerByCGLIB$$e2467acf extends AliPay
    implements Factory
{
    final void CGLIB$doPay$0()
    {
        super.doPay();
    }

    public final void doPay()
    {
        CGLIB$CALLBACK_0;
        if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
_L1:
        JVM INSTR pop ;
        CGLIB$BIND_CALLBACKS(this);
        CGLIB$CALLBACK_0;
_L2:
        JVM INSTR dup ;
        JVM INSTR ifnull 37;
           goto _L3 _L4
_L3:
        break MISSING_BLOCK_LABEL_21;
_L4:
        break MISSING_BLOCK_LABEL_37;
        this;
        CGLIB$doPay$0$Method;
        CGLIB$emptyArgs;
        CGLIB$doPay$0$Proxy;
        intercept();
        return;
        super.doPay();
        return;
    }
}

CgLib代理类继承了AliPay类,并重写了父类的所有方法,调用doPay方法时会调用CGLIB$doPay$0方法,该方法正是父类的doPay方法;接着调用了intercept()方法。

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object result = methodProxy.invokeSuper(o, objects);
        after();
        return result;
}

进入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();
        }
}

查看MethodProxy.FastClassInfo可以看到

private static class FastClassInfo {
        FastClass f1;
        FastClass f2;
        int i1;
        int i2;

        private FastClassInfo() {
        }
}

这里的f1和f2便是上述G://cglib_folder目录下的

 - AliPay__EnhancerByCGLIB__e2467acf__FastClassByCGLIB__9697d901.class(代理类的FastClass)
 - AliPay__FastClassByCGLIB__bf557141.class(被代理类的FastClass)

总结

 - CgLib动态代理类之所以执行效率比JDK动态代理类快,是因为其FastClass机制,直接通过生成的FastClass类执行对应的方法;
 - CgLib动态代理需要生成三个class类,所以比JDK动态代理生成时效率要慢;

最后附上一份手写JDK动态代理的git地址

https://github.com/TomiCat/DynamicProxyForMyself

此文章为本人平时学习整理巩固所写,如有错误,欢迎大家纠正~谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值