【设计模式系列8】深入分析代理模式(JDK动态代理和CGLIB动态代理)

设计模式系列总览

设计模式飞机票
三大工厂模式登机入口
策略模式登机入口
委派模式登机入口
模板方法模式登机入口
观察者模式登机入口
单例模式登机入口
原型模式登机入口
代理模式登机入口
装饰者模式登机入口
适配器模式登机入口
建造者模式登机入口
责任链模式登机入口
享元模式登机入口
组合模式登机入口
门面模式登机入口
桥接模式登机入口
中介者模式登机入口
迭代器模式登机入口
状态模式登机入口
解释器模式登机入口
备忘录模式登机入口
命令模式登机入口
访问者模式登机入口
软件设计7大原则和设计模式总结登机入口

什么是代理模式

代理模式(Proxy Pattern)是指为其他对象提供一种代理, 以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用。

代理模式属于结构型设计模式,属于GOF23种设计模式之一

代理模式可以分为静态代理和动态代理两种类型,而动态代理中又分为JDK动态代理和CGLIB代理两种。

静态代理

标准的静态代理模式需要定义一个接口,然后被代理对象与代理对象均需要重写接口方法,被代理对象本身只需要实现真正的业务逻辑,而代理对象中的方法需要调用被代理对象的方法,且可以在调用前后新增一些其他逻辑处理。
代理对象中需要显式声明被代理对象,也就是需要持有被代理对象的引用,一般通过代理对象的构造器传入被代理对象,以达到持有被代理对象的目的

示例

1、定义一个标准接口

package com.zwx.design.pattern.proxy;

public interface Travel {
    void buyTrainTickey();//买火车票
}

2、定义一个被代理对象实现接口Travel,并重写接口中的方法:

package com.zwx.design.pattern.proxy.staticProxy;

import com.zwx.design.pattern.proxy.Travel;

public class TravelPerson implements Travel{
    @Override
    public void buyTrainTickey() {
        System.out.println("北京到上海");
        System.out.println("早上9:00出发");
    }
}

3、定义一个代理对象实现接口Travel,并重写接口中的方法,此代理对象需要持有被代理对象的引用,可以通过构造器传入:

package com.zwx.design.pattern.proxy.staticProxy;

import com.zwx.design.pattern.proxy.Travel;

public class TravelAgency implements Travel{
    private TravelPerson travelPerson;//持有被代理对象

    public TravelAgency(TravelPerson travelPerson) {
        this.travelPerson = travelPerson;
    }

    @Override
    public void buyTrainTickey(){
        before();
        this.travelPerson.buyTrainTickey();//调用被代理对象的方法
        after();

    }
    private void before() {
        System.out.println("付定金");
    }

    private void after() {
        System.out.println("付尾款");
    }
}

4、建立测试类测试:

package com.zwx.design.pattern.proxy.staticProxy;

public class TestStaticProxy {
    public static void main(String[] args) {
        TravelAgency travelAgency = new TravelAgency(new TravelPerson());
        travelAgency.buyTrainTickey();
    }
}

输出结果为:

付定金
北京到上海
早上9:00出发
付尾款

静态代理的缺点

  • 代理对象需要显示的声明被代理对象,如果后期需要代理其他对象,只能通过修改代码的方式实现
  • 被代理对象如果新增了其他方法(接口新增了方法),那么代理对象也需要同步修改,不便于后期维护

为了解决静态代理的局限性,所以我们就需要有动态代理来动态分配代理对象。

JDK动态代理

JDK内置了一种动态代理的实现方式,主要有以下两个条件:

  • 定义的代理对象必须要实现java.lang.reflect.InvocationHandler接口
  • 被代理对象必须要显示的实现至少一个接口

示例

1、创建一个被代理对象JdkTravelPerson实现Travel接口:

package com.zwx.design.pattern.proxy.dynamicProxy.jdkProxy;

import com.zwx.design.pattern.proxy.Travel;

public class JdkTravelPerson implements Travel{
    @Override
    public void buyTrainTickey() {
        System.out.println("北京到上海");
        System.out.println("早上9:00出发");
    }
}

2、创建一个JdkTravelAgency代理类,实现InvocationHandler接口:

package com.zwx.design.pattern.proxy.dynamicProxy.jdkProxy;

import com.zwx.design.pattern.proxy.Travel;
import javafx.beans.binding.ObjectExpression;
import javax.sound.midi.Track;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class JdkTravelAgency implements InvocationHandler {
    private Object target;//被代理对象

    public Object getInstance(Object target){//动态获取代理对象
        this.target = target;
        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        Object obj = method.invoke(this.target,args);//调用代理对象方法
        after();
        return obj;
    }

    private void before() {
        System.out.println("付定金");
    }

    private void after() {
        System.out.println("付尾款");
    }
}

3、创建测试类:

package com.zwx.design.pattern.proxy.dynamicProxy.jdkProxy;

