Java 静态代理、JDK动态代理、CGLIB代理的区别及原理

本文详细介绍了Java中的三种代理方式:静态代理、JDK动态代理和CGLIB代理。静态代理通过实现相同接口实现目标对象的扩展,但增加大量代理类。JDK动态代理依赖接口,动态生成字节码实现代理,避免了静态代理的冗余。CGLIB则针对无接口的类,通过生成子类来实现代理,调用效率相对更高。
摘要由CSDN通过智能技术生成

Java 静态代理、JDK动态代理、CGLIB代理

代理(Proxy):是一种设计模式,提供了对目标对象另外的访问方式,即通过代理对象访问目标对象。

好处:可以在目标对象实现的基础上,增强额外的功能操作,即目标对象的扩展功能。

举个例子:假如产品经理提了一个新的需求,需要在某个方法调用之前以及调用之后打印日志。那么我们就可以通过代理的方式,不修改方法内部的代码而实现。

1. Java静态代理

静态代理: 代理对象和目标对象实现相同的接口或者继承相同的父类,然后通过调用代理对象的方法来调用目标对象的方法实现代理。

// 委托接口
public interface HelloService {
   

    void sayHello();
}

// 接口实现类
public class HelloServiceImpl implements HelloService {
   

    @Override
    public void sayHello() {
   
        System.out.println("hello 我是接口实现类");
    }
}

// 代理类
public class HelloServiceProxy implements HelloService {
   

    private HelloService helloService;

    public HelloServiceProxy(HelloService helloService) {
   
        this.helloService = helloService;
    }

    @Override
    public void sayHello() {
   
        System.out.println("目标对象方法调用前打印日志 ...");
        this.helloService.sayHello();
        System.out.println("目标对象方法调用后打印日志 ...");
    }
}

// 测试静态代理类
public class Main {
   

    public static void main(String[] args) {
   
        HelloService target = new HelloServiceImpl();
        HelloService proxy = new HelloServiceProxy(target);
        proxy.sayHello();
    }
}

运行结果

image-20220114191943424

我们将接口实现类注入到代理类中,然后通过代理类调用真正的实现类实现的方法。那么我们就可以在调用实现的方法的前后做一些处理。

优点:

可以做到不修改目标对象功能的前提下,对目标对象进行扩展。

缺点:

因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,同时接口一旦增加新的方法,代理对象也需要实现相应的方法。

2. JDK动态代理

为解决静态代理为不同接口实现不同的代理类而带来的缺点,我们可以利用动态代理的方式,代理类无需实现目标类的接口,就可以代理目标类的方法,但是目标类必须要实现接口

// 方法调用前后打印日志的代理类
public class LogProxy implements InvocationHandler {
   

    Object target;

    public LogProxy(Object target) {
   
        this.target = target;
    }


    /**
     * 动态生成代理类对象,Proxy.newProxyInstance
     * @return 返回代理类的实例
     */
    public Object newProxyInstance() {
   
        return Proxy.newProxyInstance(
            	//指定代理对象的类加载器
                target.getClass().getClassLoader(),
            	//代理对象需要实现的接口,可以同时指定多个接口
                target.getClass().getInterfaces(),
            	//方法调用的实际处理者,代理对象的方法调用都会转发到这里
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   
        System.out.println(target + " 的 " + method.getName() + " 方法即将调用");
        Object ret = method.invoke(this.target, args);
        System.out.println(target + " 的 " + method.getName() + " 方法调用完毕");
        return ret;
    }
}

// 测试代理类
public class Main {
   

    public static void main(String[] args) {
   
        HelloService target = new HelloServiceImpl();
        LogProxy logProxy = new LogProxy(target);
        HelloService proxyInstance = (HelloService) logProxy.newProxyInstance();
        proxyInstance.sayHello();

        /* 同一个代理类,可以代理实现同接口的目标类 */
        CharSequence target2 = new String("hello");
        LogProxy logProxy2 = new LogProxy(target2);
        CharSequence proxyInstance2 = (CharSequence) logProxy2.newProxyInstance();
        System.out.println(proxyInstance2.length());
    }
}

运行结果:

image-20220114200928897

代理类通过实现 InvocationHandler 接口的 invoke() 方法,并通过 Proxy 类的静态方法newProxyInstance()方法动态生成代理类实例, 即可对注入的目标类进行代理,而且同一个代理类可以代理实现不同接口的目标类,相比较静态代理大大提高了开发效率。

那么动态代理是如何实现的呢?

在了解动态代理之前,我们先来回顾一下,在JVM中是如何将类加载到内存中的。

加载

  1. 通过全类名获取定义此类的二进制字节流
  2. 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
  3. 在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口

在步骤1中,二进制字节流可以是从文件、ZIP包、网络流、运行时计算生成等方式获取。其中的运行时计算生成就是动态代理技术的实现方式。

那么我们就可以根据不同的目标类动态的生成不同的代理类实例的字节码,即可在内存中得到不同的代理类实例。

分析 Proxy 源码,我们可以看到其内部就是通过动态生成字节码的方式来得的到代理类实例。

image-20220114220413484

动态生成的代理类又是如何实现代理的呢?

在下面这段程序中,proxyInstance 调用的 sayHello() 方法,实际上调用的是 logProxyinvoke()方法,在invoke() 内部我们又调用了目标类真正的方法,从而实现代理的功能。

HelloService target = new HelloServiceImpl();
LogProxy logProxy = new LogProxy(target);
HelloService proxyInstance = (HelloService) logProxy.newProxyInstance();
proxyInstance.sayHello();

也就是说,在 proxyInstancesayHello()方法,实际上是调用 proxyInstace.invoke(),即

// proxyInstance 代理类实例的方法
public void sayHello() {
   
    // ...
    proxyInstance.invoke(this, method, null); // method 为目标类的真正方法
    // ...
}

invoke() 方法中,method 为目标类的真正方法,那么我们如何获取到关于 method 的相关信息呢?

答案就是:通过反射

@Override
public Object invoke(Object proxy, Method method, Object
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值