JDK动态代理和CGLIB动态代理

JDK动态代理和CGLIB动态代理

代理

代理模式是一种常用的设计模式, 代表性的有Spring中的AOP.

代理模式就像是一个中介一样, 以租房为例.

房东作为被代理类, 租客作为调用方, 中介就是租客和房东中间的代理. 在中介的操作下, 租客租下了房间, 中介在中间做了一些事情.

代理分为静态代理和动态代理.

静态代理

静态代理就是预先写好的代理类.

举个例子:

// IRoom.java
public interface IRoom {
    void rent();
}

// Room.java
public class Room implements IRoom {
    @Override
    public void rent() {
        System.out.println("房东收租金100块");
    }
}

// Client.java
public class Client {
    public static void main(String[] args) {
        Room room = new Room();
        room.rent();
    }
}

输出:

房东收租金100块

很简单一个例子, 直接找房东租房, 房东直接收租金100块.

加上代理:

// IRoom.java
public interface IRoom {
    void rent();
}

// Room.java
public class Room implements IRoom {
    @Override
    public void rent() {
        System.out.println("房东收租金100块");
    }
}


// RoomProxy.java
public class RoomProxy implements IRoom {
    private IRoom iRoom;
    public RoomProxy(IRoom iRoom) {
        this.iRoom = iRoom;
    }
    @Override
    public void rent() {
        System.out.println("中介收中介费10块");
        iRoom.rent();
        System.out.println("中介收手续费1块");
    }
}

// Client.java
public class Client {
    public static void main(String[] args) {
        RoomProxy roomProxy = new RoomProxy(new Room());
        roomProxy.rent();
    }
}

输出:

中介收中介费10块
房东收租金100块
中介收手续费1块

可以看到, 代理的含义就是在一个代理类中, 调用被代理类的方法, 在调用中可以做一些额外的事情.

动态代理

动态代理大概可以分两类:

  • JDK动态代理
  • CGLIB动态代理

JDK动态代理

// 被代理类实现的接口
public interface ITeacherDao {
    void teach();
}

// 被代理类
public class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
        System.out.println("teachDao teach");
    }
}

// 代理类工厂
public class ProxyFactory {

    private Object teacherDao;

    public ProxyFactory(Object teacherDao) {
        this.teacherDao = teacherDao;
    }

    /**
     * public static Object newProxyInstance(ClassLoader loader,
     *                                           Class<?>[] interfaces,
     *                                           InvocationHandler h)
     *
     * 1. ClassLoader loader : 指定当前目标对象使用的类加载器获取加载器的方法固定
     * 2. Class<?>[] interfaces : 目标对象实现的接口类型, 使用泛型的方式确认类型
     * 3. InvocationHandler h : 事件处理, 执行目标对象的方法时,会触发事件处理器的方法, 会把当前执行的目标对象方法作为参数传入
     *
     * @return
     */
    public Object getNewProxyInstance(){
        return Proxy.newProxyInstance(teacherDao.getClass().getClassLoader(), teacherDao.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("jdk代理开始");
                Object returnVal = method.invoke(teacherDao, args);
                System.out.println("jdk代理结束");
                return returnVal;
            }
        });
    }
}

// 调用方
public class Client {

    public static void main(String[] args) {
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        ProxyFactory proxyFactory = new ProxyFactory(new TeacherDao());
        ITeacherDao newProxyInstance = (ITeacherDao)proxyFactory.getNewProxyInstance();
        newProxyInstance.teach();

        System.out.println(newProxyInstance.getClass());
    }
}

可以看到, 虽然添加了一个ProxyFactory的类, 但是该类持有的是一个Object类型的对象, 也就是说, 该类可以创建’任何’类的代理对象.

为什么要把[任何]两个字打双引号呢?

主要看这一句代码:

Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

这里面有三个参数:

  1. ClassLoader loader: 指定使用哪个类加载器加载该生成的代理对象
  2. Class<?>[] interfaces: 传入该类实现的接口
  3. InvocationHandler h: 执行方法时的事件处理器

可以看到, 第二个参数Class<?>[] interfaces是要求该被代理类必须要实现接口的, 因此JDK的动态代理只能代理实现了接口的类.

