Java设计模式-代理模式

429 阅读8分钟

一、什么是代理模式

说明

Java 代理模式是一种设计模式,它为其他对象提供一种代理以控制对这个对象的访问。代理模式的主要作用是在不修改原始类代码的情况下,增加新的功能或者控制对原始类的访问。代理模式实现有两种方式,静态代理和动态代理,根据实际业务场景选择对应的方式。

类图

whiteboard_exported_image.png

二、静态代理

描述

静态代理是由我们手动创建代理类的代理方式。代理类和被代理类通常实现相同的接口,代理类持有被代理类的实例,并在调用方法时添加额外的逻辑。

代码示例

下面我们举例买票场景,客户委托三方代理帮我们购票。此时 Subject 就是购票接口,定义具体行为。RealSubject 就是客户,真正需要票的。Proxy 就是三方代理,帮助客户来购买车票。下面就以代码来进行实现,帮忙我们更深的了解其精髓。

Subject-定义买票接口

package proxy;

/**
 * 购票接口
 */
public interface Tickets {
    /**
     * 购买的票数.
     *
     * @param num 票数
     */
    void buy(Integer num);
}

RealSubject-定义购票客户

package proxy;

/**
 * 客户端
 */
public class Customer implements Tickets {

    @Override
    public void buy(Integer num) {
        System.out.println("Customer buy ticket size: " + num);
    }
}

Proxy-定义三方代理类

package staticprocxy;

/**
 * 代理类,实现买票接口,持有真实实现类。
 */
public class CustomerProxy implements Tickets {

    private Customer mCustomer;

    public CustomerProxy(Tickets tickets) {
        // 只代理CustomerImpl的需求
        if (tickets.getClass() == Customer.class) {
            this.mCustomer = (Customer) tickets;
        }
    }

    /**
     * 代理客户买票行为.
     *
     * @param num 票数
     */
    @Override
    public void buy(Integer num) {
        mCustomer.buy(num);
    }
}

Main-代码调用

package staticprocxy;

public class Main {
    public static void main(String[] args) {
        //构建客户,并提出需求。
        Tickets tickets = new Customer();
        //构建代理,代理客户的需求。
        Tickets proxy = new CustomerProxy(tickets);
        //完成买票动作,代理完成客户的需求。
        proxy.buy(2);
    }
}

捕获1.PNG

优缺点

优点

  1. 结构清晰:代理类和真实主题之间的关系在编译期间就已明确,易于理解和维护。
  2. 扩展性:代理类可以在调用真实对象的方法前后加入额外的操作,例如预处理、后处理、日志记录、权限检查、性能监控等。
  3. 封装增强:代理类可以对真实对象的功能进行扩展而不影响原有类的设计,遵循开闭原则。

缺点

  1. 侵入性强:每个真实主题都需要对应的代理类,当需要代理的类数量较多时,会增加大量的代理类代码,导致系统膨胀。
  2. 灵活性差:一旦接口发生改变,所有实现了该接口的静态代理类都可能需要修改。
  3. 工作量大:对于新增加的真实主题,需要手动编写新的代理类,无法做到自动化或动态化。
  4. 难以应对复杂场景:如果需要对某一类的多个对象实施不同的代理策略,则静态代理很难实现,因为静态代理通常是针对某一类对象的统一代理策略。

所以,针对复杂或者灵活性较高的场景,静态代理就无法满足,这时需要动态代理来介入。

三、动态代理

描述

动态代理是 Java 提供的一种更为灵活的方式,它利用 java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler 接口来动态地生成代理类。动态代理不需要手动创建代理类,而是运行时动态地生成。

下面我们将使用动态代理的方式,实现上述买票的需求,主要区别是 TicketProxy 和 Main 两个类的实现。

代码示例

Subject-定义买票接口

package staticprocxy;

/**
 * 购票接口
 */
public interface Tickets {
    /**
     * 购买的票数.
     *
     * @param num 票数
     */
    void buy(Integer num);
}

RealSubject-定义购票客户

package staticprocxy;

/**
 * 客户端
 */
public class Customer implements Tickets {

    @Override
    public void buy(Integer num) {
        System.out.println("Customer buy ticket size: " + num);
    }
}

Proxy-定义三方代理类

package proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 代理类,实现买票接口,持有真实实现类
 */
public class CustomerProxy implements InvocationHandler {

    private Tickets mTickets;

