4 个版本 (破坏性更新)

0.4.0 2024年5月3日
0.3.0 2024年4月24日
0.2.0 2024年4月24日
0.1.0 2024年4月23日

#108性能分析

Apache-2.0

10KB

Rust 的动态代理!

前言

理解代理

在软件工程中,代理是一个占位符或替身,它控制对另一个对象或服务的访问。代理用于添加间接层以支持分布式、受控或延迟访问。它们还可以用于日志记录、安全、缓存和其他目的。

那么动态代理是什么呢?

动态代理是在运行时动态创建的代理。它允许您创建一个代理对象,而不必显式编写具体的代理类。这通常通过反射或使用代理生成库来实现。动态代理在需要拦截方法调用以添加行为(如日志记录、性能分析或安全检查)而不修改原始类的情况下特别有用。

注意以下简单的 Rust 代码

trait Calculator {
    fn add(&self, a: i32, b: i32) -> i32;
    fn subtract(&self, a: i32, b: i32) -> i32;
}

struct CalculatorImpl;

impl Calculator for CalculatorImpl {
    fn add(&self, a: i32, b: i32) {
        a+b
    }
    fn subtract(&self, a: i32, b: i32) {
        a-b
    }
}

特性和它的实现通常位于同一个地方。但如果我们想在另一个进程中执行代码或动态解析函数名和参数以产生结果呢?

考虑一个需要执行 SQL 函数的场景。通常,我们创建一个包含所有函数的特性和编写发送参数和解析结果的代码。然而,这种方法往往涉及到重复的模板代码。

如果我们能创建一个单一、通用的函数来构建 SQL 命令、添加参数并将结果解析为 Rust 类型呢?想象一下使用 SQL 函数名及其参数调用此函数,并接收相应的结果。这正是动态代理大放异彩的地方。它使我们能够动态处理函数调用,简化我们的代码并减少冗余。

我们的实现

Rust 以其严格性而闻名,缺乏 Java 或 .NET 的反射或 Python 的 __call__ 方法。为了实现类似的功能,Rust 提供了强大的过程宏。这些宏在编译之前扩展为纯 Rust 代码。

在我们的项目中,我们实现了一个属性宏,它接受一个空结构体的名称。此宏使我们能够模拟与其他语言中类似反射或动态方法调用的行为。

pub struct Interceptor;

impl DynamicProxy for Interceptor {
    fn call(&self, invocation: &mut InvocationInfo){
        let a = invocation.get_arg_value::<i32>(0);
        let b = invocation.get_arg_value::<i32>(1);
        assert_eq!(invocation.arg_names[0], "a");
        assert_eq!(invocation.arg_names[1], "b");
        assert_eq!(invocation.get_arg_type(1), TypeId::of::<i32>());
        assert_eq!(invocation.return_type, TypeId::of::<i32>());
        invocation.set_return_value(
            match invocation.func_name {
                "add" => a + b,
                "subtract" => a - b,
                _ => 0
            })
    }
}

#[dynamic_proxy(Interceptor)]
pub trait MyTrait {
    fn add(self, a: i32, b: i32) -> i32;
    fn subtract(self, a: i32, b: i32) -> i32;
}

#[test]
fn add() {
    use crate::tests::Interceptor;
    // use crate::tests::MyTrait;
    let s = Interceptor {};
    assert_eq!(s.add(6, 7), 13);
}

#[test]
fn subtract() {
    use crate::tests::Interceptor;
    // use crate::tests::MyTrait;
    let s = Interceptor {};
    assert_eq!(s.subtract(8, 3), 5);
}

宏'dynamic_proxy'获取我们想要实现特质的结构的名称,然后实现所有将调用转发到DynamicProxy特质成员函数'call'的特质项。有一个包含函数签名和参数值的结构体。

pub struct InvocationInfo<'a> {
    pub func_name: &'a str,
    pub arg_names: &'a[&'a str],
    pub arg_values: &'a [Box<dyn Any>],
    pub return_type: TypeId,
    pub return_value: Option<Box<dyn Any>>
}

消费者必须填充返回值以包含函数响应。测试代码已经足够清晰。

依赖项

~3–10MB
~78K SLoC