动态代理

一、动态代理浅析

何为代理?就是暂时代人担任 某单位的负责职务。比如代课,火车票代售点。

代理模式:为其他对象提供一种代理以控制这个对象的访问。下面为代理模式的类图

如何实现代理

通过 组合 来实现:

首先定义一个学生接口

public interface Student {
    /**
     * 上课
     */
    public void goToClass();
}

定义一个真实的学生实体SliuStudent

public class SliuStudent implements Student{
    @Override
    public void goToClass() {
        System.out.println("sliu go to class");
    }
}

现在该学生想找一个代理来代替他去上课,但是代理要收取费用,所以代理要计算上课花费时间。

public class StudentProxy implements Student{
    private Student student;
    public StudentProxy(Student student){
        this.student =student;
    }
    @Override
    public void goToClass() {
        long start = System.currentTimeMillis();
        student.goToClass();
        long end = System.currentTimeMillis();
        System.out.println("上课耗时:"+(end - start));
    }
}

1.1、静态代理

上述学生代课的例子是一个典型的静态代理,有如下缺点:

  1. 如果同时代理多个类,依然会导致类无限制扩展

  2. 如果类中有多个方法,同样的逻辑需要反复实现

1.2、动态代理

动态代理,就是通过一个代理类,能够执行任何一个真实实体类的任何方法,并且在不改变源码的基础上,增加一些自定义操作,比如权限管理,事务提交等。通过动态代理就可以实现AOP编程。

JDK提供了Proxy和InvocationHandler接口来 实现动态代理。

1.2.1Proxy

public class Proxy extends Object implements Serializable Proxy 提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。Proxy.newProxyInstance方法(摘自JDK API 1.6.0中文版)​
public static Object newProxyInstance(
    ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler h)
    throws IllegalArgumentException
    返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
        - 此方法相当于:
            Proxy.getProxyClass(loader, interfaces).
            getConstructor(new Class[] { InvocationHandler.class }).
            newInstance(new Object[] { handler });
        - Proxy.newProxyInstance 抛出 IllegalArgumentException,原因与 Proxy.getProxyClass 相同。
​
参数:
    loader
        - 定义代理类的类加载器
        - ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
    interfaces
        - 代理类要实现的接口列表
        - Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口
        - 如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
    h
        - 指派方法调用的调用处理程序
        - InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
返回:
    一个带有代理类的指定调用处理程序的代理实例,它由指定的类加载器定义,并实现指定的接口

1.2.2、InvocationHandler

public interface InvocationHandlerInvocationHandler 是代理实例的调用处理程序实现的接口。 每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke 方法。invoke方法描述(摘自JDK API 1.6.0中文版)​
public Object invoke(Object proxy,Method method,Object[] args)throws Throwable
    在代理实例上处理方法调用并返回结果。
    在与方法关联的代理实例上调用方法时,将在调用处理程序上调用此方法。
​
参数:
    proxy
        - 在其上调用方法的代理实例,指代我们所代理的那个真实对象
        - 如果你的接口中有方法需要返回自身,如果在invoke中没有传入这个参数,将导致实例无法正常返回。在这种场景         中,proxy的用途就表现出来了。简单来说,这其实就是最近非常火的链式编程的一种应用实现。
    method
        - 对应于在代理实例上调用的接口方法的 Method 实例。
        - Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口。
        - 指代的是我们所要调用真实对象的某个方法的Method对象
    args
        - 包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。
        - 基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
        - 指代的是调用真实对象某个方法时接受的参数
返回:
    从代理实例的方法调用返回的值。
    如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;
    否则,它一定是可分配到声明返回类型的类型。
    如果此方法返回的值为 null 并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出 NullPointerException。
    否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出 ClassCastException。

现在我们通过动态代理来完成学生代课逻辑,Student类,SliuStudent类还是和上面一样,现在我们重写一个代理工厂类,来负责生成任何类的任何方法的代理类。

