静态代理还是动态代理?来聊聊Java中的代理设计模式

简介: 代理模式(Proxy Design Pattern)是一种结构型设计模式,为一个对象提供一个代理对象,然后使用代理对象控制对原对象的引用。即通过代理对象访问目标对象。被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。

image.png

代理模式(Proxy Design Pattern)是一种结构型设计模式,为一个对象提供一个代理对象,然后使用代理对象控制对原对象的引用。即通过代理对象访问目标对象。被代理的对象可以是远程对象、创建开销大的对象或需要安全控制的对象。

一、代理模式介绍

代理模式主要有两个部分:

  • 抽象主题:声明一个公共接口,给代理类和真实对象进行实现。让真实对象和代理对象一一对应
  • 真实主题:定义所要代理的真实对象,其中包括实际的业务逻辑和操作
  • 代理主题:首先代理对象有真实对象的所有接口,保证能完全替代真实对象。此外还会有其他的方法和操作,来扩展相关的功能

其主要结构的UML图如下所示:

image.png

  1. Subject:抽象主题类,通过接口或抽象类声明主题和代理对象实现的业务方法
  2. RealSubject:真实主题类,实现Subject中的具体业务,是代理对象所代表的真实对象
  3. ProxySubject:代理类,其内部含有对真实主题的引用,它可以访问、控制或扩展RealSubject的功能
  4. Client:客户端,通过使用代理类来访问真实的主题类

按照上面的类图,可以实现如下代码:

//主题类接口
public interface Subject {
    void Request();
}

//真实的主题类
public class RealSubject implements Subject{

    @Override
    public void Request() {
        System.out.println("我是真实的主题类");
    }
}

//代理类
public class Proxy implements Subject{

    private RealSubject realSubject = new RealSubject(); //保证对真实对象的引用
    
    public void PreRequest(){
     ....//调用Request()前的操作
    }
    
    @Override
    public void Request() {
        PreRequest();
        //对真实对象的调用
        realSubject.Request();
        postRequest();
    }
    
    public void PostRequest(){
        ....//调用Request()后的操作
    }
    
  
}

//客户端
public class Client {
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.Request();
    }
}

代理模式有比较广泛的使用,比如Spring AOPRPC、缓存等。在 Java 中,根据代理的创建时期,可以将代理模式分为静态代理和动态代理,下面就来分别阐述:

二、代理模式实现

动态代理和静态代理的区分就是语言类型是在运行时检查还是在编译期检查。大白话就是静态代理中的代理类是程序员自己写的,动态代理中的代理类程序员不用写,在代码运行过程中它能自动生成。

2.1 静态代理

静态代理是指在编译期,也就是在JVM运行之前就已经获取到了代理类的字节码信息。即Java源码生成.class文件时期:

image.png

由于在JVM运行前代理类和真实主题类已经是确定的,因此也被称为静态代理。

在实际使用中,通常需要定义一个公共接口及其方法,被代理对象(目标对象)与代理对象一起实现相同的接口或继承相同的父类。也就是我们自己需要构造一个代理类。在实际的日常开发中,几乎看不到使用静态代理的场景。

2.2 动态代理

动态代理,也就是在JVM运行时期动态构建对象和动态调用代理方法。它主要的作用就是可以帮助动态生成代理类,可以大大减少代理类的数量。

在JDK中,常用的实现方式是反射。而反射机制是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及其中包含的属性及方法。比如JDK Proxy。

此外动态代理也可以通过ASM(Java 字节码操作框架)来实现。比如CGLib。下面就具体进行说明:

2.2.1 JDK 动态代理

JDK 动态代理是JDK自身提供的一种方式,它的实现不需要引用第三方类,只需要实现InvocationHandler接口,重写invoke()方法就可以动态创建代理类。

  1. 实现InvocationHandler 接口,创建动态代理

这是减少代理类数量的核心部分,通过实现InvocationHandler 接口,并重写invoke() 方法,来创建动态代理类:

//核心部分 JDK Proxy 代理类
class JDKProxy implements InvocationHandler {
    //真实的委托对象
    private Object target;

    public Object getInstance(Object target) {
        this.target = target;
        //获得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }
    /**
    * @param proxy 代理对象,动态生成的代理类实例
    * @param method 被调用的方法对象,即要执行的方法,可以通过该对象获取方法的相关信息
    * @param args 方法的参数数组,这是被调用方法的参数列表。可以通过该参数获取方法的参数值
    * @return Object
    * @throws Throwable
    */
    @Override   
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //执行委托对象的内部方法
        Object result = method.invoke(target, args);
        return result;
    }
}
  1. 创建被代理对象

也就是需要被代理的真实对象,这里也可以创建多个对象:

public interface Vehicle {
    void running();
}
public class Car implements Vehicle {
    @Override
    public void running() {
        System.out.println("Car is running");
    }
}
public class Bus implements Vehicle {
    @Override
    public void running() {
        System.out.println("Bus is running");
    }
}
public class Taxi implements Vehicle {
    @Override
    public void running() {
        System.out.println("Taxi is runnig");
    }
}
  1. 客户端调用实现

