掘金 后端 ( ) • 2024-04-14 20:27

highlight: a11y-dark theme: juejin

1.概述

由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。

Java中的代理按照代理类生成时机不同又分为静态代理动态代理

  • 静态代理:代理类在编译期就生成
  • 动态代理:代理类则是在Java运行时动态生成,动态代理又分为:
    • JDK代理
    • CGLIB代理。

2.静态代理

静态代理的典型结构:

  1. 接口(Interface):定义了代理类和被代理类都要实现的接口,这个接口规定了被代理对象和代理对象的行为。
  2. 被代理类(Real Subject):实现了接口的原始对象。它是真正执行业务逻辑的对象。
  3. 代理类(Proxy):实现了与被代理对象相同的接口,并在其内部持有对被代理对象的引用。代理对象在调用被代理对象的方法时可以在方法调用前后执行额外的逻辑。

案例:

如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下:

image-20240414155325838

  • 接口:卖票,约定规范。
public interface SellTickets {
    void sell();
}
  • 被代理类:火车站具有卖票功能,所以需要实现SellTickets接口。
public class TrainStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("火车站:火车票购买成功!");
    }
}
  • 代理类:用来代理购票
public class ProxyPoint implements SellTickets{
    //被代理的对象
    private TrainStation trainStation = new TrainStation();

    @Override
    public void sell() {
        System.out.println("代理点:代理购票处,需要收取服务费 1 元");
        //真正执行业务逻辑
        trainStation.sell();
        System.out.println("代理点:代理购票完成!");
    }
}
  • 测试
public class Client {
    public static void main(String[] args) {
        ProxyPoint proxyPoint = new ProxyPoint();
        proxyPoint.sell();
    }
}

结果:

代理点:代理购票处,需要收取服务费 1 元
火车站:火车票购买成功!
代理点:代理购票完成!

从上面代码中可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)。

3.JDK 动态代理

继续上面的案例,这次使用JDK动态代理。Java中提供了一个类Proxy,提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。

  • 接口
public interface SellTickets {
    void sell();
}
  • 被代理类:火车站具有卖票功能,所以需要实现SellTickets接口。
public class TrainStation implements SellTickets{
    @Override
    public void sell() {
        System.out.println("火车站:火车票购买成功!");
    }
}
  • 生成代理类:这里开始与静态代理不一样了。
public class ProxyFactory {

    //被代理的对象
    private TrainStation trainStation = new TrainStation();

    //创建代理对象的方法
    public SellTickets getProxyObject() {
        /**
         * ClassLoader loader(第一个参数):类加载器,用于加载代理类,使用真实对象的类加载器即可
         * Class<?>[] interfaces(第二个参数):真实对象所实现的接口,真实对象和代理对象实现相同的接口
         * InvocationHandler h (第三个参数):代理对象的调用处理程序,也就是对原来功能的扩展
         */
        SellTickets  instance = (SellTickets)Proxy.newProxyInstance(trainStation.getClass().getClassLoader(),
                trainStation.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * proxy:表示代理对象
                     * method:在代理对象上调用的接口方法的 Method 实例
                     * args:代理对象调用接口方法时传递的实际参数
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("JDK代理:收取服务费 1 元");
                        //调用接口方法,result 是被接口方法的返回值,如果为 void 就为 null;
                        Object result = method.invoke(trainStation, args);
                        System.out.println("JDK代理:购票成功!");
                        return result;
                    }
                });

        return instance;
    }
}

总结就是:调用 Proxy.newProxyInstance(……)方法来生成代理对象,只不过这个方法的参数比较复杂。然后直接调用 instance.sell()

public class Client {
    public static void main(String[] args) {
        //创建代理对象生产工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        //获取代理对象
        SellTickets instance= proxyFactory.getProxyObject();
        //调用代理方法
        instance.sell();
    }
}

结果:

JDK代理:收取服务费 1 元
火车站:火车票购买成功!
JDK代理:购票成功!

4.CGLIB 动态代理

如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。

CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理。CGLIB是第三方提供的包,所以需要引入jar包:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
  • 被代理类
public class TrainStation{
    public void sell() {
        System.out.println("火车站:火车票购买成功!");
    }
}
  • 生成代理类
public class ProxyFactory implements MethodInterceptor {

    //被代理对象
    public TrainStation trainStation = new TrainStation();

    //生成代理对象
    public TrainStation getProxyObject() {
        //创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数
        Enhancer enhancer = new Enhancer();
        //设置父类的字节码对象
        enhancer.setSuperclass(trainStation.getClass());
        //设置回调函数
        enhancer.setCallback(this);
        //创建代理对象
        TrainStation obj = (TrainStation) enhancer.create();
        return obj;
    }

    /**
     *  obj : 代理对象
     *  method : 真实对象中的方法的 Method 实例
     *  args : 实际参数
     *  methodProxy:代理对象中的方法的 method 实例
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("CGLIB代理:收取服务费 1 元");
        //执行真实对象中的方法
        Object invoke = method.invoke(trainStation, args);
        System.out.println("CGLIB代理:购票成功!");
        return invoke;
    }
}

结果:

CGLIB代理:收取服务费 1 元
火车站:火车票购买成功!
CGLIB代理:购票成功!

5.多个方法

如果被代理对象中有多个方法怎么办嘞?对于静态代理,需要在代理类里面添加全部方法。但是对于动态代理来说,根本不需要这么复杂,只管调用就好了。JDK 与 CGLIB 代理一样的。

public class TrainStation{

    public void sell2(){
        System.out.println("火车站2号站:火车票购买成功!");
    }

    public void sell() {
        System.out.println("火车站:火车票购买成功!");
    }
}

假设代理类里面多了一个sell2方法,对于动态代理来说,不需要更改代理类。

    public static void main(String[] args) {
        //创建代理对象生产工厂
        ProxyFactory proxyFactory = new ProxyFactory();
        //代理类
        TrainStation proxyObject = proxyFactory.getProxyObject();
        proxyObject.sell();
        proxyObject.sell2();
    }

直接调用:

CGLIB代理:收取服务费 1 元
火车站:火车票购买成功!
CGLIB代理:购票成功!
CGLIB代理:收取服务费 1 元
火车站2号站:火车票购买成功!
CGLIB代理:购票成功!

6.动态代理 VS 静态代理

动态代理与静态代理的区别:

  • 静态代理是编译期确定的代理类,但是动态代理却是运行期确定的代理类。

  • 接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(invoke方法)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

7.JDK动态代理 VS CGLIB动态代理

基于接口 vs 基于类

  • JDK 动态代理只能代理实现了接口的类。它是通过 java.lang.reflect.Proxy 类来实现的,要求被代理的类必须实现一个或多个接口。代理对象在运行时动态生成,并实现了这些接口,在代理对象的方法调用时会被转发到 InvocationHandler 接口的实现类中。
  • CGLIB 动态代理则可以代理没有实现接口的类。它是通过继承目标类并覆盖其中的方法来实现的,因此被代理的类不需要实现接口。CGLIB 动态代理通过创建目标类的子类,并覆盖其中的方法来实现代理,因此称为基于类的代理。

适用范围

  • JDK 动态代理适用于那些已经实现了接口的类,且可以使用接口来进行代理的场景。由于 Java 中的单继承限制,对于没有实现接口的类,JDK 动态代理无法实现代理。
  • CGLIB 动态代理适用于任何类,无论是否实现了接口。它通过生成目标类的子类来实现代理,因此可以对没有实现接口的类进行代理。