public class ProxyFactory {
​
    public static Object getProxyInstance(final Class<?> serviceClass){
        return  Proxy.newProxyInstance(serviceClass.getClassLoader(),serviceClass.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("method "+method);
                System.out.println("args "+args);
                long start = System.currentTimeMillis();
                Object invoke =   method.invoke(serviceClass.newInstance(),args);
                long end = System.currentTimeMillis();
                System.out.println("上课耗时:"+(end - start));
                return invoke;
            }
        });
    }
}

1.3、代理模式的应用

  1. 动态代理是十分强大的设计模式,代理类的代码量被固定下来,不会因为业务的逐渐庞大而庞大;

  2. 可以实现AOP编程,实际上静态代理也可以实现,总的来说,AOP可以算作是代理模式的一个典型应用;

  3. 事务提交或回退(Web开发中很常见)

  4. 权限管理

  5. 自定义缓存逻辑处理

  6. SDK Bug修复

  7. 解耦,通过参数就可以判断真实类,不需要事先实例化,更加灵活多变;

二、动态代理深入理解

2.1、原理

JDK的Proxy类是代理类的超类,通过静态方法newProxyInstance来动态生成对应的代理类实例,该代理类应该是继承Proxy并实现了方法中传入的接口的所有方法。所以Proxy类只能代理有接口的类。

下面我们来看一下动态生成的代理类到底长什么样:

首先列出相关代码,Student,SliuStudent,ProxyFactory

public interface Student {
​
    /**
     * 上课
     */
    public void goToClass(String name);
​
}
public class SliuStudent implements Student {
    @Override
    public void goToClass(String name) {
        System.out.println("sliu go to class:" + name);
    }
}
public class ProxyFactory {
​
    public static Object getProxyInstance(final Class<?> serviceClass){
        return  Proxy.newProxyInstance(serviceClass.getClassLoader(),serviceClass.getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("method "+method);
                System.out.println("args "+args);
                long start = System.currentTimeMillis();
                Object invoke =   method.invoke(serviceClass.newInstance(),args);
                long end = System.currentTimeMillis();
                System.out.println("上课耗时:"+(end - start));
                return invoke;
            }
        });
    }
}

然后我们写一个Test类,将Proxy类生成的代理类的对象字节码写到磁盘然后通过反编译器来查看

public class Test {
    public static void main(String[] args) throws IOException {
        SliuStudent sliuStudent = new SliuStudent();
        Student proxy = (Student) ProxyFactory.getProxyInstance(sliuStudent.getClass());
        proxy.goToClass("语文");
        System.out.println(proxy.getClass());
        /**
         * 上面生成的代理对象字节码 com.sun.proxy.$Proxy0 是在内存中的
         * 这里将其对象写到文件中,通过反编译查看
         */
        byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0",new Class[]{Student.class});
        FileOutputStream fos = new FileOutputStream("E:\\$Proxy0.class");
        fos.write(bytes);
        System.out.println("写入文件成功");
    }
}

$Proxy0.class类的反编译文件如下:

public final class $Proxy0 extends Proxy implements Student {
    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);
        }
    }
​
    public final void goToClass(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("proxy.Student").getMethod("goToClass", 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超类,并实现了Student接口的方法,连equals,toString,hashCode也实现了。并且这里的方法都调用了InvocationHandler接口的invoke()方法。通过super.h.invoke调用用户自定义实现的方法,在invoke方法中,method.invoke才是真正通过反射调用了我们自身所写的方法,而我们可以在这个方法的前后嵌入我们的代码。这样就实现了不改变源码的情况下增强方法。

2.2、Proxy源码

首先看一下newProxyInstance方法实现,该方法首先对参数进行了一些权限校验,然后通过getProxyClass0方法获得代理类的类对象,然后获取行参为InvocationHandler.class的构造函数。最后传入InvocationHandler实例生成代理类实例。

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h) throws IllegalArgumentException {
    //验证传入的InvocationHandler不能为空
    Objects.requireNonNull(h);
    //复制代理类实现的所有接口
    final Class<?>[] intfs = interfaces.clone();
    //获取安全管理器
    final SecurityManager sm = System.getSecurityManager();
    //进行一些权限检验
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }
    //该方法先从缓存获取代理类, 如果没有再去生成一个代理类
    Class<?> cl = getProxyClass0(loader, intfs);
    try {
        //进行一些权限检验
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }
        //获取参数类型是InvocationHandler.class的代理类构造器
        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;
                }
            });
        }
        //传入InvocationHandler实例去构造一个代理类的实例
        //所有代理类都继承自Proxy, 因此这里会调用Proxy的构造器将InvocationHandler引用传入
        return cons.newInstance(new Object[]{h});
    } catch (Exception e) {
        //为了节省篇幅, 笔者统一用Exception捕获了所有异常
        throw new InternalError(e.toString(), e);
    }
}
private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    //目标类实现的接口不能大于65535
    if (interfaces.length > 65535) {
        throw new IllegalArgumentException("interface limit exceeded");
    }
    //获取代理类使用了缓存机制
    return proxyClassCache.get(loader, interfaces);
}