这里我们就能够通过一个代理类JDKProxy 来动态代理多个委托类了:

public class ProxyExampleDemo {
    public static void main(String[] args) {
        JDKProxy jdkProxy = new JDKProxy();
        Vehicle carInstance = (Vehicle) jdkProxy.getInstance(new Car());
        carInstance.running();
        Vehicle busInstance = (Vehicle) jdkProxy.getInstance(new Bus());
        busInstance.running();
        Vehicle taxiInstance = (Vehicle) jdkProxy.getInstance(new Taxi());
        taxiInstance.running();
    }
}
  1. 测试结果
Car is running
Bus is running
Taxi is running

从结果会发现通过一个JDKProxy,就代理实现多个真实对象的内部方法。但是存在一个问题,JDK提供的动态代理只能代理接口,而不能代理没有接口的类,有的,他就是Cglib。

2.2.2 CGLib 动态代理

CGLib类库可以代理没有接口的类,它采取的是创建目标类的子类的方式,通过子类化,我们可以达到近似使用被调用者本身的效果。实现代码如下所示:

  1. 核心代理类

和JDK proxy一样,需要实现一个接口MethodInterceptor ,但我们发现有些不同,其内部是通过实现被代理类的子类来实现动态代理的功能。所以在使用时一定要注意被代理的类不能使用final修饰

class CGLibProxy implements MethodInterceptor {
    private Object target;

    public Object getInstance(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        //设置父类为实例类
        enhancer.setSuperclass(this.target.getClass());
        //回调方法
        enhancer.setCallback(this);
        //创建代理对象
        return enhancer.create();
    }
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Object result = methodProxy.invokeSuper(o, objects);
        return result;
    }
}
  1. 创建被代理对象

这个被代理对象可以不用实现接口

class car {
    public void running() {
        System.out.println("car is running");
    }
}
  1. 客户端及测试
public class CGLibExample {
    public static void main(String[] args) {
        CGLibProxy cgLibProxy = new CGLibProxy();
        car instance = (car) cgLibProxy.getInstance(new car());
        instance.running();
    }
}
//测试结果:
// car is running

2.2.3 JDK Proxy 和 CGLib 的区别

  1. 来源:JDK Proxy 是JDK 自带的功能,CGLib 是第三方提供的工具
  2. 实现:JDK Proxy 通过拦截器加反射的方式实现;CGLib 基于ASM实现,性能比较高
  3. 接口:JDK Proxy 只能代理继承接口的类,CGLib 不需要通过接口来实现,它是通过实现子类的方式来完成调用,但是要注意被代理的类不能被final修饰

三、代理模式的应用场景

代理模式其实在日常的开发中并不常见,但是在一些框架中使用的很多,比如springAop, myBatis mapper, 远程代理等等

3.1 MapperProxyFactory

在MyBatis 中,也存在着代理模式的使用,比如MapperProxyFactory。其中的newInstance()方法就是生成一个具体的代理来实现功能,代码如下:

public class MapperProxyFactory<T> {
  private final Class<T> mapperInterface;
    
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
    
  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  // 创建代理类
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

3.2 Spring AOP

代理模式最常使用的一个应用场景就是在业务系统中开发一些非功能性需求,比如监控、统计、鉴权、限流、事务、日志等。将这些附加功能与业务功能解耦,放在代理类中统一处理,让程序员只需要关注业务方面的开发。而Spring AOP 的切面实现原理就是基于动态代理

image.png

Spring AOP 的底层通过上面提到的 JDK Proxy 和 CGLib动态代理机制,为目标对象执行横向织入。当Bean实现了接口时, Spring就会使用JDK Proxy,在没有实现接口时就会使用 CGLib。也可以在配置中强制使用 CGLib:

<aop:aspectj-autoproxy proxy-target-class="true"/>

3.3 远程代理

java 中的RMI(Remote Method Invocation),比如

RPC 框架的实现可以看作成是一种代理模式,通过远程代理、将网络同步、数据编解码等细节隐藏起来,让客户端在使用 RPC 服务时,不必考虑这些细节。

四、代理模式实战

4.1 利用静态代理模式实现模拟访问网页功能

这里可以使用静态代理模式来实现模拟访问网页的功能

  1. 定义访问网站的接口和统一访问方法
public interface PageVisitor {
    void visitPage();
}
  1. 实现真实访问和代理访问具体类

两个具体的访问类都要实现统一的访问接口

public class RealPageVisitor implements PageVisitor {

    private final OkHttpClient httpClient;

    public RealPageVisitor() {
        this.httpClient = new OkHttpClient();
    }

    @Override
    public void visitPage() {
        String url = "要访问的网站地址Url";
        Request request = new Request.Builder()
                .url(url)
                .build();
        try {
            Response response = httpClient.newCall(request).execute();
            if (response.isSuccessful()) {
                System.out.println("正在访问网址:" + url);
            } else {
                System.out.println("访问失败网址是:" + url);
            }
        } catch (IOException e) {
            System.out.println("出现访问异常信息");
            e.printStackTrace();
        }
    }
}

可以通过对代理类,对真实委托类进行扩展和管理:

public class ProxyPageVisitor implements PageVisitor {

