Java-设计模式-08-代理模式

30 阅读5分钟

1 代理模式

代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。

举个例子来说明:张三想买某种用品,虽然他可以自己去找,但是这确实太浪费时间和精力了,或者不好意思去买。于是张三就通过中介Mark来买,Mark来帮张三,张三只是负责选择自己喜欢的的size,然后付钱就可以了。

目的:

(1)通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要复杂性;

(2)通过代理对象对原有的业务增强;

代理模式一般会有三个角色:

image.png

抽象角色:指代理角色和真实角色对外提供的公共方法,一般为一个接口

真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是真正的业务逻辑在此。

代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制都放到代理角色中处理!

而访问者不再访问真实角色,而是去访问代理角色。

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类。一般来说,被代理对象和代理对象是一对一的关系,当然一个代理对象对应多个被代理对象也是可以的。

静态代理,一对一则会出现时静态代理对象量多、代码量大,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。

2 静态代理

静态代理是指在编译时由程序员创建或使用工具生成代理类。这个代理类实现了和目标对象相同的接口,并且持有目标对象的引用。静态代理类在调用方法时,可以在调用目标对象的方法之前或之后增加额外的功能。

2.1 工作原理

  • 定义接口:定义一个公共接口,该接口由目标对象和代理对象实现。
  • 目标对象:实现该接口的具体类,包含了业务逻辑。
  • 代理对象:实现该接口的代理类,内部持有目标对象的引用。在代理对象的方法中,可以在调用目标对象的方法前后添加自定义逻辑(如日志、权限检查等)。

2.2 静态代理的示例

java
复制代码
// 定义接口
public interface Service {
    void execute();
}

// 目标对象
public class RealService implements Service {
    @Override
    public void execute() {
        System.out.println("Executing service...");
    }
}

// 代理对象
public class ServiceProxy implements Service {
    private RealService realService;

    public ServiceProxy(RealService realService) {
        this.realService = realService;
    }

    @Override
    public void execute() {
        System.out.println("Before execution...");
        realService.execute();
        System.out.println("After execution...");
    }
}

// 使用
public class Client {
    public static void main(String[] args) {
        RealService realService = new RealService();
        Service serviceProxy = new ServiceProxy(realService);
        serviceProxy.execute();
    }
}

输出

mathematica
复制代码
Before execution...
Executing service...
After execution...

2.3 静态代理的优缺点

  • 优点

    • 可以在不修改目标对象的情况下为其增加额外功能。
    • 代码简单,易于理解。
  • 缺点

    • 代码冗余:每个目标类都需要对应的代理类,增加了维护成本。
    • 不灵活:如果接口发生改变,代理类也需要修改。

3 动态代理

动态代理是在运行时生成代理类,而不是在编译时。Java 提供了 java.lang.reflect.Proxy 类和 InvocationHandler 接口来实现动态代理。动态代理能够更灵活地处理代理逻辑,因为代理类是在运行时根据需要动态生成的。

3.1 工作原理

  • 接口:动态代理通常基于接口来创建代理对象。
  • InvocationHandler:定义代理行为的接口,代理对象的所有方法调用都会转发给 InvocationHandlerinvoke 方法。
  • Proxy.newProxyInstance() :通过调用 Proxy.newProxyInstance 方法生成代理对象。

3.2 动态代理的示例

java
复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义接口
public interface Service {
    void execute();
}

// 目标对象
public class RealService implements Service {
    @Override
    public void execute() {
        System.out.println("Executing service...");
    }
}

// InvocationHandler 实现
public class ServiceInvocationHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before execution...");
        Object result = method.invoke(target, args);
        System.out.println("After execution...");
        return result;
    }
}

// 使用
public class Client {
    public static void main(String[] args) {
        RealService realService = new RealService();
        Service serviceProxy = (Service) Proxy.newProxyInstance(
                realService.getClass().getClassLoader(),
                realService.getClass().getInterfaces(),
                new ServiceInvocationHandler(realService));
        
        serviceProxy.execute();
    }
}

输出

mathematica
复制代码
Before execution...
Executing service...
After execution...

3.3 动态代理的优缺点

  • 优点

    • 灵活性高:动态代理可以在运行时动态生成代理类,不需要为每个目标类手动编写代理类。
    • 代码简洁:可以为多个目标类复用相同的代理逻辑。
  • 缺点

    • 性能开销:由于动态代理依赖于反射机制,可能会带来一定的性能开销。
    • 只能代理接口:Java 原生的动态代理只能代理实现了接口的类,不能代理普通类(但是可以使用第三方库如 CGLIB 进行类的代理)。

4 静态代理与动态代理的对比

特性静态代理动态代理
代理类生成时间编译时运行时
实现方式需要手动编写代理类通过 Proxy 类和 InvocationHandler 接口
灵活性较低,代理类与目标类紧密耦合较高,可以为任何实现了接口的类生成代理
维护成本较高,每个目标类都需要对应的代理类较低,可以复用同一个 InvocationHandler
性能高(没有反射开销)较低(有反射开销)

5 实际应用场景

  • 静态代理:适用于代理类较少且功能明确的场景,如权限控制、日志记录等简单功能。
  • 动态代理:常用于框架或库的设计中,如 Spring AOP、MyBatis 中的动态代理机制,能够灵活应对多变的需求。