设计模式系列(四)代理模式

1. 定义

代理模式也叫为委托模式,为其他对象提供⼀种代理以控制对这个对象的访问。它的作⽤就是通过提供⼀个代理类, 让我们在调⽤目标⽅法的时候, 不再是直接对目标⽅法进行调⽤, 而是通过代理类间接调用。
在某些情况下, ⼀个对象不适合或者不能直接引用另⼀个对象, 而代理对象可以在客户端和目标对象之间起到中介的作用。代理模式可以在不修改被代理对象的基础上, 通过扩展代理类, 进行一些功能的附加与增强
使用代理之前:
image.png
使用代理之后:
image.png
代理模式的主要角色:

  1. Subject:业务接⼝类。可以是抽象类或者接⼝(不⼀定有)
  2. RealSubject:业务实现类。具体的业务执⾏, 也就是被代理对象
  3. Proxy:代理类。RealSubject的代理

根据代理的创建时期, 代理模式分为静态代理和动态代理。

2. 静态代理

静态代理:由程序员创建代理类或特定⼯具自动⽣成源代码再对其编译, 在编译时代理类的.class ⽂件就已经存在了。
静态代理实现步骤:

  1. 定义一个接口及其实现类;
  2. 创建一个代理类同样实现这个接口
  3. 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。

我们以房租租赁为例,进行代码演示:

  1. 定义接口 (定义房东要做的事情, 也是中介需要做的事情):
public interface HouseSubject {
    void rentHouse();
}
  1. 实现接口(房东出租房子)
public class RealHouseSubject implements HouseSubject{
    @Override
    public void rentHouse() {
        System.out.println("我是房东,我要出租房子");
    }
}
  1. 代理(中介, 帮房东出租房⼦)
public class HouseProxy implements HouseSubject{
    private HouseSubject houseSubject;

    public HouseProxy(HouseSubject houseSubject) {
        this.houseSubject = houseSubject;
    }

    @Override
    public void rentHouse() {
        // 调用方法之前,可以添加自己的操作
        System.out.println("我是中介,我开始代理");
        houseSubject.rentHouse();
        // 调用方法之后,也可以添加自己的操作
        System.out.println("我是中介,我代理结束");
    }
}
  1. 实际使用
public class Main {
    public static void main(String[] args) {
        HouseSubject houseSubject = new HouseProxy(new RealHouseSubject());
        houseSubject.rentHouse();
    }
}

运行上述代码:
image.png
从上述程序可以看出, 虽然静态代理也完成了对⽬标对象的代理, 但是由于代码都写死了, 对目标对象的每个方法的增强都是⼿动完成的,非常不灵活(比如接口一旦新增加方法,目标对象和代理对象都要进行修改)且麻烦(需要对每个目标类都单独写一个代理类),所以日常开发几乎看不到静态代理的场景。
接下来新增需求: 中介又新增了其他业务: 代理房屋出售
此时,我们需要对上述代码进行修改。

// 接⼝定义修改
public interface HouseSubject {
    void rentHouse();
    void saleHouse();
}

// 接口实现修改
public class RealHouseSubject implements HouseSubject{
    @Override
    public void rentHouse() {
        System.out.println("我是房东,我要出租房子");
    }

    @Override
    public void saleHouse() {
        System.out.println("我是房东,我要出售房子");
    }
}

// 代理类修改
public class HouseProxy implements HouseSubject{
    private HouseSubject houseSubject;

    public HouseProxy(HouseSubject houseSubject) {
        this.houseSubject = houseSubject;
    }

    @Override
    public void rentHouse() {
        System.out.println("我是中介,我开始代理");
        houseSubject.rentHouse();
        System.out.println("我是中介,我代理结束");
    }

    @Override
    public void saleHouse() {
        System.out.println("我是中介,我开始代理出售房子");
        houseSubject.saleHouse();
        System.out.println("我是中介,我结束代理出售房子");
    }
}

从上述代码可以看出, 我们修改接⼝(Subject)和业务实现类(RealSubject)时, 还需要修改代理类(Proxy)。同样的, 如果有新增接⼝(Subject)和业务实现类(RealSubject), 也需要对每⼀个业务实现类新增代理类(Proxy)。

