静态代理还是动态代理?来聊聊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 重学设计模式》

《大话设计模式》

目录
相关文章
|
27天前
|
设计模式 消息中间件 搜索推荐
Java 设计模式——观察者模式:从优衣库不使用新疆棉事件看系统的动态响应
【11月更文挑战第17天】观察者模式是一种行为设计模式,定义了一对多的依赖关系,使多个观察者对象能直接监听并响应某一主题对象的状态变化。本文介绍了观察者模式的基本概念、商业系统中的应用实例,如优衣库事件中各相关方的动态响应,以及模式的优势和实际系统设计中的应用建议,包括事件驱动架构和消息队列的使用。
|
1月前
|
设计模式 Java 数据库连接
Java编程中的设计模式:单例模式的深度剖析
【10月更文挑战第41天】本文深入探讨了Java中广泛使用的单例设计模式,旨在通过简明扼要的语言和实际示例,帮助读者理解其核心原理和应用。文章将介绍单例模式的重要性、实现方式以及在实际应用中如何优雅地处理多线程问题。
38 4
|
1月前
|
Java
JAVA 静态代理 & 动态代理
【11月更文挑战第14天】静态代理是一种简单的代理模式实现,其中代理类和被代理类的关系在编译时已确定。代理类实现与被代理类相同的接口,并持有被代理类的实例,通过调用其方法实现功能增强。优点包括代码结构清晰,易于理解和实现;缺点是对于多个被代理类,需为每个类编写相应的代理类,导致代码量大增,维护成本高。动态代理则在运行时动态生成代理类,更加灵活,减少了代码冗余,但可能引入性能损耗和兼容性问题。
|
2月前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
38 0
[Java]静态代理与动态代理(基于JDK1.8)
|
2月前
|
设计模式 网络协议 Java
06.动态代理设计模式
本文详细介绍了动态代理设计模式,包括其必要性、概念、实现方式及案例分析。动态代理允许在运行时动态创建代理对象,增强代码复用性和灵活性,减少类膨胀。文章通过对比静态代理,深入解析了动态代理的实现机制,如基于接口和类的动态代理,以及其在Retrofit中的应用。同时,讨论了动态代理的优势和潜在问题,如性能开销和调试难度。最后,提供了丰富的学习资源链接,帮助读者进一步理解和掌握动态代理。
25 1
|
2月前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
54 0
[Java]23种设计模式
|
2月前
|
设计模式 网络协议 Java
05.静态代理设计模式
《静态代理设计模式》详细介绍了静态代理的基本概念、原理与实现、应用场景及优缺点。主要内容包括静态代理的由来、定义、使用场景、实现方式、结构图与时序图,以及其在降低耦合、保护对象权限等方面的优势。同时,文章也指出了静态代理的局限性,如缺乏灵活性、难以复用、难以动态添加功能等,并介绍了动态代理如何弥补这些不足。最后,通过多个实际案例和代码示例,帮助读者更好地理解和应用静态代理模式。
47 4
|
1月前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
2月前
|
Java
深入理解Java动态代理
深入理解Java动态代理
94 1
|
2月前
|
设计模式 监控 算法
Java设计模式梳理:行为型模式(策略,观察者等)
本文详细介绍了Java设计模式中的行为型模式,包括策略模式、观察者模式、责任链模式、模板方法模式和状态模式。通过具体示例代码,深入浅出地讲解了每种模式的应用场景与实现方式。例如,策略模式通过定义一系列算法让客户端在运行时选择所需算法;观察者模式则让多个观察者对象同时监听某一个主题对象,实现松耦合的消息传递机制。此外,还探讨了这些模式与实际开发中的联系,帮助读者更好地理解和应用设计模式,提升代码质量。
Java设计模式梳理:行为型模式(策略,观察者等)