MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java代理模式的使用与实现

2024-12-104.4k 阅读

什么是代理模式

代理模式是一种设计模式,属于结构型模式。在代理模式中,一个类代表另一个类的功能。这种类型的设计模式属于代理模式,在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。代理对象可以在调用实际对象的方法前后执行一些额外的操作,比如日志记录、权限检查、缓存等。

Java 中的代理模式分类

在 Java 中,代理模式主要分为静态代理和动态代理,动态代理又包括 JDK 动态代理和 CGLIB 动态代理。

静态代理

  1. 原理:静态代理在编译时就已经确定了代理类,代理类和被代理类实现相同的接口或者继承相同的父类。代理类中持有被代理类的实例,通过构造函数传入。在代理类的方法中,调用被代理类的相应方法,并可以在调用前后添加额外的逻辑。
  2. 代码示例
    • 首先定义一个接口 Subject
public interface Subject {
    void request();
}
  • 然后创建被代理类 RealSubject 实现 Subject 接口:
public class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject is handling request.");
    }
}
  • 接着创建代理类 ProxySubject 也实现 Subject 接口,并持有 RealSubject 的实例:
public class ProxySubject implements Subject {
    private RealSubject realSubject;

    public ProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        System.out.println("ProxySubject pre - handling.");
        realSubject.request();
        System.out.println("ProxySubject post - handling.");
    }
}
  • 最后在测试类中使用代理:
public class StaticProxyTest {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        ProxySubject proxySubject = new ProxySubject(realSubject);
        proxySubject.request();
    }
}

在上述代码中,ProxySubject 作为 RealSubject 的代理,在调用 request 方法前后添加了额外的输出信息。静态代理的优点是实现简单,容易理解。缺点是如果被代理类有很多方法,代理类需要一一实现,代码冗余度高。而且如果接口有变动,代理类和被代理类都需要修改,维护成本较高。

JDK 动态代理

  1. 原理:JDK 动态代理是基于接口实现的。Java 提供了 InvocationHandler 接口和 Proxy 类来实现动态代理。InvocationHandler 接口的 invoke 方法会在代理对象的方法被调用时执行,在这个方法中可以实现代理逻辑,比如在调用实际方法前后添加操作。Proxy 类用于创建代理对象。
  2. 代码示例
    • 还是使用前面定义的 Subject 接口和 RealSubject 类。
    • 创建一个实现 InvocationHandler 接口的类 DynamicProxyHandler
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class DynamicProxyHandler implements InvocationHandler {
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("JDK Dynamic Proxy pre - handling.");
        Object result = method.invoke(target, args);
        System.out.println("JDK Dynamic Proxy post - handling.");
        return result;
    }
}
  • 在测试类中创建代理对象并使用:
import java.lang.reflect.Proxy;

public class JDKDynamicProxyTest {
    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        DynamicProxyHandler handler = new DynamicProxyHandler(realSubject);
        Subject proxy = (Subject) Proxy.newProxyInstance(
                realSubject.getClass().getClassLoader(),
                realSubject.getClass().getInterfaces(),
                handler);
        proxy.request();
    }
}

JDKDynamicProxyTest 中,通过 Proxy.newProxyInstance 方法创建了代理对象。该方法接收三个参数:被代理对象的类加载器、被代理对象实现的接口数组以及 InvocationHandler 实例。JDK 动态代理的优点是不需要手动编写代理类,减少了代码冗余。缺点是只能代理实现了接口的类,如果一个类没有实现接口则无法使用 JDK 动态代理。

CGLIB 动态代理

  1. 原理:CGLIB(Code Generation Library)是一个强大的高性能的代码生成包,它通过继承的方式来实现代理。CGLIB 动态代理不需要被代理类实现接口,它通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,在拦截方法中可以实现代理逻辑。
  2. 代码示例:首先需要引入 CGLIB 的依赖,如果使用 Maven,可以在 pom.xml 中添加如下依赖:
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>
  • 创建一个目标类 TargetClass
public class TargetClass {
    public void method() {
        System.out.println("TargetClass method is called.");
    }
}
  • 创建一个实现 MethodInterceptor 接口的类 CglibProxy
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("CGLIB Dynamic Proxy pre - handling.");
        Object result = methodProxy.invokeSuper(o, objects);
        System.out.println("CGLIB Dynamic Proxy post - handling.");
        return result;
    }

    public Object getProxy(Class targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback(this);
        return enhancer.create();
    }
}
  • 在测试类中使用 CGLIB 动态代理:
