【设计模式】代理模式详解

1.简介

代理模式 是常用的Java设计模式,该模式的特点是代理类与委托类共享相同的接口。代理类主要负责预处理消息、过滤消息、将消息转发给委托类,并在事后处理消息等。代理类与委托类之间通常存在关联关系,一个代理类对象与一个委托类对象关联。代理类对象本身不真正实现服务,而是通过调用委托类对象的相关方法来提供特定的服务。

代理模式主要包括以下角色:

  • 抽象主题(Subject):定义代理类和委托类(RealSubject)的共同接口。这个接口规定了代理类和委托类必须实现的方法,代理类可以通过这个接口来调用委托类的方法。
  • 真实主题(RealSubject):实现抽象主题,定义委托类的操作。它包含了实际的业务逻辑,是客户端实际需要调用的对象。
  • 代理类(Proxy):实现抽象主题,持有对委托类的引用,并在其方法被调用时进行控制。代理类在调用委托类的方法前后可以添加一些额外的功能,如日志记录、权限控制、事务处理等。

2.静态代理

静态代理: 在编译时期确定代理类和目标类的关系,代理类和目标类都要实现同一个接口。

定义一个简单的例子:假如一个租客需要租房子,他可以直接通过房东(委托类) 去租房,也可以经过中介(代理类) 去租房。房东(realsubject)和中介(proxy)都需要实现subject接口实现房子出租。

  1. 确定接口具体行为

首先创建一个Person接口。这个接口是房东和中介的共同接口,租房行为可以被中介代理。

java 复制代码
public interface Person {
    // 出租房子
    void hire();
}
  1. 编写委托类业务逻辑

创建一个委托类,实现subject接口,并编写业务逻辑

java 复制代码
public class Landlord implements Person{
	// 房东直售
    @Override
    public void hire() {
        System.out.println("出租,收款1000元");
    }
}
  1. 代理类增强方法

创建一个代理类,同样实现subject接口,对委托类的方法进行增强

java 复制代码
public class Agency implements Person{

    private final Landlord landlord;

    public Agency(Landlord landlord) {
        this.landlord = landlord;
    }
	// 中介出租,额外收取费用
    @Override
    public void hire() {
        System.out.println("开始办理租房手续");
        landlord.hire();
        System.out.println("额外收取中介费200元");
    }
}
  1. 测试类使用代理对象
java 复制代码
public class Main {
    public static void main(String[] args) {
        // 获取代理对象
        Landlord landlord = new Landlord();
        Agency agency = new Agency(landlord);
        // 使用代理方法
        agency.hire();
    }
}

输出结果如下,可以发现对方法进行了增强

3.动态代理

动态代理: 在程序运行时动态生成代理类(subject的实现类)。

相比于静态代理, 动态代理的优势在于其较高的灵活性和代码复用性。同一个动态代理处理器可以代理多个目标对象,而静态代理则需要创建大量的代理类。

在Java中,可以通过JDK和CGLIB实现动态代理。

3.1.JDK动态代理

实现原理

JDK动态代理:在java的java.lang.reflect包下提供了Proxy类和InvocationHandler接口,利用这两个类和接口,可以在运行时动态生成指定接口的实现类

Proxy类就是用来创建一个代理对象的类,在JDK动态代理中我们需要使用其newProxyInstance方法。

java 复制代码
public static Object newProxyInstance(
    ClassLoader loader, 
    Class<?>[] interfaces, 
    InvocationHandler h);

这个方法的作用就是创建一个代理类对象,它接收以下三个参数:

  • loader:一个ClassLoader对象,指定哪个ClassLoader将加载生成的代理类。
  • interfaces:一个Interface对象数组,定义代理对象实现的一组接口,代理类可以调用这些接口中声明的所有方法。
  • h:一个InvocationHandler对象,指定代理对象的方法调用将关联到哪个InvocationHandler对象,由它处理实际的方法调用。

InvocationHandler接口提供了一个invoke方法,当代理对象调用方法时,invoke方法会被调用。通过实现这个接口,可以在方法调用前后添加自定义逻辑。

java 复制代码
/**
    * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
    * method:我们所要调用某个对象真实的方法的Method对象
    * args:指代代理对象方法传递的参数
    */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
代码实现
  1. 确定接口具体行为

这里我们设计两个接口

java 复制代码
public interface HouseServiceA {
    // 出租房子
    void hire();
}
java 复制代码
public interface HouseServiceB {
    // 转租房子
    void sublet();
}
  1. 编写委托类业务逻辑

委托类实现这两个接口,并且定义具体的业务逻辑

java 复制代码
public class HouseServiceImpl implements HouseServiceA, HouseServiceB {

    @Override
    public void hire() {
        System.out.println("出租房子");
    }

    @Override
    public void sublet() {
        System.out.println("转租房子");
    }
}
  1. 编写代理工厂代码

代理工厂负责在运行时动态生成代理类,需要实现InvocationHandler接口重写invoke方法来做方法增强,使用Proxy类创建代理对象。

java 复制代码
/**
 * JDK动态代理实现InvocationHandler接口
 */
public class HouseFactory implements InvocationHandler {
    private Object target;