可以看到getProxyClass0方法内部没有多少内容,首先是检查目标代理类实现的接口不能大于65535这个数,之后是通过类加载器和接口集合去缓存里面获取,如果能找到代理类就直接返回,否则就会调用ProxyClassFactory这个工厂去生成一个代理类。关于这里使用到的缓存机制我们留到下一篇专门介绍,首先我们先看看这个工厂类是怎样生成代理类的。

//代理类生成工厂
private static final class ProxyClassFactory
                implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    //代理类名称前缀
    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) {
            //这里遍历interfaces数组进行验证, 主要做三件事情
            //1.intf是否可以由指定的类加载进行加载
            //2.intf是否是一个接口
            //3.intf在数组中是否有重复
        }
        //生成代理类的包名
        String proxyPkg = null;
        //生成代理类的访问标志, 默认是public final的
        int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
        for (Class<?> intf : interfaces) {
            //获取接口的访问标志
            int flags = intf.getModifiers();
            //如果接口的访问标志不是public, 那么生成代理类的包名和接口包名相同
            if (!Modifier.isPublic(flags)) {
                //生成的代理类的访问标志设置为final
                accessFlags = Modifier.FINAL;
                //获取接口全限定名, 例如:java.util.Collection
                String name = intf.getName();
                int n = name.lastIndexOf('.');
                //剪裁后得到包名:java.util
                String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                //生成的代理类的包名和接口包名是一样的
                if (proxyPkg == null) {
                    proxyPkg = pkg;
                } else if (!pkg.equals(proxyPkg)) {
                    //代理类如果实现不同包的接口, 并且接口都不是public的, 那么就会在这里报错
                    throw new IllegalArgumentException(
                        "non-public interfaces from different packages");
                }
            }
        }
        //如果接口访问标志都是public的话, 那生成的代理类都放到默认的包下:com.sun.proxy
        if (proxyPkg == null) {
            proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
        }
        //生成代理类的序号
        long num = nextUniqueNumber.getAndIncrement();
        //生成代理类的全限定名, 包名+前缀+序号, 例如:com.sun.proxy.$Proxy0
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        //这里是核心, 用ProxyGenerator来生成字节码, 该类放在sun.misc包下
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName,
                                  interfaces, accessFlags);
        try {
            //根据二进制文件生成相应的Class实例
            return defineClass0(loader, proxyName, proxyClassFile,
                              0, proxyClassFile.length);
        } catch (ClassFormatError e) {
            throw new IllegalArgumentException(e.toString());
        }
    }
}

该工厂的apply方法会被调用用来生成代理类的Class对象,由于代码的注释比较详细,我们只挑关键点进行阐述,其他的就不反复赘述了。

  1. 在代码中可以看到JDK生成的代理类的类名是“$Proxy”+序号。

  1. 如果接口是public的,代理类默认是public final的,并且生成的代理类默认放到com.sun.proxy这个包下。

  1. 如果接口是非public的,那么代理类也是非public的,并且生成的代理类会放在对应接口所在的包下。

  1. 如果接口是非public的,并且这些接口不在同一个包下,那么就会报错。

