代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)

代理模式

    代理模式是设计模式之一,为一个对象提供一个替身或者占位符以控制对这个对象的访问,它给目标对象提供一个代理对象,由代理对象控制对目标对象的访问。

那么为什么要使用代理模式呢?

1、隔离,客户端类不能或者不想直接访问目标对象,代理类可以在远程客户端类和目标类之间充当中介。

2.代理类可以对业务或者一些消息进行预处理,做一些过滤,然后再将消息转给目标类,主要处理逻辑还是在目标类,符合开闭原则。

    在我们生活中有很多体现代理模式的例子,如中介、媒婆、经纪人等等,比如说某个球队要签约一个球星,就需要和经纪人进行沟通,在一些编程框架中,也有很多地方使用代理模式,如Spring的AOP,java的RMI远程调用框架等。

   代理模式分为静态代理和动态代理,动态代理又分为jdk动态代理和cglib动态代理,下面分别来阐述下这几种代理模式的区别。

静态代理

    静态代理在使用的时候,需要定义一个接口或者父类,代理类和目标类(被代理类)都需要实现这个接口,代理类持有目标类的引用。我们以球员签约为例,湖人想要签下安东尼戴维斯,安东尼说,先和我的经纪人商讨签约情况,商谈成功之后再来找我签约。我们定义一个会谈的接口,这个接口提供一个签约的方法,再定义一个经纪人类和球员类,分别实现会谈接口的签约方法。经纪人和湖人说,想要签下戴维斯也可以,不过我们需要交易否决权,且最后一年是球员选项。湖人的魔术师想了想,詹姆斯巅峰期的尾巴也没几年了,反正这几年垃圾合同也签了不少,不在乎这一个,而且戴维斯正值巅峰,联盟前十球员,不算太亏,就同意了,毕竟还是总冠军重要。所以,经纪人在正式签约前,谈妥了戴维斯的球员选项和薪水,剩下的就需要戴维斯自己亲自签约了。然后经纪人就拉着戴维斯来签约了。(写博客的期间,魔术师辞职了,我........,算了,懒得改了)

/**
 * 会谈接口,有一个签约的方法
 */
public interface Talk {

    public void sign();
}

/**
 * 球员安东尼戴维斯,实现了签约的方法,需要本人亲自签约
 */
public class Davis implements Talk {
    @Override
    public void sign() {
        System.out.println("签约了,5年2.25亿美元");
    }
}

/**
 * 经纪人,也实现了签约的方法,持有球员的引用,但是具体签约流程还是必须由球员完成
 */
public class Broker implements Talk {
    private Davis davis;
    public Broker(Davis davis){
        this.davis = davis;
    }
    @Override
    public void sign() {
        System.out.println("我们拥有最后一年的球员选项");
        davis.sign();
        System.out.println("签约成功,交易否决权开始生效");


    }
}

//**
 * 测试类,只需要调用经纪人的签约方法就可以了
 */
public class StaticProxyTest {

    public static void main(String[] args) {

        Davis davis = new Davis();
        Broker broker = new Broker(davis);
        broker.sign();

    }
}
控制台打印:
我们拥有最后一年的球员选项
签约了,5年2.25亿美元
签约成功,交易否决权开始生效

   通过以上代码,我们发现,代理对象需要与目标对象实现一样的接口,当需要代理的对象很多的时候,就需要增加很多的类,假如代理接口需要新增一个方法,那么代理类和目标类都需要修改维护,那么有没有更好的解决方式呢?

动态代理

一、jdk动态代理

  静态代理的代理类是我们在编译期就已经创建好了,而动态代理是是指代理类是程序在运行过程中创建的。jdk的动态代理是是基于接口的代理。

第一步:还是定义一个会谈接口:

/**
 * 会谈接口,有一个签约的方法
 */
public interface Talk {

    public void sign();
}

第二步:定义一个球员,需要实现真正的签约方法

/**
 * 球员安东尼戴维斯,实现了签约的方法,需要本人亲自签约
 */
public class Davis implements Talk {
    @Override
    public void sign() {
        System.out.println("签约了,5年2.25亿美元");
    }
}

第三步:定义一个实现了InvocationHandler接口的实现类,该类需要绑定目标类。

public class MyInvocationHandler implements InvocationHandler {

    //目标类(被代理的类)
    private Object target;
    