总结: 静态代理是一种在编译时就已经确定代理关系的代理方式。在静态代理中,代理类和被代理类都要实现同一个接口或继承同一个父类,代理类中包含了被代理类的实例,并在调用被代理类的方法前后执行相应的操作。静态代理的优点是实现简单,易于理解和掌握,但是它的缺点是需要为每个被代理类编写一个代理类,当被代理类的数量增多时,代码量会变得很大。

3. 动态代理

动态代理:在程序运行时, 运用反射机制动态创建而成。
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建⼀个代理类, 而是把这个创建代理对象的⼯作推迟到程序运行时由JVM来实现。也就是说动态代理在程序运行时, 根据需要动态创建⽣成。
Java也对动态代理进⾏了实现, 并给我们提供了⼀些API, 常见的实现⽅式有两种:

  1. JDK动态代理
  2. CGLIB动态代理

3.1. JDK动态代理

JDK 动态代理是一种使用 Java 标准库中的 java.lang.reflect.Proxy 类来实现动态代理的技术。JDK 动态代理要求被代理的类必须实现一个接口,它通过反射来接收被代理的类,并使用 InvocationHandler 接口和 Proxy 类实现代理。

3.1.1 介绍

在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。
Proxy 类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        ......
    }

这个方法一共有 3 个参数:

  1. loader :类加载器,用于加载代理对象。
  2. interfaces: 被代理类实现的一些接口;
  3. h: 实现了 InvocationHandler 接口的对象

要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。 当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的** invoke 方法**来调用。

public interface InvocationHandler {

    /**
     * 当你使用代理对象调用方法的时候实际会调用到这个方法
     */
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

invoke() 方法有下面三个参数:

  1. proxy :动态生成的代理类
  2. method : 与代理类对象调用的方法相对应
  3. args : 当前 method 方法的参数

也就是说:你通过Proxy 类的 newProxyInstance() 创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler 接口的类的 invoke()方法。 你可以在 invoke() 方法中自定义处理逻辑,比如在方法执行前后做什么事情。
JDK动态代理类使用步骤:

  1. 定义一个接口及其实现类。
  2. 自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑。
  3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象。

3.1.2. 代码实现:

具体来说,当使用 JDK 动态代理时,需要定义一个实现InvocationHandler 接口的类,并在该类中实现代理类的具体逻辑。然后,通过 Proxy.newProxyInstance() 方法来创建代理类的实例。该方法接受三个参数:类加载器、代理类要实现的接口列表和 InvocationHandler 对象。

  1. 定义接口
public interface HouseSubject {
    void rentHouse();
    void saleHouse();
}
  1. 实现接口
public class RealHouseSubject implements HouseSubject{
    @Override
    public void rentHouse() {
        System.out.println("我是房东,我要出租房子");
    }

    @Override
    public void saleHouse() {
        System.out.println("我是房东,我要出售房子");
    }
}
  1. 定义一个JDK动态代理类
@Slf4j
public class JDKInvocationHandler implements InvocationHandler {
    // 目标对象,即被代理的对象
    private Object target;

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

