代理模式(Proxy Pattern)

代理模式(Proxy Pattern)

代理模式是一种结构型设计模式,它为其他对象提供一个代理,以控制对这个对象的访问。代理模式通过引入一个代理对象,可以在不改变原始对象的情况下,增加额外的功能或控制访问。

模式结构

  1. 服务接口(Subject)
    定义服务接口,代理和真实服务对象都必须实现该接口。
  2. 真实服务类(Real Subject)
    提供具体的业务逻辑,是代理对象所代表的真实对象。
  3. 代理类(Proxy)
    实现服务接口,并持有一个真实服务对象的引用。代理类可以在调用真实服务对象之前或之后执行额外的操作。
  4. 客户端(Client)
    通过服务接口与代理对象交互,代理对象将请求转发给真实服务对象。

代码示例

1. 服务接口(Subject)

// 服务接口:定义服务接口
interface CommandExecutor {
    void run();
}

2. 真实服务类(Real Subject)

// 真实服务类:提供具体的业务逻辑
class RealCommandExecutor implements CommandExecutor {
    @Override
    public void run() {
        System.out.println("命令执行中 ...");
    }
}

3. 代理类(Proxy)

// 静态代理类:实现服务接口,并持有一个真实服务对象的引用
class CommandExecutorProxy implements CommandExecutor {
    private final CommandExecutor commandExecutor;

    public CommandExecutorProxy(CommandExecutor commandExecutor) {
        this.commandExecutor = commandExecutor;
    }

    @Override
    public void run() {
        System.out.println("代理启动 ...");
        commandExecutor.run();
        System.out.println("代理结束 ...");
    }
}

4. 动态代理工厂(Dynamic Proxy Factory)

// 动态代理工厂:创建动态代理对象
class DynamicProxyFactory {
    private final Object target;

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

    public Object getProxyInstance() {
        return Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("动态代理启动 ...");
                        Object res = method.invoke(target, args);
                        System.out.println("动态代理结束 ...");
                        return res;
                    }
                }
        );
    }
}

5. CGLib 代理工厂(CGLib Proxy Factory)

// CGLib 代理工厂:创建 CGLib 代理对象
class CGLibProxyFactory implements MethodInterceptor {
    private final Object target;

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

    public Object getProxyInstance() {
        Enhancer en = new Enhancer();
        en.setSuperclass(target.getClass());
        en.setCallback(this);
        return en.create();
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLIB 代理启动 ...");
        Object invoke = method.invoke(target, objects);
        System.out.println("CGLIB 代理结束 ...");
        return invoke;
    }
}

6.调用示例

public class ProxyPattern {
    public static void main(String[] args) {
        System.out.println("---------- 静态代理 ----------");
        RealCommandExecutor commandExecutor = new RealCommandExecutor();
        CommandExecutorProxy proxy = new CommandExecutorProxy(commandExecutor);
        proxy.run();

        System.out.println("---------- 动态代理 ----------");
        CommandExecutor dynamicProxy = (CommandExecutor) new DynamicProxyFactory(commandExecutor).getProxyInstance();
        dynamicProxy.run();

        System.out.println("---------- CGLIB 代理 ----------");
        CommandExecutor cglibProxy = (CommandExecutor) new CGLibProxyFactory(commandExecutor).getProxyInstance();
        cglibProxy.run();
    }
}

输出结果

---------- 静态代理 ----------
代理启动 ...
命令执行中 ...
代理结束 ...

---------- 动态代理 ----------
动态代理启动 ...
命令执行中 ...
动态代理结束 ...

---------- CGLIB 代理 ----------
CGLIB 代理启动 ...
命令执行中 ...
CGLIB 代理结束 ...

应用场景

  1. 延迟初始化(虚拟代理)
    当需要延迟初始化一个重量级对象时,可以使用代理模式。代理可以在真正需要时才创建和初始化对象。
  2. 访问控制(安全代理)
    当需要控制对某个对象的访问权限时,可以使用代理模式。代理可以在访问对象之前进行权限检查。
  3. 远程代理
    当对象位于远程服务器上时,可以使用代理模式。代理负责处理网络通信,客户端无需关心远程调用的细节。
  4. 日志记录(日志代理)
    当需要记录对对象的访问日志时,可以使用代理模式。代理可以在调用对象之前或之后记录日志。
  5. 缓存(缓存代理)
    当需要缓存对象的请求结果时,可以使用代理模式。代理可以在调用对象之前检查缓存,避免重复计算。

优缺点

优点

  • 代理模式能将代理对象与真实被调用的目标对象分离。
  • 可以扩展目标对象的功能。
  • 即使服务对象还未准备好或不存在, 代理也可以正常工作。
  • 你可以在不对服务或客户端做出修改的情况下创建新代理,满足开闭原则。
  • 可以很好的保护目标对象。

缺点

  1. 复杂性增加
    代理模式引入了额外的类和接口,增加了代码的复杂性。
  2. 性能开销
    代理模式可能会引入额外的性能开销,特别是在频繁调用时。

总结

代理模式通过引入一个代理对象,可以在不改变原始对象的情况下,增加额外的功能或控制访问。它适用于需要延迟初始化、访问控制、远程调用、日志记录和缓存等场景。尽管它可能会增加代码的复杂性,但其优点在于提高了代码的灵活性和可扩展性。代理模式在需要控制对象访问或增加额外功能的场景中非常有用。