// ProxyGenerator
private byte[] generateClassFile() {
    // 添加hashCode(),equals(),toString()方法
    this.addProxyMethod(hashCodeMethod, Object.class);
    this.addProxyMethod(equalsMethod, Object.class);
    this.addProxyMethod(toStringMethod, Object.class);
    // 获取代理类的接口
    Class[] var1 = this.interfaces;
    int var2 = var1.length;

    int var3;
    Class var4;
    for(var3 = 0; var3 < var2; ++var3) {
        var4 = var1[var3];
        // 获取接口中的方法
        Method[] var5 = var4.getMethods();
        int var6 = var5.length;

        for(int var7 = 0; var7 < var6; ++var7) {
            Method var8 = var5[var7];
            // 添加代理方法
            this.addProxyMethod(var8, var4);
        }
    }

    Iterator var11 = this.proxyMethods.values().iterator();

    List var12;
    while(var11.hasNext()) {
        var12 = (List)var11.next();
        checkReturnTypes(var12);
    }

    Iterator var15;
    try {
        // 将方法添加进class文件中
        // 添加生成的构造方法
        this.methods.add(this.generateConstructor());
        var11 = this.proxyMethods.values().iterator();

        while(var11.hasNext()) {
            var12 = (List)var11.next();
            var15 = var12.iterator();

            while(var15.hasNext()) {
                ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
                this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
                this.methods.add(var16.generateMethod());
            }
        }

        this.methods.add(this.generateStaticInitializer());
    } catch (IOException var10) {
        throw new InternalError("unexpected I/O Exception", var10);
    }

    if (this.methods.size() > 65535) {
        throw new IllegalArgumentException("method limit exceeded");
    } else if (this.fields.size() > 65535) {
        throw new IllegalArgumentException("field limit exceeded");
    } else {
        this.cp.getClass(dotToSlash(this.className));
        this.cp.getClass("java/lang/reflect/Proxy");
        var1 = this.interfaces;
        var2 = var1.length;

        for(var3 = 0; var3 < var2; ++var3) {
            var4 = var1[var3];
            this.cp.getClass(dotToSlash(var4.getName()));
        }

        this.cp.setReadOnly();
        ByteArrayOutputStream var13 = new ByteArrayOutputStream();
        DataOutputStream var14 = new DataOutputStream(var13);

        try {
            var14.writeInt(-889275714);
            var14.writeShort(0);
            var14.writeShort(49);
            this.cp.write(var14);
            var14.writeShort(this.accessFlags);
            var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
            var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
            var14.writeShort(this.interfaces.length);
            Class[] var17 = this.interfaces;
            int var18 = var17.length;

            for(int var19 = 0; var19 < var18; ++var19) {
                Class var22 = var17[var19];
                var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
            }

            var14.writeShort(this.fields.size());
            var15 = this.fields.iterator();

            while(var15.hasNext()) {
                ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
                var20.write(var14);
            }

            var14.writeShort(this.methods.size());
            var15 = this.methods.iterator();

            while(var15.hasNext()) {
                ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                var21.write(var14);
            }

            var14.writeShort(0);
            return var13.toByteArray();
        } catch (IOException var9) {
            throw new InternalError("unexpected I/O Exception", var9);
        }
    }
}

从上面的代码可以看到, JDK中添加代理类是使用获取接口, 从接口获取方法再添加到class文件中.

因此JDK中动态代理的劣势就是要求被代理类必须要实现接口.

保存动态生成的代理类

在代码中添加下面一行代码就可以将生成的代理类保存下来, 默认保存地址是当前工作路径的 com.sun.proxy包下.

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

CGLIB动态代理

CGLIB动态代理就规避了这个问题, 不再要求被代理类必须实现接口, 而且写起来更加简单, 只需要在pom文件里添加cglib的引用即可.

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>

代码:

// 被代理类
public class TeacherDao implements ITeacherDao {
    @Override
    public void teach() {
        System.out.println("teachDao rent");
    }
}

// 调用方
public class Client {

    public static void main(final String[] args) {

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TeacherDao.class);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println("开始cglib代理");
                Object invoke = methodProxy.invokeSuper(o, objects);
                System.out.println("结束cglib代理");
                return invoke;
            }
        });

        TeacherDao o = (TeacherDao)enhancer.create();
        o.teach();
    }
}

输出:

开始cglib代理
teachDao rent
结束cglib代理

可以看到, enhancer.setSuperclass(TeacherDao.class);设置的是SuperClass,

而调用中Object invoke = methodProxy.invokeSuper(o, objects);也是使用invokeSuper的方式调用的.

因此CGLIB生成的代理类是被代理类的子类.

如果该被代理类被声明为final, 就无法使用CGLIB生成代理类了.


   转载规则


《JDK动态代理和CGLIB动态代理》 echi1995 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
多线程与高并发(一) 多线程与高并发(一)
多线程与高并发(一)程序, 进程, 线程, 纤程(协程) 程序 程序就是硬盘上保存的可执行的代码 进程 程序开始执行, 硬盘上的代码加载到内存中就叫进程 线程 线程就是进程调度的最小单位 纤程(协程) 用户态的线程 ThreadThread
下一篇 
NIO API NIO API
NIONIO简介Java NIO (New IO / Non Blocking IO)是从Java1.4版本开始引入的一个新的IO API, 可以的替代标准的Java IO API.NIO与原来的IO有同样的作用和目的, 但是使用的方式
  目录