代理模式

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

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

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

源码有一些检验判断,我们暂且忽略,重点是看怎么创建代理类以及代理类调用方法的时候如何调用的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<ClassLoader, Class<?>[], Class<?>>
    {
        // 所有代理类的前缀名都以$Proxy开头
        private static final String proxyClassNamePrefix = "$Proxy";

        // 下一个用于生成唯一代理类名的数字
        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<?> 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,就会在项目中生成一个代理类:

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动态代理)的更多相关文章

  1. Java-JDK动态代理(AOP)使用及实现原理分析

    Java-JDK动态代理(AOP)使用及实现原理分析 第一章:代理的介绍 介绍:我们需要掌握的程度 动态代理(理解) 基于反射机制 掌握的程度: 1.什么是动态代理? 2.动态代理能够做什么? 后面我 ...

  2. Java中的代理模式--静态代理和动态代理本质理解

    代理模式定义:为其他对象提供了一种代理以控制对这个对象的访问. 代理模式的三种角色: Subject抽象主题角色:抽象主题类可以是抽象类也可以是接口,是一个最普通的业务类型定义,无特殊要求. Real ...

  3. java 代理模式-静态代理与动态代理

    最近在研究SpringAOP,当然要学习AOP就要知道这么健硕.强大的功能的背后究竟隐藏着怎样不可告人的“秘密”?? 接下来就是查阅了许多资料详细的研究了一下Java的代理模式,感觉还是非常非常重要的 ...

  4. 代理模式:利用JDK原生动态实现AOP

    代理模式:利用JDK原生动态实现AOP http://www.cnblogs.com/qiuyong/p/6412870.html 1.概述 含义:控制对对象的访问. 作用:详细控制某个(某类)某对象 ...

  5. java 设计模式之单利模式以及代理模式(静态)

    1:单利模式: public class Singleton { private static Singleton uniqueInstance = null; private Singleton() ...

  6. 代理模式 静态代理、JDK动态代理、Cglib动态代理

    1 代理模式 使用代理模式时必须让代理类和被代理类实现相同的接口: 客户端通过代理类对象来调用被代理对象方法时,代理类对象会将所有方法的调用分派到被代理对象上进行反射执行: 在分派的过程中还可以添加前 ...

  7. Java代理模式/静态代理/动态代理

    代理模式:即Proxy Pattern,常用的设计模式之一.代理模式的主要作用是为其他对象提供一种代理以控制对这个对象的访问. 代理概念 :为某个对象提供一个代理,以控制对这个对象的访问. 代理类和委 ...

  8. Java JDK 动态代理(AOP)使用及实现原理分析

    一.什么是代理? 代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问.代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理. 代理模式U ...

  9. 【Java】代处理?代理模式 - 静态代理,动态代理

    >不用代理 有时候,我希望在一些方法前后都打印一些日志,于是有了如下代码. 这是一个处理float类型加法的方法,我想在调用它前打印一下参数,调用后打印下计算结果.(至于为什么不直接用+号运算, ...

随机推荐

  1. AndroidStudio 问题点 - app:preFUNDebugAndroidTestBuild

    Error:Execution failed for task ':app:preFUNDebugAndroidTestBuild'. >Conflictwith dependency 'com ...

  2. python语法之函数1

    函数 计算机中的函数和数学中的函数不是一回事,而是一个subroutine .子程序.procedures.过程. 作用: 1.减少重复代码: 2.方便修改,更易扩展: 3.保持代码的一致性. 最简单 ...

  3. idea快捷键(后续更新)

    自动补全当前行的标点符号 ctrl + shirt + 回车 跳到下一行 shirt +回车 复制一行 crtl + d 删除一行 ctrl + y 提示报错 alt + 回车 查看当前可以产什么参数 ...

  4. jquery-能拖拽宽度的table

    控件官方地址:http://www.bacubacu.com/colresizable/?utm_source=jquer.in&utm_medium=website&utm_camp ...

  5. Win10专业版激活

    转载来自:http://www.zhuangjiba.com/bios/3432.html 如何激活win10正式版图文解说 打开开始菜单,找到设置,点开“更新和安全”,切换到“激活”选项卡,查看到当 ...

  6. 第一次OO总结

    作业1——多项式加减法 看到这个名字就开始瑟瑟发抖了,毕竟一年前用C语言让我写这么一个程序都很头疼,什么堆栈啊还有结构都稀里糊涂的,更别说用一个完全没接触过的语言来完成最简单的一次作业.像我这样越老心 ...

  7. Jquery动态添加/删除表格行和列

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  8. sftp修改用户home目录后登录时报connection closed by remote host

    在sftp用户需要修改登录根目录的情况下,我们可以修改/etc/ssh/sshd_config文件中ChrootDirectory /home/[path]的路径. 但是,在重启sshd服务后,sft ...

  9. Runtime "Apache Tomcat v6.0 (3)" is invalid. The JRE could not be found. Edit the server and change the JRE location解决方案

    使用eclipse,启动Tomcat时出现The JRE could not be found ,Edit server and change teh JRE location的错误提示! 原因:重装 ...

  10. 传统对象池&AB对象池

    前序: Q:为啥需要对象池? A: 游戏中大量出现或销毁对象时会反复的开堆和放堆,程序与内存之间交互过于频繁导致资源的大量浪费 Q: 对象池实现原理? A: 当子对象池没有物体的时候,它会和普通没加对 ...