import com.zwx.design.pattern.proxy.Travel;

public class TestJdkProxy {
    public static void main(String[] args){
        Travel travel = (Travel) new JdkTravelAgency().getInstance(new JdkTravelPerson());
        travel.buyTrainTickey();
    }
}

运行结果:

付定金
北京到上海
早上9:00出发
付尾款

JDK动态代理原理分析

首先来看一下上面示例中的类图关系:
在这里插入图片描述
动态代理最核心的就是如何生成代理类,所以最核心的逻辑是在JdkTravelAgency 中的getInstance方法调用的Proxy.newProxyInstance方法。
我们先把上面示例中的代理类通过ProxyGenerator类提供的方法generateProxyClass,生成出来后反编译看一下代理类究竟是什么样子的一个类:

package com.zwx.design.pattern.proxy.dynamicProxy.jdkProxy;

import com.zwx.design.pattern.proxy.Travel;
import sun.misc.ProxyGenerator;

import java.io.FileOutputStream;
import java.io.IOException;

public class TestJdkProxy {
    public static void main(String[] args){
        try{
            byte[] bytes = ProxyGenerator.generateProxyClass("$proxy0",new Class[]{Travel.class});//生成代理类
            FileOutputStream out = new FileOutputStream("G:\\$proxy0.class");
            out.write(bytes);
            out.flush();
            out.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

然后将$proxy0反编译(反编译工具用的是jad,jd-gui反编译出来的有点小问题):

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 

import com.zwx.design.pattern.proxy.Travel;
import java.lang.reflect.*;

public final class $proxy0 extends Proxy
    implements Travel
{

    public $proxy0(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 String toString()
    {
        try
        {
            return (String)super.h.invoke(this, m2, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void buyTrainTickey()
    {
        try
        {
            super.h.invoke(this, m3, 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);
        }
    }

    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.zwx.design.pattern.proxy.Travel").getMethod("buyTrainTickey", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        }
        catch(NoSuchMethodException nosuchmethodexception)
        {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception)
        {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

我们可以看到,生成的代理类 $proxy0(原类为TravelPerson),自动帮我们继承了Proxy类,然后定义了m0-m4中的4个方法,其中,m0,m1,m4这三个方法是Object类提供的,而m3方法是我们自己定义的接口中的方法。

我们看到buyTrainTickey方法中,调用了super.h.invoke(this, m3, null)方法。

h是什么呢?h实际上是其父类Proxy中的持有的InvocationHandler对象:
在这里插入图片描述
而Proxy里面的h就是我们传进去的JdkTravelAgency对象,所以最终就是调用了我们自己的方法。
在这里插入图片描述

Proxy.newProxyInstance

 @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();//克隆一份接口对象
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);//获得当前代理类

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            //获得代理对象的构造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);

            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});//通过构造器初始化代理对象实例后返回
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

这个方法就是三步:

  1. 通过传入的接口,调用getProxyClass0方法获得当前代理类的Class文件
  2. 获得代理类的构造器
  3. 通过构造器,初始化当前代理类对象
    而这三步当中,最重要的就是第1步,通过上面的反编译文件我们知道,代理类已经被重写了,那么代理类是如何被重写的?

getProxyClass0

getProxyClass0方法获得代理类的字节码文件,会先去WeakCache缓存中获取,如果获取不到,则通过Proxy类中的内部工厂类ProxyClassFactory获取:

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";//代理类前缀

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();//通过原子类实现代理类计数

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * 验证类加载器是否可以将接口名称解析为相同的Class对象
                 */
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * 校验一下加载出来的Class对象是否是一个接口
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * 校验接口是否重复
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;// 代理类的包名
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;//访问标志

            /*
             * 记录非public类型接口的包,所以代理类将会被定义在同一个包下
             * 校验所有的非public代理接口是否在同一个包下
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {//如果不是public类型
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;//第一次循环
                    } else if (!pkg.equals(proxyPkg)) {//如果不在一个包下会报错
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {//默认包名
                // public static final String PROXY_PACKAGE = "com.sun.proxy";
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * 生成代理类名:包名+$proxy+num
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * 生成代理类的byte数组
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                //将byte数组通过类加载器加载成为Class字节码文件返回
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

通过上面的源码,可以得到JDK动态代理分为以下几步:

  1. 拿到被代理对象的引用,并且通过反射获取到它的所有的接口。
  2. 通过JDK Proxy类重新生成一个新的类,同时新的类要实现被代理类所实现的所有的接口。
  3. 动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用。
  4. 编译新生成的 Java 代码.class。
  5. 将新生成的Class文件重新加载到 JVM 中运行。

所以说JDK动态代理的核心是通过重写被代理对象所实现的接口中的方法来重新生成代理类来实现的,那么假如被代理对象没有实现接口呢?那么这时候就需要CGLIB动态代理了。

CGLIB动态代理

JDK动态代理是通过重写被代理对象实现的接口中的方法来实现,而CGLIB是通过继承被代理对象来实现,和JDK动态代理需要实现指定接口一样,CGLIB也要求代理对象必须要实现MethodInterceptor接口,并重写其唯一的方法intercept。

注意:因为CGLIB是通过继承目标类来重写其方法来实现的,故而如果是final和private方法则无法被重写,也就是无法被代理。

示例

1、引入依赖:

  <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>2.2</version>
        </dependency>

2、创建一个被代理对象(不需要实现接口):

package com.zwx.design.pattern.proxy.dynamicProxy.cglibProxy;

public class CglibTravelPerson{
    public void buyTrainTickey() {
        System.out.println("cglib:北京到上海");
        System.out.println("cglib:早上9:00出发");
    }
}

3、建立一个代理对象实现MethodInterceptor接口:

package com.zwx.design.pattern.proxy.dynamicProxy.cglibProxy;

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 CglibTravelAgency implements MethodInterceptor {

    public Object getInstance(Class<?> clazz){
        Enhancer enhancer = new Enhancer();//相当于JDK动态代理中的Proxy类
        enhancer.setSuperclass(clazz);//设置为即将生成的代理类的父类
        enhancer.setCallback(this);//设置回调对象
        return enhancer.create();//相当于JDK动态代理的Proxy.newProxyInstance方法,生成新的字节码文件,并加载到JVM中
    }

 	/**
     *
     * @param o - CBLIG生成的代理对象
     * @param method - 被代理对象中被拦截的方法
     * @param objects - 方法中的参数
     * @param methodProxy - 代理对象中对应的方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        before();
        Object obj = methodProxy.invokeSuper(o,objects);
        after();
        return obj;
    }

    private void before() {
        System.out.println("付定金");
    }

    private void after() {
        System.out.println("付尾款");
    }
}

4、创建测试类测试

package com.zwx.design.pattern.proxy.dynamicProxy.cglibProxy;

public class TestCglibProxy {
    public static void main(String[] args){
        CglibTravelPerson cglibTravelPerson = (CglibTravelPerson) new CglibTravelAgency().getInstance(CglibTravelPerson.class);
        cglibTravelPerson.buyTrainTickey();
    }
}

5、输出结果如下:

付定金
cglib:北京到上海
cglib:早上9:00出发
付尾款

CGLIB动态代理实现原理分析

我们先通过如下方法将代理类生成出来:

package com.zwx.design.pattern.proxy.dynamicProxy.cglibProxy;

import net.sf.cglib.core.DebuggingClassWriter;

public class TestCglibProxy {
    public static void main(String[] args){
     	System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"G:\\");
    }
}

生成之后发现有三个class文件(CGLIB底层通过asm框架来生成字节码文件):
在这里插入图片描述
通过反编译之后,发现CglibTravelPerson$$EnhancerByCGLIB$$c590ba4f类继承了CglibTravelPerson,所以这个类就是新生成的代理类,里面重写了我们被代理对象中的所有方法,并且每一个方法都有一个MethodProxy与之对应(因为类比较长,所以就只选取片段过来说明,感兴趣的可以自己生成出来看一下):
在这里插入图片描述
另外两个带有FastClass的类:CglibTravelPerson$$FastClassByCGLIB$$629469e0是被代理对象CglibTravelPerson对应的FastClass,另一个也就是类名最长的那个就是新生成的代理对象所对应的FastClass。

CGLib动态代理采用了FastClass机制,其分别为代理类和被代理类各生成一个FastClass,这个FastClass类会为代理类或被代理类的方法分配一个 index(int类型)。这个index当做一个入参,FastClass 就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比 JDK 动态代理通过反射调用更高。

但是我们看上面的源码也可以明显看到,JDK动态代理只生成一个文件,而CGLIB生成了三个文件,所以生成代理对象的过程会更复杂。

JDK和CGLib动态代理对比

  1. JDK 动态代理是实现了被代理对象所实现的接口,CGLib是继承了被代理对象。
  2. JDK和CGLib 都是在运行期生成字节码,JDK是直接写Class字节码,CGLib 使用 ASM 框架写Class字节码,Cglib代理实现更复杂,生成代理类的效率比JDK代理低。
  3. JDK 调用代理方法,是通过反射机制调用,CGLib 是通过FastClass机制直接调用方法,CGLib 执行效率更高。

静态代理和动态的本质区别

  • 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。
  • 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
  • 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成, 无需修改代理类的代码。

代理模式的优点

  • 代理模式能将代理对象与真实被调用的目标对象分离。
  • 一定程度上降低了系统的耦合度,扩展性好。
  • 可以起到保护目标对象的作用。
  • 可以对目标对象的功能增强。

代理模式的缺点

  • 代理模式会造成系统设计中类的数量增加。
  • 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

双子孤狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值