    public CustomerProxy(Tickets tickets) {
        this.mTickets = tickets;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //实际场景中,在执行方法之前可以织入相应的业务代码。
        System.out.println("invoke buy before");
        //通过反射动态调用Tickets的方法,也就是buy方法。
        Object invoke = method.invoke(mTickets, args);
        //同理执行方法之后也可以进行业务操作。
        System.out.println("invoke buy after");
        return invoke;
    }
}

Main-代码调用

package proxy;

import java.lang.reflect.Proxy;

public class MainProxy {
    public static void main(String[] args) {
        //创建接口
        Tickets tickets = new CustomerImpl();
        //创建接口类加载器
        ClassLoader classLoader = Tickets.class.getClassLoader();
        //创建接口数组
        Class[] classes = {Tickets.class};
        //实例化代理类
        TicketProxy proxy = new TicketProxy(tickets);
        //创建Tickets对象
        Tickets exe = (Tickets) Proxy.newProxyInstance(classLoader, classes, proxy);
        //实现购票
        exe.buy(2);
    }
}


捕获.PNG

原理分析

接下来我们对动态代理的实现来进行分析,JDK 如何帮忙我们实现“看不到”的逻辑。在上述例子中,我们关注一下 Proxy.newProxyInstance(classLoader, classes, proxy) 方法。

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

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     * 查找或生成指定的代理类。
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     * 使用指定类调用处理程序调用其构造函数。
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

通过上述源码,我们主要关注 Class<?> cl = getProxyClass0(loader, intfs) 这行代码。其实源码注释也明确表明该方法会查找或者生成制定的代理类,该代理类是 JDK 在编译期间帮我们生成的,我们是无法直接查看的,但是可以通过下面的方式将代理类给打印出来。

byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Customer.class.getInterfaces());
String path = "D:\IDEAWorkSpace\TestKotlin\RealCustomerProxy.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
    fos.write(classFile);
    fos.flush();
    System.out.println("CustomerProxy代理类写入成功");
} catch (Exception e) {
    System.out.println("CustomerProxy代理类写入失败");
}

执行完上述代码,我们拿到真正的代理类 RealCustomerProxy,如下所示:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Tickets;

public final class $Proxy0 extends Proxy implements Tickets {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    //在这里,我们发现了定义的buy方法,我们重点关注。
    public final void buy(Integer var1) throws  {
        try {
            // 在这里h就是InvocationHandler,通过调用InvocationHandler的invoke方法,
            // 回过头来CustomerProxy类实现InvocationHandler接口并重写invoke,
            // 其实调用的就是Tickets的buy方法。
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            // 通过反射的方式,拿到buy方法。
            m3 = Class.forName("proxy.Tickets").getMethod("buy", Class.forName("java.lang.Integer"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

所以,动态代理代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的 invoke 方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。

优缺点

优点

  1. 减少冗余代码:与静态代理相比,动态代理不需要为每一个业务接口创建单独的代理类,它可以在运行时动态地创建代理类及其对象,大大减少了代理类的数量和编码工作。
  2. 灵活性高:动态代理可以根据需要在运行时动态决定是否需要创建代理以及代理的行为,这意味着我们可以更加灵活地添加、修改或删除代理逻辑,无需重新编译源代码。
  3. 解耦增强:动态代理使得代理对象和实际目标对象之间解耦更彻底,代理对象和目标对象之间通过接口关联,只需要目标对象实现了某个接口,就可以动态地为所有实现该接口的对象创建代理。
  4. 广泛的应用场景:动态代理在AOP(面向切面编程)、框架整合、远程调用(如RMI)、权限控制、事务管理等领域有着广泛应用,可以方便地进行横切关注点的集中处理。

缺点

  1. 性能损耗:动态代理相比于直接调用目标方法会有一定的性能损失,因为它涉及到反射操作和额外的间接调用开销。不过在大多数情况下,这个损耗是可以接受的,除非是在对性能极度敏感的场景下。
  2. 学习成本与调试难度:由于动态代理涉及到了Java反射机制,因此对于开发者来说,理解和调试可能会比静态代理稍微复杂一些。
  3. 兼容性问题:如果要代理的目标对象不是一个接口的实例而是具体类的实例,Java标准库的动态代理机制(java.lang.reflect.Proxy)就不能直接使用,此时需要借助第三方库如CGLIB来实现。
  4. 错误提示不够直观:在动态代理出错时,抛出的异常通常与原始业务逻辑相关性较弱,这可能加大定位问题的难度。

参考

www.cnblogs.com/gonjan-blog…