    public MyInvocationHandler(Object target){
        this.target = target;
    }
    /**
     *
     * @param proxy  生成的代理类的实例
     * @param method 被调用的方法对象
     * @param args   调用method方法时传的参数
     * @return       method方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object object = null;
        System.out.println("我们拥有最后一年的球员选项");
        //执行方法,相当于执行了Davis中的sign()方法,
        //当代理对象调用其方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
        object = method.invoke(target,args);
        System.out.println("签约成功,交易否决权开始生效");
        return object;
    }
}

第四步:编写测试类:

public class DynamicProxyTest {
    public static void main(String[] args) {
        //被代理的对象
        Talk talk = new Davis();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(talk);
        //传入代理对象的字节码文件和接口类型,让Proxy来生成代理类
        Talk davisProxy = (Talk)Proxy.newProxyInstance(talk.getClass().getClassLoader(),
                talk.getClass().getInterfaces(),myInvocationHandler);
        //调用签约方法
        davisProxy.sign();
    }
}
控制台打印:

我们拥有最后一年的球员选项
签约了,5年2.25亿美元
签约成功,交易否决权开始生效

  可以看到控制台和静态代理打印的一模一样。下面来分析一下jdk动态代理的原理:

    我们看到,客户端通过Proxy的静态方法newProxyInstance生成了代理类,该方法有三个参数,分别是代理类的类加载器,这个代理类需要实现的接口以及一个处理器InvocationHandler,当我们使用代理类调用sign()方法的时候,是怎么执行这个方法的前置操作和后置操作,从代码来看,我们并没有显示的调用MyInvocationHandler的invoke方法,但是这个方法确实被执行了,究竟是哪里调用的呢?我们来看一下newProxyInstance的源码:

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

源码有一些检验判断,我们暂且忽略,重点是看怎么创建代理类以及代理类调用方法的时候如何调用的invoke方法的

重点剖析这行代码:

Class cl = getProxyClass0(loader, intfs);

这个方法根据传入的类加载器和接口类型生成了一个类,这个类即是代理类。点进去:

 private static Class getProxyClass0(ClassLoader loader,
                                           Class... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

英文注释写到:如果缓存中有代理类了直接返回,否则将由ProxyClassFactory创建代理类。我们再来看一下ProxyClassFactory:

private static final class ProxyClassFactory
        implements BiFunction[], Class>
    {
        // 所有代理类的前缀名都以$Proxy开头
        private static final String proxyClassNamePrefix = "$Proxy";

        // 下一个用于生成唯一代理类名的数字
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

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

            Map, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class intf : interfaces) {
                /*
                 
                 */
                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;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * 记录非公共代理接口的包,以便代理类将在同一个包中定义
             * 验证所有非公共代理接口都在同一个包中
             */
            for (Class intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    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) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * 为代理类选择一个全限定类名
             */
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * 生成代理类的字节码文件
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

我们看到,最终调用了ProxyGenerator的generateProxyClass方法生成字节码文件。回到newProxyInstance方法中,我们看看这几行代码:

    //代理类构造函数的参数类型
1、private static final Class[] constructorParams =
        { InvocationHandler.class };

2、final Constructor cons = cl.getConstructor(constructorParams);

3、 cons.newInstance(new Object[]{h});

    代理类实例化的代码在第三行,通过反射调用代理类对象的构造方法,选择了这个InvocationHandler为参数的构造方法,这个h就是我们传递过来的实现了InvocationHandler的实例。所以,我们猜测是生成的代理类持有我们前文定义的MyInvocationHandler实例,并调用里面的invoke方法。所以,我们通过反编译来看下生成的代理类的源码。
使用IDEA,在VM options一栏中输入:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,就会在项目中生成一个代理类:

代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理)_第1张图片

 

package com.sun.proxy;

import chenhuan.designpattern.proxy.staticproxy.Talk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

//该类继承了Proxy类,实现了Talk接口
public final class $Proxy0 extends Proxy implements Talk {

//声明了一些Method变量,后面会用到
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

//代理类的构造方法调用父类的构造方法
    public $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);
        }
    }
  //实现sign()方法,注意传入的是m3,
    public final void sign() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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");
            //加载Talk接口,并获取其sign方法
            m3 = Class.forName("chenhuan.designpattern.proxy.staticproxy.Talk").getMethod("sign");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

至此,我们发现,代理类通过调super.h.invoke(this, m2, (Object[])null);执行我们实现的InvocationHandler接口的invoke方法。复盘下jdk动态代理的实现原理的几大步骤:

1、新建一个接口

2、为接口实现一个代理类

3、创建一个实现了InvocationHandler接口的处理器

4、通过Proxy的静态方法,根据类加载器,实现的接口,以及InvocationHandler处理器生成一个代理类

  ①为接口创建代理类的字节码文件

  ②使用ClassLoader将字节码文件加载到JVM

  ③创建代理类实例对象,执行对象的目标方法

我们看到,使用JDK动态代理,目标类必须实现的某个接口,如果某个类没有实现接口则不能生成代理对象,那么,有没有不需要目标类实现接口的动态代理呢?让我们来看下cglib动态代理。

二、cglib动态代理

使用cglib需要引入cglib jar包,本篇案例使用的是cglib2.2.jar

直接先来看下代码实现:

1、先定义一个目标类

package chenhuan.designpattern.proxy;

public class Davis  {
    
    public void sign() {
        System.out.println("签约了,5年2.25亿美元");
    }
}

2、定义一个拦截器,

public class MyMethodInterceptor implements MethodInterceptor {
    /**
     *
     * @param o 表示增强的对象,即实现这个接口类的一个对象
     * @param method 表示要被拦截的方法
     * @param objects 表示要被拦截方法的参数
     * @param methodProxy
     * @return 表示要触发父类的方法对象
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("我们拥有最后一年的球员选项");
      //调用代理类实例上的proxy方法的父类方法 Object object
= methodProxy.invokeSuper(o,objects); System.out.println("签约成功,交易否决权开始生效"); return object; } }

3、使用字节码增强器来生成代理类:

public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //设置enhancer的父类对象
        enhancer.setSuperclass(Davis.class);
        //设置enhancer的回调对象,就是我们定义的拦截器
        enhancer.setCallback(new MyMethodInterceptor());
        //生成代理类
        Davis davis = (Davis)enhancer.create();
        davis.sign();
    }
}

cglib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。需要注意的是,cglib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

本文感觉篇幅过长,就不分析cglib动态代理的源码了 。

 

总结:静态代理由程序员创建代理类,在程序运行前代理类就已经存在了,并且代理类和目标类都要实现相同的接口。当需要代理的对象很多的时候,就需要增加很多的类,假如代理接口需要新增一个方法,那么代理类和目标类都需要修改维护,不易维护。jdk动态代理需要目标类实现接口,也就是说jdk动态代理只能对该类中实现了目标接口的方法进行代理,这个在实际编程中可能存在局限性,cglib动态代理完全不受代理类必须实现接口的限制,其生成的代理类是目标类的子类。

 

你可能感兴趣的:(代理模式(静态代理、JDK动态代理原理分析、CGLIB动态代理))