    //定义获取代理对象的方法(将目标对象传入进行代理)
    public Object getJDKProxy(Object target){
        //为目标对象target赋值
        this.target = target;
        //JDK动态代理只能针对实现了接口的类进行代理,因此需要传递接口的class
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class<?>[]{HouseServiceA.class, HouseServiceB.class},
                this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK动态代理开始");
        // 调用invoke方法,result存储该方法的返回值
        Object result = method.invoke(target, args);
        System.out.println("JDK动态代理结束");
        return result;
    }
}
  1. 测试类使用代理对象

在实际使用时,只需要将工厂类生成的代理对象转为需要的代理类,即可实现同时代理多个接口的方法。

java 复制代码
public class Main {
    public static void main(String[] args) {
        // 创建代理类工厂
        HouseFactory factory = new HouseFactory();
        // 动态生成接口A的代理类
        HouseServiceA houseProxyA = (HouseServiceA) factory.getJDKProxy(new HouseServiceImpl());
        houseProxyA.hire();
        System.out.println("=====================");
        // 动态生成接口B的代理类
        HouseServiceB houseProxyB = (HouseServiceB) factory.getJDKProxy(new HouseServiceImpl());
        houseProxyB.sublet();
    }
}

返回结果:

3.2.CGLIB动态代理

实现原理

CGLIB动态代理:依赖于ASM下的Enhancer类和MethodInterceptor接口,可以在运行时动态生成目标类的子类

Enhancer类是用来创建代理对象的类。在CGLIB动态代理中,我们需要使用其create方法。

java 复制代码
public class Enhancer {
    public Object create();
    // 其他方法
}

这个方法的作用是创建一个代理类对象,通常还需要设置以下几个属性:

  • setSuperclass:设置被代理的目标类,CGLIB通过生成目标类的子类来实现代理。
  • setCallback:设置回调接口,用于处理代理对象的方法调用。

MethodInterceptor接口提供了一个intercept方法,当代理对象调用方法时,intercept方法会被调用。通过实现这个接口,可以在方法调用前后添加自定义逻辑。

java 复制代码
/**
    * obj: 代理对象
    * method: 被代理的方法
    * args: 方法的参数
    * proxy: 用于调用父类方法的代理
    */
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable;

与JDK动态代理不同,CGLIB代理不需要目标类实现接口。CGLIB通过生成目标类的子类并重写方法来实现代理,因此它可以代理没有实现接口的类。

代码实现
  1. 首先导入依赖
xml 复制代码
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
  1. 编写委托类业务逻辑(无需实现接口)
java 复制代码
public class HouseServiceImpl {

    public void hire() {
        System.out.println("出租房子");
    }

    public void sublet() {
        System.out.println("转租房子");
    }
}
  1. 测试类使用代理对象
java 复制代码
public class Main {
    public static void main(String[] args) {
        HouseFactory factory = new HouseFactory();
        HouseServiceImpl cglibProxy = (HouseServiceImpl) factory.getCglibProxy(new HouseServiceImpl());
        cglibProxy.hire();
        System.out.println("=====================");
        cglibProxy.sublet();
    }
}

返回结果:

4.总结

静态代理

实现方式:

  • 由程序员显式编写代理类。代理类在编译期确定,编译前就存在代理类的字节码文件。
  • 需要实现与目标对象相同的接口,且在代理类中显式调用目标对象的方法。

优点:

  • 结构简单,容易理解。

缺点:

  • 每增加一个接口,都需要编写对应的代理类,代码量大,维护成本高。静态代理类在编译期生成,灵活性差。

JDK动态代理

实现方式:

  • 使用java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。
  • 代理类在运行时动态生成,不需要显式编写代理类。

优点:

  • 代理类在运行时生成,增加了代码的灵活性和可维护性。

缺点:

  • 只能代理实现了接口的类,不能代理没有实现接口的类。

CGLIB动态代理

实现方式:

  • 使用CGLIB(Code Generation Library),依赖ASM字节码生成框架。
  • 代理类在运行时动态生成,不需要显式编写代理类。

优点:

  • 不要求目标类实现接口,可以代理普通的类。
  • 性能通常比JDK动态代理更高,尤其在代理大量方法调用时更为显著。

缺点:

  • 不能代理final类和final方法。

适用场景:

  • 静态代理:需要手动编写代理类,适用于简单的场景,但不够灵活,维护成本高。

  • JDK动态代理:适用于实现了接口的类,代理类在运行时生成,灵活性高,但只能代理接口。

  • CGLIB动态代理 :适用于没有实现接口的类,性能优于JDK动态代理,但不能代理final类和final方法,且使用复杂度稍高。

相关推荐
晨米酱15 小时前
JavaScript 中"对象即函数"设计模式
前端·设计模式
数据智能老司机20 小时前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机21 小时前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机21 小时前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机21 小时前
精通 Python 设计模式——性能模式
python·设计模式·架构
使一颗心免于哀伤21 小时前
《设计模式之禅》笔记摘录 - 21.状态模式
笔记·设计模式
数据智能老司机2 天前
精通 Python 设计模式——创建型设计模式
python·设计模式·架构
数据智能老司机2 天前
精通 Python 设计模式——SOLID 原则
python·设计模式·架构
烛阴2 天前
【TS 设计模式完全指南】懒加载、缓存与权限控制:代理模式在 TypeScript 中的三大妙用
javascript·设计模式·typescript
李广坤2 天前
工厂模式
设计模式