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

《大话设计模式》

目录
相关文章
|
23天前
|
设计模式 算法 Java
Java中的设计模式:提升代码质量的秘诀
【8月更文挑战第23天】在Java开发中,设计模式是提高代码可读性、可维护性和扩展性的强有力工具。本文通过浅显易懂的语言和实际案例,探讨几种常见的设计模式及其在Java中的应用,旨在帮助开发者更好地理解并运用这些模式来优化自己的代码结构。
37 2
|
1天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
|
1天前
|
设计模式 安全 算法
【Java面试题汇总】设计模式篇(2023版)
谈谈你对设计模式的理解、七大原则、单例模式、工厂模式、代理模式、模板模式、观察者模式、JDK中用到的设计模式、Spring中用到的设计模式
【Java面试题汇总】设计模式篇(2023版)
|
1天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑】设计模式——原型模式
对比原型模式和传统方式的实现思路、代码方案、优缺点,阐述原型模式的使用场景,以及深拷贝、浅拷贝等相关概念,并扩展原型模式在Spring源码中的应用。
【Java笔记+踩坑】设计模式——原型模式
|
27天前
|
设计模式 存储 Java
【十】设计模式~~~结构型模式~~~享元模式(Java)
文章详细介绍了享元模式(Flyweight Pattern),这是一种对象结构型模式,通过共享技术实现大量细粒度对象的重用,区分内部状态和外部状态来减少内存中对象的数量,提高系统性能。通过围棋棋子的设计案例,展示了享元模式的动机、定义、结构、优点、缺点以及适用场景,并探讨了单纯享元模式和复合享元模式以及与其他模式的联用。
【十】设计模式~~~结构型模式~~~享元模式(Java)
|
16天前
|
设计模式 缓存 算法
揭秘策略模式:如何用Java设计模式轻松切换算法?
【8月更文挑战第30天】设计模式是解决软件开发中特定问题的可重用方案。其中,策略模式是一种常用的行为型模式,允许在运行时选择算法行为。它通过定义一系列可互换的算法来封装具体的实现,使算法的变化与客户端分离。例如,在电商系统中,可以通过定义 `DiscountStrategy` 接口和多种折扣策略类(如 `FidelityDiscount`、`BulkDiscount` 和 `NoDiscount`),在运行时动态切换不同的折扣逻辑。这样,`ShoppingCart` 类无需关心具体折扣计算细节,只需设置不同的策略即可实现灵活的价格计算,符合开闭原则并提高代码的可维护性和扩展性。
35 2
|
16天前
|
设计模式 Java
Java 设计模式之谜:工厂模式与抽象工厂模式究竟隐藏着怎样的神奇力量?
【8月更文挑战第30天】在Java编程中,设计模式为常见问题提供了高效解决方案。工厂模式与抽象工厂模式是常用的对象创建型设计模式,能显著提升代码的灵活性、可维护性和可扩展性。工厂模式通过定义创建对象的接口让子类决定实例化哪个类;而抽象工厂模式则进一步提供了一个创建一系列相关或相互依赖对象的接口,无需指定具体类。这种方式使得系统更易于扩展和维护。
29 1
|
16天前
|
设计模式 Java
重构你的代码:探索Java中的混合、装饰器与组合设计模式
【8月更文挑战第30天】在软件开发中,设计模式为特定问题提供了结构化的解决方案,使代码更易理解、维护及扩展。本文将介绍三种常用的 Java 设计模式:混合模式、装饰器模式与组合模式,并附有示例代码展示实际应用。混合模式允许通过继承多个接口或抽象类实现多重继承;装饰器模式可在不改变对象结构的情况下动态添加新功能;组合模式则通过树形结构表示部分-整体层次,确保客户端处理单个对象与组合对象时具有一致性。
13 1
|
27天前
|
设计模式 算法 安全
Java编程中的设计模式:提升代码的可维护性和扩展性
【8月更文挑战第19天】在软件开发的世界里,设计模式是解决常见问题的一种优雅方式。本文将深入探讨Java编程语言中常用的几种设计模式,并解释如何通过这些模式来提高代码的可维护性和扩展性。文章不涉及具体的代码实现,而是侧重于理论和实践相结合的方式,为读者提供一种思考和改善现有项目的新视角。
|
27天前
|
设计模式 Java
常用设计模式介绍~~~ Java实现 【概念+案例+代码】
文章提供了一份常用设计模式的全面介绍,包括创建型模式、结构型模式和行为型模式。每种设计模式都有详细的概念讲解、案例说明、代码实例以及运行截图。作者通过这些模式的介绍,旨在帮助读者更好地理解源码、编写更优雅的代码,并进行系统重构。同时,文章还提供了GitHub上的源码地址,方便读者直接访问和学习。
常用设计模式介绍~~~ Java实现 【概念+案例+代码】