详解JDK动态代理和CGLib动态代理

开发 后端
代理模式(Proxy Pattern)是23种设计模式中的一种,属于结构型设计模式。代理模式给某一个对象提供一个代理,并由代理对象控制原对象的引用。代理对象在客户端和目标对象之间起到中介作用。

代理模式

代理模式(Proxy Pattern)是23种设计模式中的一种,属于结构型设计模式。代理模式给某一个对象提供一个代理,并由代理对象控制原对象的引用。代理对象在客户端和目标对象之间起到中介作用。

举个例子:你要去吃饭,你可以选择自己在家做饭、吃饭、刷碗,所有的事情都自己做;也可以选择去餐厅,自己只是吃饭,把做饭和刷碗的活儿都交给代理对象,也就是餐厅的工作人员。

下图是代理模式的通用类图。结合例子,就很容易理解了。

代理模式通用类图

代理模式包含如下角色:

  • Subject (抽象主题角色) 抽象主题角色声明了真实主题和代理主题的共同接口,这样一来在任何使用真实主题 的地方都可以使用代理主题。客户端需要针对抽象主题角色进行编程。
  • Proxy (代理主题角色) 代理主题角色内部包含对真实主题的引用,从而可以在任何时候操作真实主题对象。 在代理主题角色中提供一个与真实主题角色相同的接口,以便在任何时候都可以替代真实主体。代理主题角色还可以控制对真实主题的使用,负责在需要的时候创建和删除真实主题对象,并对真实主题对象的使用加以约束。代理角色通常在客户端调用所引用的真实主题操作之前或之后还需要执行其他操作,而不仅仅是单纯的调用真实主题对象中的操作。
  • RealSubject (真实主题角色) 真实主题角色定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的方法。

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

JDK动态代理

在jdk的动态代理机制中,有几个重要的角色:

  • Interface:对于JDK Proxy,业务类是需要一个Interface的。
  • Proxy:Proxy类是动态产生的,这个类在调用Proxy.newProxyInstance()方法之后,产生一个Proxy类的实例。实际上,这个Proxy类也是存在的,不仅仅是类的实例,这个Proxy类可以保存在硬盘上。
  • Method:对于业务委托类的每个方法,现在Proxy类里面都不用静态显示出来。
  • InvocationHandler:这个类在业务委托类执行时,会先调用invoke方法。invoke方法再执行想要的代理操作,可以实现对业务方法的再包装。

(1)InvocationHandler

每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

InvocationHandler这个接口的唯一一个方法 invoke 方法:

Object invoke(Object proxy, Method method, Object[] args) throws Throwable

这个方法一共接受三个参数,那么这三个参数分别代表如下:

  • proxy:指代JDK动态生成的最终代理对象
  • method:指代的是我们所要调用真实对象的某个方法的Method对象
  • args:指代的是调用真实对象某个方法时接受的参数

(2)Proxy

Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是newProxyInstance 这个方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException

这个方法的作用就是得到一个动态的代理对象,其接收三个参数,我们来看看这三个参数所代表的含义:

  • loader:ClassLoader对象,定义了由哪个ClassLoader来对生成的代理对象进行加载,即代理类的类加载器。
  • interfaces:Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了。
  • Handler:InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。

所以我们所说的DynamicProxy(动态代理类)是这样一种class:它是在运行时生成的class,在生成它时你必须提供一组interface给它,然后该class就宣称它实现了这些 interface。这个DynamicProxy其实就是一个Proxy,它不会做实质性的工作,在生成它的实例时你必须提供一个handler,由它接管实际的工作。

(3)代码实现

被代理对象:

/**
 * 抽象主题角色
 */
interface Subject {
    void eat();
}

/**
 * 真实主题角色 - 你自己 - 专注吃饭
 */
class YourSelf implements Subject{

    @Override
    public void eat() {
        System.out.println("自己吃饭");
    }
}

代理对象:

/**
 * 代理主题角色 - 餐厅
 * 每次生成动态代理类对象时都需要指定一个实现了InvocationHandler接口的调用处理器对象
 */
class JdkProxySubject implements InvocationHandler {

    // 这个就是我们要代理的真实对象,也就是真正执行业务逻辑的类
    private Object target;

    // 通过构造方法传入这个被代理对象
    public JdkProxySubject(Object target) {
        super();
        this.target = target;
    }

    // 创建代理对象
    public Object createProxy() {
        // 1.得到目标对象的类加载器
        ClassLoader classLoader = target.getClass().getClassLoader();
        // 2.得到目标对象的实现接口
        Class<?>[] interfaces = target.getClass().getInterfaces();
        // 3.第三个参数需要一个实现invocationHandler接口的对象
        Object newProxyInstance = Proxy.newProxyInstance(classLoader, interfaces, this);
        return newProxyInstance;
    }


    // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("餐厅工作人员做饭......");
        Object invoke = method.invoke(target, args);
        System.out.println("餐厅工作人员刷碗......");
        return invoke;
    }
}

测试类:

/**
 * 测试类
 * @author tianxiaopeng@hxy
 * @date 2023/10/11 11:09 AM
 */
public class ProxyTest {
    public static void main(String[] args) {
        // 1.创建对象
        YourSelf yourSelf = new YourSelf();
        // 2.创建代理对象
        JdkProxySubject proxy = new JdkProxySubject(yourSelf);
        // 3.调用代理对象的增强方法,得到增强后的对象
        Subject createProxy = (Subject) proxy.createProxy();
        createProxy.eat();
    }
}

CGLIB动态代理

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

CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。(利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理)

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

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

(1)CGLib核心类

net.sf.cglib.proxy.Enhancer:主要增强类,通过字节码技术动态创建委托类的子类实例。

Enhancer可能是CGLIB中最常用的一个类,和Java1.3动态代理中引入的Proxy类差不多。和Proxy不同的是,Enhancer既能够代理普通的class,也能够代理接口。Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。Enhancer不能够拦截final方法,例如Object.getClass()方法,这是由于Java final方法语义决定的。基于同样的道理,Enhancer也不能对fianl类进行代理操作。这也是Hibernate为什么不能持久化final class的原因。

net.sf.cglib.proxy.MethodInterceptor:常用的方法拦截器接口,需要实现intercept方法,实现具体拦截处理。

public java.lang.Object intercept(java.lang.Object obj,
                                   java.lang.reflect.Method method,
                                   java.lang.Object[] args,
                                   MethodProxy proxy) throws java.lang.Throwable{}
  • obj:动态生成的代理对象。
  • method:实际调用的方法。
  • args:调用方法入参。
  • net.sf.cglib.proxy.MethodProxy:java Method类的代理类,可以实现委托类对象的方法的调用;常用方法:methodProxy.invokeSuper(proxy, args);在拦截方法内可以调用多次。

(2)CGLib代理实例

创建被代理类。

/**
 * 真实主题角色 - 你自己 - 专注吃饭
 */
class YourSelf {
    public void eat(){
        System.out.println("自己吃饭");
    }
}

创建代理类:

/**
 * 代理主题角色 - 餐厅
 */
class ProxyCglib implements MethodInterceptor {
    private Enhancer enhancer = new Enhancer();
    public Object getProxy(Class clazz){
        //设置需要创建子类的类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        //通过字节码技术动态创建子类实例
        return enhancer.create();
    }

    //实现MethodInterceptor接口方法
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("餐厅工作人员做饭......");
        //通过代理类调用父类中的方法
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("餐厅工作人员刷碗......");
        return result;
    }
}

测试类:

/**
 * 测试类
 * @author tianxiaopeng@hxy
 * @date 2023/10/11 11:51 AM
 */
public class CglibTest {
    public static void main(String[] args) {
        ProxyCglib proxy = new ProxyCglib();
        //通过生成子类的方式创建代理类
        YourSelf proxyImp = (YourSelf)proxy.getProxy(YourSelf.class);
        proxyImp.eat();
    }
}

结果:

餐厅工作人员做饭......
自己吃饭
餐厅工作人员刷碗......

(2)CGLIB动态代理实现分析

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

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

两者区别

  • JDK代理只能对实现接口的类生成代理;CGLib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
  • JDK代理使用的是反射机制实现aop的动态代理,CGLib代理使用字节码处理框架ASM,通过修改字节码生成子类。
  • JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLib则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。
责任编辑:姜华 来源: 今日头条
相关推荐

2022-09-01 10:40:29

SpringAOPJDK

2021-07-06 06:39:22

Java静态代理动态代理

2021-04-22 09:58:15

JDK代理动态

2021-07-14 11:07:56

AOPJDKCglib

2021-01-14 05:16:09

MyBatis动态代理

2023-07-05 08:17:38

JDK动态代理接口

2017-05-11 21:30:01

Android动态代理ServiceHook

2024-01-04 07:42:44

JavaCGLIBJDK

2011-04-06 11:41:25

Java动态代理

2024-09-05 09:35:58

CGLIBSpring动态代理

2023-02-24 07:42:30

Java动态代理

2022-02-22 22:44:46

接口源码对象

2012-08-28 10:59:26

JavaJava动态代理Proxy

2015-09-22 11:09:47

Java 8动态代理

2011-03-23 10:40:51

java代理模式

2021-07-03 08:59:49

动态代理JDK

2009-12-28 15:45:22

动态网络接入控制

2020-12-29 05:34:00

动态代理

2022-06-30 10:05:30

Java接口动态代理

2017-10-12 14:56:11

点赞
收藏

51CTO技术栈公众号