生成具体的字节码是调用了ProxyGenerator这个类的generateProxyClass方法。这个类放在sun.misc包下,后续我们会扒出这个类继续深究其底层源码。到这里我们已经分析了Proxy这个类是怎样生成代理类对象的,通过源码我们更直观的了解了整个的执行过程,包括代理类的类名是怎样生成的,代理类的访问标志是怎样确定的,生成的代理类会放到哪个包下面,以及InvocationHandler实例的引用是怎样传入的

(0)

相关推荐

  • 框架虽好,但不要丢了其背后的原理。​

    开始之前先简单介绍一下我所在团队的技术栈,基于这个背景再展开后面将提到的几个问题,将会有更深刻的体会. 控制层基于SpringMvc,数据持久层基于JdbcTemplate自己封装了一套类MyBati ...

  • 通过模拟JDK中的动态代理,由浅入深讲解动态代理思想.

    目录 场景引入 动态代理引入 动态代理进阶 总结 个人认为动态代理在设计模式中算是比较难的, 本篇文章将从无到有, 从一个简单代码示例开始迭代, 逐步深入讲解动态代理思想. 场景引入 假设现在有一个坦 ...

  • Jdk动态代理原理解析

    作者:行径行 动态代理这个知识点,也是我们开发过程中非常容易遇到.特别的是在一些框架中,为了满足软件开发的开闭原则,以及增强框架自身的灵活拓展功能.在底层就会为那些特定的目标类或者接口实现类进行渲染与 ...

  • jdk 动态代理源码分析

    闲来无事,撸撸源码 食用方法 直接看代码吧.. package com.test.demo.proxy;import java.lang.reflect.InvocationHandler;impor ...

  • .NET 下基于动态代理的 AOP 框架实现揭秘

    Intro 之前基于 Roslyn 实现了一个简单的条件解析引擎,想了解的可以看这篇文章 https://www.cnblogs.com/weihanli/p/roslyn-based-conditi ...

  • 一文读懂Java动态代理

    引言 最早的代理模式,我们大致可以联想到三国时期,孟德君挟天子以令诸侯是代理模式,是权利代理:现今生活中类似房产中介.票务中介是代理模式,是业务代理:还有翻墙浏览网页是代理模式,是VPN代理:回到我们 ...

  • Java动态代理设计模式

    本文主要介绍Java中两种常见的动态代理方式:JDK原生动态代理和CGLIB动态代理. 什么是代理模式 就是为其他对象提供一种代理以控制对这个对象的访问.代理可以在不改动目标对象的基础上,增加其他额外 ...

  • java设计模式基础--动态代理

    动态代理的意义在于生成一个代理对象,来代理真实对象,从而控制真实对象的访问.比如你是以为软件工程师,客户带着需求去找公司,显然不会直接和你谈,而是去找商务,此时客户认为商务就代表公司.商务(代理对象) ...

  • Java中动态代理使用

    相比于静态代理,动态代理避免了开发人员编写各个繁锁的静态代理类,只需简单地指定一组接口及目标类对象就能动态的获得代理对象. 代理模式 使用代理模式必须要让代理类和目标类实现相同的接口,客户端通过代理类 ...

  • 对JDK动态代理的模拟实现

    动态代理在JDK中的实现: IProducer proxyProduec = (IProducer)Proxy.newProxyInstance(producer.getClass().getClas ...

  • jdk动态代理

    JDK动态代理和CGLIB代理的区别: JDK动态代理:其代理对象必须是某个接口的实现,他是通过在运行期间创建一个接口的实现类来完成对目标对象的代理. CGLIB代理:实现原理类似于JDK动态代理,只 ...

  • 动态代理竟然如此简单!

    来自公众号:Java建设者 这篇文章我们来聊一下 Java 中的动态代理. 动态代理在 Java 中有着广泛的应用,比如 AOP 的实现原理.RPC远程调用.Java 注解对象获取.日志框架.全局性异 ...