public class CGLIBDynamicProxyTest {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        TargetClass proxy = (TargetClass) cglibProxy.getProxy(TargetClass.class);
        proxy.method();
    }
}

在上述代码中,CglibProxy 实现了 MethodInterceptor 接口,在 intercept 方法中实现了代理逻辑。通过 Enhancer 类来创建代理对象,Enhancer 设置目标类为父类,并设置回调为 CglibProxy 实例。CGLIB 动态代理的优点是可以代理没有实现接口的类,适用范围更广。缺点是因为采用继承方式,无法代理 final 类和 final 方法,并且由于是通过字节码生成技术,性能上可能会比 JDK 动态代理略差一些。

代理模式在实际项目中的应用场景

  1. 远程代理:在分布式系统中,当需要访问远程对象时,可以使用远程代理。远程代理负责处理网络通信,将本地调用转换为对远程对象的调用。比如,在企业级应用中调用远程服务,本地代码通过远程代理类来调用远程服务接口,远程代理类封装了网络连接、数据传输等细节,对本地调用者透明。
  2. 虚拟代理:当一个对象的创建开销很大时,可以使用虚拟代理。虚拟代理在真正需要使用对象时才创建它,比如加载大图片。假设在一个图形界面应用中,有一个图片展示功能,图片文件可能很大。可以使用虚拟代理,在界面加载时,只显示一个占位符,当用户真正需要查看图片时,虚拟代理再去加载实际的图片。
  3. 保护代理:用于控制对对象的访问权限。比如在一个系统中,不同用户角色有不同的权限,对于一些敏感操作,如删除用户数据,只有管理员角色才能执行。可以通过保护代理来检查当前用户的权限,只有权限符合要求时才调用实际的删除方法。
  4. 缓存代理:在某些场景下,方法的执行结果可能是固定的或者不经常变化的,这时可以使用缓存代理。缓存代理在第一次调用实际方法后,将结果缓存起来,后续再次调用时,如果缓存有效,则直接返回缓存结果,避免重复执行实际方法,提高系统性能。比如,一个查询数据库的方法,查询结果在一定时间内不会变化,缓存代理可以缓存查询结果,减少数据库的查询压力。

代理模式与其他设计模式的关系

  1. 代理模式与装饰器模式:两者在结构上有相似之处,都涉及到对对象进行包装。但是装饰器模式主要目的是为对象添加新的行为,强调的是功能的增强,并且装饰器和被装饰对象通常实现相同的接口,是一种 “is - a” 的关系。而代理模式重点在于控制对对象的访问,代理对象和被代理对象也实现相同接口或继承相同父类,但代理模式更侧重于对访问的控制,是一种 “stands - in - for” 的关系。
  2. 代理模式与适配器模式:适配器模式主要用于将一个类的接口转换成客户希望的另一个接口,解决的是接口不兼容的问题。代理模式则主要是为对象提供一种代理以控制对这个对象的访问。适配器模式通常是为了让原本不兼容的类能够协同工作,而代理模式是为了在不改变被代理类的基础上,对其访问进行控制和增强。

总结代理模式的优缺点

  1. 优点
    • 职责清晰:代理类和被代理类职责明确,代理类专注于控制访问和添加额外逻辑,被代理类专注于业务逻辑的实现。
    • 扩展性强:可以很方便地在代理类中添加新的功能,而不需要修改被代理类的代码。比如在权限检查代理中,如果需要增加新的权限判断逻辑,只需要在代理类中修改,不影响被代理类的业务逻辑。
    • 保护被代理对象:通过代理可以对被代理对象的访问进行控制,避免非法访问。例如在保护代理中,只有符合权限要求的用户才能访问被代理对象的敏感方法。
  2. 缺点
    • 可能增加系统复杂度:对于简单的业务场景,使用代理模式可能会引入过多的类,增加系统的复杂度。比如在一个简单的单用户应用中,使用代理模式来控制访问可能显得过于复杂。
    • 性能开销:动态代理,尤其是 CGLIB 动态代理,由于涉及字节码生成等技术,会带来一定的性能开销。在对性能要求极高的场景下,需要谨慎使用。

通过对 Java 代理模式的详细介绍,包括静态代理、JDK 动态代理和 CGLIB 动态代理的原理、代码实现以及应用场景等方面的内容,希望能帮助读者深入理解和掌握代理模式在 Java 开发中的使用与实现,在实际项目中能够根据具体需求选择合适的代理方式来优化系统设计。