    @Autowired
    private RealPageVisitor realPageVisitor;

    private int visitCount;

    public ProxyPageVisitor(RealPageVisitor realPageVisitor) {
        this.realPageVisitor = realPageVisitor;
        this.visitCount = 0;
    }

    @Override
    public void visitPage() {
        //管理真实代理的生命周期
        if (canVisit()) {
            realPageVisitor.visitPage();
            visitCount++;
        } else {
            throw new IllegalStateException("超出最大访问量");
        }

    }

    private boolean canVisit() {
        int maxVisitCount = 120;
        return visitCount < maxVisitCount;
    }

}
  1. 设置访问的定时任务

该部分也就相当于UML图中的client 客户端,调用代理类来实现具体业务逻辑

public class PageVisitorScheduler {

    @Autowired
    private ProxyPageVisitor pageVisitor;

    @Scheduled(fixedDelay = 25000)
    public void visitPage() {
        pageVisitor.visitPage();
    }
}

五、参考资料

https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59#/detail/pc?id=1768

https://time.geekbang.org/column/article/7489

https://time.geekbang.org/column/article/201823

《Java 重学设计模式》

《大话设计模式》

目录
相关文章
|
1月前
|
设计模式 缓存 安全
【高薪程序员必看】万字长文拆解Java并发编程!(8):设计模式-享元模式设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的经典对象复用设计模式-享元模式,废话不多说让我们直接开始。
51 0
|
2月前
|
缓存 监控 Java
java动态代理
本文介绍了Java中的动态代理及其优势,通过增强原有方法或拦截调用实现无侵入式代码扩展,如添加日志、缓存等。文章先讲解了静态代理的基本概念和实现方式,随后引出动态代理解决静态代理在多方法、多类场景下的局限性。通过JDK提供的InvocationHandler接口和Proxy类,展示了如何动态生成代理对象。最后,文章还探讨了代理Hook技术,包括寻找Hook点、选择代理方式以及替换原始对象的具体步骤。
|
3月前
|
设计模式 Java 数据安全/隐私保护
Java 设计模式:装饰者模式(Decorator Pattern)
装饰者模式属于结构型设计模式,允许通过动态包装对象的方式为对象添加新功能,提供比继承更灵活的扩展方式。该模式通过组合替代继承,遵循开闭原则(对扩展开放,对修改关闭)。
|
3月前
|
设计模式
课时90:代理设计模式
课时90介绍了代理设计模式,该模式通过代理对象帮助用户专注于核心业务功能。代理模式中,客户端只关注核心业务(如“吃”),而代理对象负责准备、执行和清理工作。通过接口IEat、真实主题EatReal和服务代理EatProxy的实现,展示了如何在代码中应用代理模式。代理模式的特点是将业务逻辑与辅助操作分离,使代码更清晰且易于维护。
|
4月前
|
Dubbo Java 应用服务中间件
Java的动态代理
Java动态代理是一种强大的机制,允许在运行时创建接口的代理实例,并拦截方法调用。其核心组件包括`java.lang.reflect.Proxy`和`java.lang.reflect.InvocationHandler`。通过自定义接口、实现接口、编写`InvocationHandler`处理器并生成代理对象,可以灵活地增强方法功能,如日志记录、事务管理等。典型应用场景包括AOP、RPC、延迟加载和Mock测试。与CGLIB相比,JDK动态代理基于接口,性能稍慢但无需第三方库,适用于需要无侵入式增强的场合。
|
4月前
|
设计模式 中间件 Java
设计模式3:代理、适配器、装饰器模式
代理模式是一种结构型设计模式,通过中间件解耦服务提供者和使用者,使使用者间接访问服务提供者,便于封装和控制。
92 3
|
4月前
|
Java API 数据安全/隐私保护
探索Java动态代理的奥秘:JDK vs CGLIB
动态代理是一种在 运行时动态生成代理类的技术,无需手动编写代理类代码。它通过拦截目标方法的调用,实现对核心逻辑的 无侵入式增强(如日志、事务、权限控制等)。
114 0
探索Java动态代理的奥秘:JDK vs CGLIB
|
7月前
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
125 6
|
7月前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
101 4
|
8月前
|
设计模式 网络协议 Java
06.动态代理设计模式
本文详细介绍了动态代理设计模式,包括其必要性、概念、实现方式及案例分析。动态代理允许在运行时动态创建代理对象,增强代码复用性和灵活性,减少类膨胀。文章通过对比静态代理,深入解析了动态代理的实现机制,如基于接口和类的动态代理,以及其在Retrofit中的应用。同时,讨论了动态代理的优势和潜在问题,如性能开销和调试难度。最后,提供了丰富的学习资源链接,帮助读者进一步理解和掌握动态代理。
81 1