    /**
     *  调用被代理的方法,并对被代理类的方法进行功能增强
     * @param proxy  被代理的对象
     *
     * @param method 被拦截的方法
     *
     * @param args  方法参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用方法之前,执行的操作
        log.info("JDK动态代理开始");
        // 通过反射调用目标类的方法
        Object result = method.invoke(target, args);
        // 调用方法之后,执行的操作
        log.info("JDK动态代理结束");
        return result;
    }
}

invoke() 方法: 当我们的动态代理对象调用原生方法的时候,最终实际上调用到的是 invoke() 方法,然后 invoke() 方法代替我们去调用了被代理对象的原生方法。

  1. 创建⼀个代理对象并使⽤
public class DynamicMain {
    public static void main(String[] args) {
        // 被代理类
        HouseSubject target = new RealHouseSubject();
        // 生成一个代理对象(通过被代理类、被代理实现的接口、方法调用处理器来创建)
        HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),// 目标类的类加载器
                new Class[]{HouseSubject.class},// 代理需要实现的接口,可指定多个
                new JDKInvocationHandler(target)// 代理对象对应的自定义 InvocationHandler
                );
        proxy.rentHouse();
        proxy.saleHouse();
    }
}

JDK 动态代理的优点是实现简单,易于理解和掌握,但是它的缺点是只能代理实现了接口的类,无法代理没有实现接口的类。
运行结果:
image.png

3.2. CGLIB动态代理

CGLIB(Code Generation Library)是⼀个基于ASM的字节码⽣成库,它允许我们在运行时对字节码进行修改和动态⽣成。它会在运行时动态地生成某个类的子类,通过继承的方式实现代理。在 CGLIB 动态代理中,代理类不需要实现接口,而是通过继承被代理类来实现代理。如果目标类没有实现接口,Spring AOP 会选择使用 CGLIB 来动态代理目标类。

3.2.1. 介绍

在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。
你需要自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法。

public interface MethodInterceptor extends Callback{
    // 拦截被代理类中的方法
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
  1. obj : 被代理的对象(需要增强的对象)
  2. method : 被拦截的方法(需要增强的方法)
  3. args : 方法参数
  4. proxy : 用于调用原始方法

你可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。
CGLIB动态代理类的实现步骤:

  1. 定义一个类。
  2. 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似。
  3. 通过 Enhancer 类的 create()创建代理类。

3.2.2. 代码实现

具体来说,当使用 CGLIB 动态代理时,需要定义一个继承被代理类的子类,并在该子类中实现代理类的具体逻辑。然后,通过 Enhancer.create() 方法来创建代理类的实例。该方法接受一个类作为参数,表示要代理的类。

  1. 添加依赖

和JDK 动态代理不同, CGLIB(Code Generation Library) 实际是属于⼀个开源项⽬,如果你要使⽤它的话,需要⼿动添加相关依赖。

<dependency>
  <groupId>cglib</groupId>
  <artifactId>cglib</artifactId>
  <version>3.3.0</version>
</dependency>
  1. 定义一个类
public class RealHouseSubject {
    public void rentHouse() {
        System.out.println("我是房东,我要出租房子");
    }

    public void saleHouse() {
        System.out.println("我是房东,我要出售房子");
    }
}
  1. 自定义 MethodInterceptor(方法拦截器)
@Slf4j
public class CGLIBMethodInterceptor implements MethodInterceptor {
    // 被代理对象
    private Object target;

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

    /**
     *  调用被代理类的方法,并对被代理类的方法进行功能增强
     * @param o  被代理的对象(需要增强的对象)
     * @param method  被拦截的方法(需要增强的方法)
     * @param objects 方法参数
     * @param methodProxy  用于调用原始方法
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 代理增强的内容
        log.info("CGLIB动态代理开始");
        // 通过反射调用被代理类的方法
        Object result = methodProxy.invoke(target, objects);
        // 代理增强的内容
        log.info("CGLIB动态代理结束");
        return result;
    }
}
  1. 创建代理类并使用
public class DynamicMain {
    public static void main(String[] args) {
        // 被代理对象
        RealHouseSubject target = new RealHouseSubject();
        // 生成一个代理对象
        RealHouseSubject proxy = (RealHouseSubject) Enhancer.create(
                target.getClass(), new CGLIBMethodInterceptor(target));
        proxy.rentHouse();
        proxy.saleHouse();
    }
}

运行结果:
image.png

总结:动态代理是一种在运行时动态生成代理类的代理方式。在动态代理中,代理类不需要实现同一个接口或继承同一个父类,而是通过 Java 反射机制动态生成代理类,并在调用被代理类的方法前后执行相应的操作。动态代理的优点是可以为多个被代理类生成同一个代理类,从而减少了代码量,但是它的缺点是实现相对复杂,需要了解 Java 反射机制和动态生成字节码的技术。

3.3.JDK动态代理和CGLIB动态代理的区别

  • JDK 动态代理基于接口,要求目标对象实现接口或者直接代理接口;CGLIB 动态代理基于类,可以代理没有实现接口的目标对象。
  • JDK 动态代理使用 java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler 来生成代理对象;CGLIB 动态代理使用 CGLIB 库来生成代理对象。
  • JDK 动态代理生成的代理对象是目标对象的接口实现;CGLIB 动态代理生成的代理对象是目标对象的子类。
  • JDK 动态代理性能相对较高,生成代理对象速度较快;CGLIB 动态代理性能相对较低,生成代理对象速度较慢。
  • CGLIB 动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final类型的类和方法;JDK 动态代理可以代理任意类。
  • 26
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值