当前位置: 首页 > article >正文

Rust-AOP编程实战

文章本天成,妙手偶得之。粹然无疵瑕,岂复须人为?君看古彝器,巧拙两无施。汉最近先秦,固已殊淳漓。胡部何为者,豪竹杂哀丝。后夔不复作,千载谁与期?

——《文章》宋·陆游

【哲理】文章本是不加人工,天然而成的,是技艺高超的人在偶然间所得到的。其实作者所说的“天成”,并不就是大自然的恩赐,而是基于长期积累起来的感性印象和深入的思考,由于偶然出发而捕捉到灵感。

灵感就是长时间的积累和瞬间的爆发,人品也是,成就亦如是。

一、AOP 的概念

面向方面编程(Aspect-Oriented Programming,AOP)是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)分离出来,从而提高代码的模块化和可维护性。横切关注点是指那些影响多个模块的功能,比如日志记录、安全检查、事务管理等。在传统的面向对象编程(OOP)中,这些关注点往往会散布在各个类中,导致代码重复和难以维护。

AOP 通过引入“方面”(aspect)的概念,将这些横切关注点集中到一个地方进行管理。主要的 AOP 概念包括:

  1. Aspect(方面):封装横切关注点的模块。
  2. Join Point(连接点):程序执行过程中可以插入方面的具体点,比如方法调用或异常抛出。
  3. Advice(通知):在特定的连接点上执行的代码,可以分为前置通知(Before)、后置通知(After)和环绕通知(Around)。
  4. Pointcut(切入点):定义在哪些连接点上应用通知的表达式。
  5. Weaving(织入):将方面应用到目标对象的过程,可以在编译时、加载时或运行时进行。

二、使用 Rust 实现 AOP

虽然 Rust 没有直接支持 AOP,但我们可以通过宏和闭包来实现类似的效果。

1、声明宏实现 AOP

1.1、定义宏和函数

首先,我们定义一个宏,用于在函数调用前后执行一些额外的逻辑:

macro_rules! aop {
    ($func:expr, $before:expr, $after:expr) => {{
        $before();
        let result = $func();
        $after();
        result
    }};
}

这个宏接受三个参数:

  • $func:要调用的函数。
  • $before:在函数调用前执行的闭包。
  • $after:在函数调用后执行的闭包。

1.2、使用宏实现 AOP

接下来,我们定义一些示例函数和通知,并使用 aop! 宏来包装函数调用:

fn main() {
    // 定义前置通知
    let before = || println!("Before function call");

    // 定义后置通知
    let after = || println!("After function call");

    // 定义一个示例函数
    let my_function = || {
        println!("Inside the function");
        42 // 返回一些值
    };

    // 使用 aop! 宏包装函数调用
    let result = aop!(my_function, before, after);
    println!("Function returned: {}", result);
}

运行这个程序,你会看到以下输出:

Before function call
Inside the function
After function call
Function returned: 42

1.3、环绕通知

为了更好地展示 AOP 的灵活性,我们可以扩展示例,添加更多的通知类型,比如环绕通知:

macro_rules! aop_around {
    ($func:expr, $around:expr) => {{
        $around($func)
    }};
}

fn main() {
    // 定义环绕通知
    let around = |func: fn() -> i32| {
        println!("Before function call (around)");
        let result = func();
        println!("After function call (around)");
        result
    };

    // 定义一个示例函数
    let my_function = || {
        println!("Inside the function");
        42 // 返回一些值
    };

    // 使用 aop_around! 宏包装函数调用
    let result = aop_around!(my_function, around);
    println!("Function returned: {}", result);
}

运行这个扩展示例,你会看到以下输出:

Before function call (around)
Inside the function
After function call (around)
Function returned: 42

1.4、更精确的切入点定义

定义宏和函数

首先,我们定义一个宏,用于在函数调用前后执行一些额外的逻辑,并允许通过条件判断来决定是否应用通知:

macro_rules! aop {
    ($func:expr, $before:expr, $after:expr, $pointcut:expr) => {{
        if $pointcut() {
            $before();
        }
        let result = $func();
        if $pointcut() {
            $after();
        }
        result
    }};
}

这个宏接受四个参数:

  • $func:要调用的函数。
  • $before:在函数调用前执行的闭包。
  • $after:在函数调用后执行的闭包。
  • $pointcut:一个返回布尔值的闭包,用于决定是否应用通知。
使用宏实现更精确的切入点

接下来,我们定义一些示例函数和通知,并使用 aop! 宏来包装函数调用,同时定义切入点条件:

fn main() {
    // 定义前置通知
    let before = || println!("Before function call");

    // 定义后置通知
    let after = || println!("After function call");

    // 定义一个示例函数
    let my_function = || {
        println!("Inside the function");
        42 // 返回一些值
    };

    // 定义切入点条件
    let pointcut = || true; // 可以根据需要修改条件

    // 使用 aop! 宏包装函数调用
    let result = aop!(my_function, before, after, pointcut);
    println!("Function returned: {}", result);
}

运行这个程序,你会看到以下输出:

Before function call
Inside the function
After function call
Function returned: 42

如果我们修改切入点条件,使其返回 false,则通知不会被应用:

fn main() {
    // 定义前置通知
    let before = || println!("Before function call");

    // 定义后置通知
    let after = || println!("After function call");

    // 定义一个示例函数
    let my_function = || {
        println!("Inside the function");
        42 // 返回一些值
    };

    // 定义切入点条件
    let pointcut = || false; // 修改条件

    // 使用 aop! 宏包装函数调用
    let result = aop!(my_function, before, after, pointcut);
    println!("Function returned: {}", result);
}

运行这个程序,你会看到以下输出:

Inside the function
Function returned: 42

扩展切入点条件

为了更灵活地定义切入点条件,我们可以将条件逻辑扩展为更加复杂的表达式。例如,我们可以根据函数名称、参数类型或其他上下文信息来决定是否应用通知。

fn main() {
    // 定义前置通知
    let before = || println!("Before function call");

    // 定义后置通知
    let after = || println!("After function call");

    // 定义多个示例函数
    let my_function1 = || {
        println!("Inside function 1");
        42 // 返回一些值
    };

    let my_function2 = || {
        println!("Inside function 2");
        24 // 返回一些值
    };

    // 定义切入点条件
    let pointcut = |func_name: &str| func_name == "my_function1";

    // 使用 aop! 宏包装函数调用
    let result1 = aop!(my_function1, before, after, || pointcut("my_function1"));
    println!("Function 1 returned: {}", result1);

    let result2 = aop!(my_function2, before, after, || pointcut("my_function2"));
    println!("Function 2 returned: {}", result2);
}

运行这个程序,你会看到以下输出:

Before function call
Inside function 1
After function call
Function 1 returned: 42
Inside function 2
Function 2 returned: 24

在这个示例中,只有 my_function1 满足切入点条件,因此只有它的调用前后会执行通知,而 my_function2 则不会。

1.5、简化切入点的定义过程

为了简化切入点的定义过程,我们可以通过封装和抽象来减少重复代码,并使切入点的定义更加直观和易于管理。以下是一些方法,可以帮助我们简化切入点的定义过程:

  1. 使用宏进行封装:将切入点逻辑封装在宏中,使其更易于复用。
  2. 使用函数指针或闭包:将切入点条件作为参数传递给宏,以便灵活地定义不同的切入点。
  3. 定义通用的切入点条件:创建一些常见的切入点条件函数,以便在不同场景中复用。

下面是一个示例,展示如何通过这些方法简化切入点的定义过程:

定义通用的切入点条件

首先,我们定义一些通用的切入点条件函数,这些函数可以根据需要进行扩展:

fn always_true() -> bool {
    true
}

fn always_false() -> bool {
    false
}

fn function_name_is(target: &str, func_name: &str) -> bool {
    target == func_name
}
封装宏

接下来,我们定义一个宏,用于在函数调用前后执行通知,并接受切入点条件作为参数:

macro_rules! aop {
    ($func:expr, $before:expr, $after:expr, $pointcut:expr) => {{
        if $pointcut() {
            $before();
        }
        let result = $func();
        if $pointcut() {
            $after();
        }
        result
    }};
}
使用宏和通用切入点条件

最后,我们使用这些通用的切入点条件和宏来包装函数调用:

fn main() {
    // 定义前置通知
    let before = || println!("Before function call");

    // 定义后置通知
    let after = || println!("After function call");

    // 定义多个示例函数
    let my_function1 = || {
        println!("Inside function 1");
        42 // 返回一些值
    };

    let my_function2 = || {
        println!("Inside function 2");
        24 // 返回一些值
    };

    // 使用 aop! 宏包装函数调用,并使用通用切入点条件
    let result1 = aop!(my_function1, before, after, || function_name_is("my_function1", "my_function1"));
    println!("Function 1 returned: {}", result1);

    let result2 = aop!(my_function2, before, after, || function_name_is("my_function1", "my_function2"));
    println!("Function 2 returned: {}", result2);
}

运行这个程序,你会看到以下输出:

Before function call
Inside function 1
After function call
Function 1 returned: 42
Inside function 2
Function 2 returned: 24

在这个示例中,我们使用了 function_name_is 函数来定义切入点条件,从而简化了切入点的定义过程。通过这种方式,我们可以轻松地复用通用的切入点条件,并且使代码更加简洁和易于维护。

2、过程宏实现 AOP

在 Rust 中,过程宏(procedural macros)是一种强大的工具,可以用来生成代码、修改代码结构以及实现复杂的编译时逻辑。通过使用过程宏属性,我们可以实现类似 AOP 的功能,并且使切入点的定义更加简洁和直观。

下面是一个示例,展示如何使用过程宏属性来实现 AOP 编程范例。

2.1、创建过程宏

首先,我们需要创建一个新的 Rust 库项目,用于定义我们的过程宏。你可以使用以下命令创建一个新的库项目:

cargo new aop_macro --lib

然后,在 Cargo.toml 文件中添加对 syn 和 quote crate 的依赖:

[dependencies]
syn = { version = "2", features = ["full"] }
quote = "1"
proc-macro2 = "1"

[lib]
proc-macro = true

接下来,在 src/lib.rs 文件中定义我们的过程宏:

extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn aop(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let func_name = &input.sig.ident;
    let block = &input.block;

    // 解析前置和后置通知
    let attr_args = attr.to_string();
    let args: Vec<&str> = attr_args.split(',').collect();
    let before = args.get(0).map(|s| s.trim()).unwrap_or("");
    let after = args.get(1).map(|s| s.trim()).unwrap_or("");

    let before_ident = syn::Ident::new(before, proc_macro2::Span::call_site());
    let after_ident = syn::Ident::new(after, proc_macro2::Span::call_site());

    let expanded = quote! {
        fn #func_name() {
            #before_ident();
            let result = (|| #block)();
            #after_ident();
            result
        }
    };

    TokenStream::from(expanded)
}

这个过程宏接受两个参数:前置通知和后置通知的函数名。它会在目标函数调用前后插入这些通知。

2.2、使用过程宏

接下来,我们在一个新的二进制项目中使用这个过程宏。你可以使用以下命令创建一个新的二进制项目:

cargo new aop_example

然后,在 aop_example 项目的 Cargo.toml 文件中添加对 aop_macro 库的依赖:

[dependencies]
aop_macro = { path = "../aop_macro" }

接下来,在 src/main.rs 文件中使用我们定义的过程宏:

use aop_macro::aop;

fn before() {
    println!("Before function call");
}

fn after() {
    println!("After function call");
}

#[aop(before, after)]
fn my_function() {
    println!("Inside the function");
}

fn main() {
    my_function();
}

运行这个程序,你会看到以下输出:

Before function call
Inside the function
After function call

通过使用过程宏属性,我们可以在 Rust 中实现类似 AOP 的功能,并且使切入点的定义更加简洁和直观。过程宏允许我们在编译时生成和修改代码,从而实现复杂的编译时逻辑。

2.3、优化过程宏的性能

优化过程宏的性能主要涉及减少编译时间和生成高效的代码。以下是一些优化过程宏性能的方法:

  1. 避免不必要的解析和转换。在编写过程宏时,尽量避免不必要的解析和转换操作。只解析和处理你需要的部分,以减少开销。
  2. 使用更高效的数据结构。在处理过程中,选择合适的数据结构可以提高效率。例如,使用 Vec 而不是 HashMap,如果你只需要顺序访问元素。
  3. 缓存结果。如果你的过程宏需要进行重复计算,可以考虑缓存中间结果以减少重复计算的开销。
  4. 减少依赖。尽量减少对外部 crate 的依赖,特别是那些会增加编译时间的依赖。对于必须使用的依赖,确保它们是最新版本,因为新版本通常包含性能改进。
  5. 优化生成的代码。确保生成的代码是高效的,不引入不必要的开销。例如,避免生成多余的闭包或函数调用。
  6. 使用 syn 和 quote 的高级特性。syn 和 quote 提供了许多高级特性,可以帮助你更高效地解析和生成代码。熟悉这些特性并加以利用,可以显著提高过程宏的性能。

三、与Spring Boot的AOP机制对比

Spring Boot 的 AOP(面向切面编程)机制和 Rust 中使用过程宏实现的 AOP 机制在概念上有相似之处,但在实现方式和应用场景上有显著的不同。以下是对这两种机制的详细对比:

1、实现方式

Spring Boot AOP:

  • 基于代理:Spring AOP 主要通过动态代理(JDK 动态代理或 CGLIB 代理)来实现。这意味着 Spring AOP 在运行时生成代理对象,并在调用目标方法之前和之后插入通知逻辑。
  • 注解驱动:Spring AOP 使用注解(如 @Aspect@Before@After 等)来定义切面和通知。开发者可以通过这些注解轻松地将横切关注点(如日志记录、事务管理等)应用到目标方法上。
@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }

    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("After method: " + joinPoint.getSignature().getName());
    }
}

Rust 过程宏 AOP:

  • 编译时代码生成:Rust 的过程宏在编译时生成代码。这意味着所有的切面逻辑在编译时就已经确定,不会在运行时引入额外的开销。
  • 宏属性:通过自定义的宏属性,开发者可以在编译时插入通知逻辑。这个过程需要解析和修改抽象语法树(AST),然后生成新的代码。
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn aop(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let func_name = &input.sig.ident;
    let block = &input.block;

    // 解析前置和后置通知
    let attr_args = attr.to_string();
    let args: Vec<&str> = attr_args.split(',').collect();
    let before = args.get(0).map(|s| s.trim()).unwrap_or("");
    let after = args.get(1).map(|s| s.trim()).unwrap_or("");

    let before_ident = syn::Ident::new(before, proc_macro2::Span::call_site());
    let after_ident = syn::Ident::new(after, proc_macro2::Span::call_site());

    let expanded = quote! {
        fn #func_name() {
            #before_ident();
            let result = (|| #block)();
            #after_ident();
            result
        }
    };

    TokenStream::from(expanded)
}

2、性能

Spring Boot AOP:

  • 运行时开销:由于 Spring AOP 基于动态代理,在运行时可能会引入一些性能开销,特别是在创建代理对象和方法调用时。
  • 灵活性:尽管有一定的运行时开销,Spring AOP 提供了极大的灵活性,可以在运行时动态地应用和移除切面。

Rust 过程宏 AOP:

  • 编译时开销:Rust 的过程宏在编译时进行代码生成,因此不会在运行时引入额外的开销。编译时的处理可能会增加编译时间,但生成的代码在运行时非常高效。
  • 静态性:由于所有的切面逻辑在编译时就已经确定,缺乏运行时的灵活性。这意味着无法在运行时动态地改变切面逻辑。

3、应用场景

Spring Boot AOP:

  • 企业级应用:Spring AOP 广泛应用于企业级 Java 应用中,用于处理横切关注点,如事务管理、日志记录、安全性检查等。
  • 动态配置:适用于需要在运行时动态配置和调整切面的场景。

Rust 过程宏 AOP:

  • 系统编程:Rust 更适合系统编程和性能关键的应用场景。通过过程宏实现的 AOP 可以在不引入运行时开销的情况下实现类似的功能。
  • 编译时保证:适用于需要在编译时确定所有逻辑的场景,提供更高的性能和安全性保证。

4、易用性

Spring Boot AOP:

  • 易于使用:Spring AOP 提供了丰富的注解和配置选项,使得开发者可以轻松地定义和应用切面。
  • 强大的生态系统:Spring 框架本身提供了大量的工具和库,与 AOP 紧密集成,进一步简化了开发过程。

Rust 过程宏 AOP:

  • 学习曲线:编写过程宏需要深入了解 Rust 的宏系统和编译器插件,具有一定的学习曲线。
  • 定制化:虽然过程宏提供了强大的功能,但需要开发者手动编写和维护宏代码,增加了复杂性。

5、总结

Spring Boot 的 AOP 机制和 Rust 中使用过程宏实现的 AOP 机制各有优劣。Spring AOP 提供了极大的灵活性和易用性,适用于企业级应用和动态配置的场景。而 Rust 的过程宏 AOP 则在性能和编译时保证方面具有优势,更适合系统编程和性能关键的应用。

四、AOP 实现记录日志(线程安全)

Step1、新增日志依赖

在 Cargo.toml 中添加 log 和 env_logger 依赖:

[dependencies]
syn = { version = "2", features = ["full"] }
quote = "1"
proc-macro2 = "1"
env_logger = "0.11.5"
log = "0.4.22"

[lib]
proc-macro = true

Step2、实现 aop 逻辑

修改过程宏文件 src/lib.rs

extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};

#[proc_macro_attribute]
pub fn aop(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let func_name = &input.sig.ident;
    let fn_args = &input.sig.inputs;
    let fn_return_type = &input.sig.output;
    let block = &input.block;

    // 解析前置和后置通知
    let attr_args = attr.to_string();
    let args: Vec<&str> = attr_args.split(',').collect();
    let before = args.get(0).map(|s| s.trim()).unwrap_or("");
    let after = args.get(1).map(|s| s.trim()).unwrap_or("");

    let before_ident = syn::Ident::new(before, proc_macro2::Span::call_site());
    let after_ident = syn::Ident::new(after, proc_macro2::Span::call_site());

    let expanded = quote! {
        fn #func_name(#fn_args) #fn_return_type {
            log::info!("Before function call: {} with args: {:?}", stringify!(#func_name), (#fn_args));
            #before_ident();
            let result = (|| #block)();
            #after_ident();
            log::info!("After function call: {} returned: {:?}", stringify!(#func_name), result);
            result
        }
    };

    TokenStream::from(expanded)
}

Step3、使用 aop 过程宏

在 src/main.rs 中使用这个过程宏,并初始化日志记录器:

[dependencies]
aop_macro = { path = "../aop_macro" }
env_logger = "0.11.5"
log = "0.4.22"
use aop_macro::aop;

fn before() {
    println!("Before function call");
}

fn after() {
    println!("After function call");
}

#[aop(before, after)]
fn my_function() {
    println!("Inside the function");
}

fn main() {
    env_logger::init();
    my_function();
}

通过设置 RUST_LOG 环境变量,你可以控制显示哪些日志信息。例如: 

# RUST_LOG=info ./your_executable
RUST_LOG=info ./target/debug/aop_example

这将展示所有级别为 info 及其以上的日志条目。日志级别包括 errorwarninfodebug 和 trace,不区分大小写。

[2024-11-08T09:51:57Z INFO  aop_example] Before function call: my_function with args: ()
Before function call
Inside the function
After function call
[2024-11-08T09:51:57Z INFO  aop_example] After function call: my_function returned: ()

五、根据配置生成代码

我们可以编写一个过程宏来解析配置文件,并为指定的函数添加前置和后置操作。我们将通过以下步骤来实现这个功能:

  1. 定义一个配置文件,包含函数名、前置操作函数和后置操作函数。
  2. 编写一个过程宏,读取配置文件并为指定的函数添加前置和后置操作。
  3. 使用过程宏修饰目标函数。

Step1、创建宏

cargo new aop_config --lib

Cargo.toml 添加依赖项,

[dependencies]
env_logger = "0.11.5"
log = "0.4.22"
chrono = "0.4"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
quote = "1"
proc-macro2 = "1"
syn = { version = "2", features = ["full"] }

[lib]
proc-macro = true

修改 src/lib.rs

extern crate proc_macro;
use chrono::Utc;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use std::fs;
use syn::{parse_macro_input, ItemFn};

#[derive(serde::Deserialize)]
struct Config {
    func_name: String,
    before: String,
    after: String,
}

#[proc_macro_attribute]
pub fn aop(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemFn);
    let fn_name = &input.sig.ident;
    let fn_block = &input.block;
    let fn_inputs = &input.sig.inputs;
    let fn_output = &input.sig.output;

    let arg_names = fn_inputs.iter().map(|arg| {
        if let syn::FnArg::Typed(pat_type) = arg {
            if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
                return pat_ident.ident.to_string();
            }
        }
        "".to_string()
    });

    // Read and parse the configuration file
    let config_data = match fs::read_to_string("config.json") {
        Ok(data) => data,
        Err(_) => return TokenStream::from(quote! { #input }),
    };

    let configs: Vec<Config> = match serde_json::from_str(&config_data) {
        Ok(configs) => configs,
        Err(_) => return TokenStream::from(quote! { #input }),
    };

    // Find the matching configuration for the function
    let config = configs.iter().find(|c| c.func_name == fn_name.to_string());

    let expanded = if let Some(config) = config {
        let before_fn = syn::Ident::new(&config.before, fn_name.span());
        let after_fn = syn::Ident::new(&config.after, fn_name.span());

        quote! {

            fn #fn_name(#fn_inputs) #fn_output {

                // before_fn
                if !#before_fn() {
                    log::info!("Function {} skipped due to {}", stringify!(#fn_name), stringify!(#before_fn));
                    return Default::default();
                }

                // fn_block
                //println!("Function {} called with args: {:?}", stringify!(#fn_name), (#(#arg_names),*));
                let start_time = Utc::now();
                log::info!("Before function call: {} with args: {:?}", stringify!(#fn_name), (#(#arg_names),*));
                let result = (|| #fn_block)();
                //println!("Function {} returned: {:?}", stringify!(#fn_name), result);
                let end_time = Utc::now();
                log::info!("After function call: {} returned: {:?}, elapsed time: {:?}", stringify!(#fn_name), result, end_time - start_time);

                // after_fn
                #after_fn();

                result
            }

        }
    } else {
        quote! {
            fn #fn_name(#fn_inputs) #fn_output {
                //println!("Function {} called with args: {:?}", stringify!(#fn_name), (#(#arg_names),*));
                let start_time = Utc::now();
                log::info!("Before function call: {} with args: {:?}", stringify!(#fn_name), (#(#arg_names),*));
                let result = (|| #fn_block)();
                //println!("Function {} returned: {:?}", stringify!(#fn_name), result);
                let end_time = Utc::now();
                log::info!("After function call: {} returned: {:?}, elapsed time: {:?}", stringify!(#fn_name), result, end_time - start_time);
                result
            }
        }
    };

    expanded.into()
}

Step2、使用宏

在 src/main.rs 中使用这个过程宏,并实现 fun_rule 和 fun_log 函数:

[dependencies]
aop_macro = { path = "../aop_macro" }
aop_config= { path = "../aop_config" }
env_logger = "0.11.5"
log = "0.4.22"
chrono = "0.4"
use aop_config::aop;
use env_logger::Builder;
use log::LevelFilter;
use std::collections::HashMap;
use chrono::Utc;

#[aop]
fn fun1(a:i32, b: i32, c: i32) -> (i32, i32, i32) {
    println!("Inside fun1");
    (a + 1, b + 1, c + 1)
}

#[aop]
fn fun2(d: i32, e: i32, f: i32) -> (i32, i32, i32) {
    println!("Inside fun2");
    (d * 2, e * 2, f * 2)
}

#[aop]
fn fun3(x: i32, y: i32, z: i32) -> (i32, i32, i32) {
    println!("Inside fun3");
    (x - 1, y - 1, z - 1)
}

fn fun_rule() -> bool {
    // Define your rule here
    true
}

fn fun_log() {
    // Define your log operation here
    println!("--------> Executing fun_log after function execution");
}

fn main() {
    // Initialize logger
    Builder::new().filter(None, LevelFilter::Info).init();

    // Define the execution sequence
    let sequence = vec![("fun1", vec![1, 2, 3]), ("fun2", vec![]), ("fun3", vec![])];

    // Create a map of function pointers
    let mut functions: HashMap<&str, Box<dyn Fn(Vec<i32>) -> Vec<i32>>> = HashMap::new();
    functions.insert(
        "fun1",
        Box::new(|args| {
            let (a, b, c) = (args[0], args[1], args[2]);
            let (d, e, f) = fun1(a, b, c);
            vec![d, e, f]
        }),
    );
    functions.insert(
        "fun2",
        Box::new(|args| {
            let (d, e, f) = (args[0], args[1], args[2]);
            let (x, y, z) = fun2(d, e, f);
            vec![x, y, z]
        }),
    );
    functions.insert(
        "fun3",
        Box::new(|args| {
            let (x, y, z) = (args[0], args[1], args[2]);
            let (p, q, r) = fun3(x, y, z);
            vec![p, q, r]
        }),
    );

    // Execute the sequence
    let mut current_args = sequence[0].1.clone();
    for (func_name, _) in &sequence {
        if let Some(func) = functions.get(func_name) {
            current_args = func(current_args);
        } else {
            panic!("Function {} not found", func_name);
        }
    }

    println!("Final result: {:?}", current_args);
}

Step3、配置文件

在主项目根目录下创建一个配置文件 config.json,内容如下:

[
    {
        "func_name": "fun1",
        "before": "fun_rule",
        "after": "fun_log"
    },
    {
        "func_name": "fun2",
        "before": "fun_rule",
        "after": "fun_log"
    }
]

在这个示例中,我们定义了三个函数 fun1fun2 和 fun3,并使用 AOP 宏来记录它们的参数值、返回值和耗时时间。我们还实现了一个简单的调度算法,根据预定义的执行顺序依次调用这些函数,并将每个函数的返回值作为下一个函数的参数。

此外,我们引入了 fun_rule 和 fun_log 函数。fun_rule 用于判断是否执行函数主体,如果不满足条件,则返回一个默认值。fun_log 用于在函数执行后进行额外的日志操作。

通过这种方式,我们实现了一个通用的函数调度算法,同时使用 AOP 记录每个函数的参数值、返回值和耗时时间,并根据配置条件决定是否执行某些函数。在多线程环境中,这种方法也是适用的,因为我们使用了线程安全的日志库 log 和 env_logger

Step4、运行效果

[2024-11-09T07:29:57Z INFO  aop_example] Before function call: fun1 with args: ("a", "b", "c")
Inside fun1
[2024-11-09T07:29:57Z INFO  aop_example] After function call: fun1 returned: (2, 3, 4), elapsed time: TimeDelta { secs: 0, nanos: 29902 }
--------> Executing fun_log after function execution
[2024-11-09T07:29:57Z INFO  aop_example] Before function call: fun2 with args: ("d", "e", "f")
Inside fun2
[2024-11-09T07:29:57Z INFO  aop_example] After function call: fun2 returned: (4, 6, 8), elapsed time: TimeDelta { secs: 0, nanos: 8210 }
--------> Executing fun_log after function execution
[2024-11-09T07:29:57Z INFO  aop_example] Before function call: fun3 with args: ("x", "y", "z")
Inside fun3
[2024-11-09T07:29:57Z INFO  aop_example] After function call: fun3 returned: (3, 5, 7), elapsed time: TimeDelta { secs: 0, nanos: 7769 }
Final result: [3, 5, 7]

Step5、源代码解析

我们可以通过 cargo-expand 查看编译器对代码进行宏展开后的结果,

cargo expand

#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use aop_config::aop;
use env_logger::Builder;
use log::LevelFilter;
use std::collections::HashMap;
use chrono::Utc;
fn fun1(a: i32, b: i32, c: i32) -> (i32, i32, i32) {
    if !fun_rule() {
        {
            let lvl = ::log::Level::Info;
            if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {
                ::log::__private_api::log(
                    format_args!("Function {0} skipped due to {1}", "fun1", "fun_rule"),
                    lvl,
                    &("aop_example", "aop_example", ::log::__private_api::loc()),
                    (),
                );
            }
        };
        return Default::default();
    }
    let start_time = Utc::now();
    {
        let lvl = ::log::Level::Info;
        if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {
            ::log::__private_api::log(
                format_args!(
                    "Before function call: {0} with args: {1:?}",
                    "fun1",
                    ("a", "b", "c"),
                ),
                lvl,
                &("aop_example", "aop_example", ::log::__private_api::loc()),
                (),
            );
        }
    };
    let result = (|| {
        {
            ::std::io::_print(format_args!("Inside fun1\n"));
        };
        (a + 1, b + 1, c + 1)
    })();
    let end_time = Utc::now();
    {
        let lvl = ::log::Level::Info;
        if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {
            ::log::__private_api::log(
                format_args!(
                    "After function call: {0} returned: {1:?}, elapsed time: {2:?}",
                    "fun1",
                    result,
                    end_time - start_time,
                ),
                lvl,
                &("aop_example", "aop_example", ::log::__private_api::loc()),
                (),
            );
        }
    };
    fun_log();
    result
}
fn fun2(d: i32, e: i32, f: i32) -> (i32, i32, i32) {
    if !fun_rule() {
        {
            let lvl = ::log::Level::Info;
            if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {
                ::log::__private_api::log(
                    format_args!("Function {0} skipped due to {1}", "fun2", "fun_rule"),
                    lvl,
                    &("aop_example", "aop_example", ::log::__private_api::loc()),
                    (),
                );
            }
        };
        return Default::default();
    }
    let start_time = Utc::now();
    {
        let lvl = ::log::Level::Info;
        if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {
            ::log::__private_api::log(
                format_args!(
                    "Before function call: {0} with args: {1:?}",
                    "fun2",
                    ("d", "e", "f"),
                ),
                lvl,
                &("aop_example", "aop_example", ::log::__private_api::loc()),
                (),
            );
        }
    };
    let result = (|| {
        {
            ::std::io::_print(format_args!("Inside fun2\n"));
        };
        (d * 2, e * 2, f * 2)
    })();
    let end_time = Utc::now();
    {
        let lvl = ::log::Level::Info;
        if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {
            ::log::__private_api::log(
                format_args!(
                    "After function call: {0} returned: {1:?}, elapsed time: {2:?}",
                    "fun2",
                    result,
                    end_time - start_time,
                ),
                lvl,
                &("aop_example", "aop_example", ::log::__private_api::loc()),
                (),
            );
        }
    };
    fun_log();
    result
}
fn fun3(x: i32, y: i32, z: i32) -> (i32, i32, i32) {
    let start_time = Utc::now();
    {
        let lvl = ::log::Level::Info;
        if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {
            ::log::__private_api::log(
                format_args!(
                    "Before function call: {0} with args: {1:?}",
                    "fun3",
                    ("x", "y", "z"),
                ),
                lvl,
                &("aop_example", "aop_example", ::log::__private_api::loc()),
                (),
            );
        }
    };
    let result = (|| {
        {
            ::std::io::_print(format_args!("Inside fun3\n"));
        };
        (x - 1, y - 1, z - 1)
    })();
    let end_time = Utc::now();
    {
        let lvl = ::log::Level::Info;
        if lvl <= ::log::STATIC_MAX_LEVEL && lvl <= ::log::max_level() {
            ::log::__private_api::log(
                format_args!(
                    "After function call: {0} returned: {1:?}, elapsed time: {2:?}",
                    "fun3",
                    result,
                    end_time - start_time,
                ),
                lvl,
                &("aop_example", "aop_example", ::log::__private_api::loc()),
                (),
            );
        }
    };
    result
}
fn fun_rule() -> bool {
    true
}
fn fun_log() {
    {
        ::std::io::_print(
            format_args!("--------> Executing fun_log after function execution\n"),
        );
    };
}

总结

在这个简单的例子中,我们使用了配置文件来控制过程宏的行为。我们定义了一个 aop 过程宏作用于函数,而该过程宏的逻辑取决于 JSON 配置文件的定义,从而影响编译源码。过程宏是在编译时执行的,它们可以根据输入生成新的代码。以此类推,如果过程宏的行为依赖于系统变量,那么这些变量的值会直接影响生成的代码。


http://www.kler.cn/a/386582.html

相关文章:

  • 基于SpringBoot+RabbitMQ完成应⽤通信
  • 企业如何提高招聘能力?
  • opencv常用api
  • ios swift开发--ios远程推送通知配置
  • apache2配置多站点
  • 【安全通信】告别信息泄露:搭建你的开源视频聊天系统briefing
  • vue2 -- el-form组件动态增减表单项及表单项验证
  • 关于我重生到21世纪学C语言这件事——三子棋游戏!
  • Java打造智能语音陪聊软件?提升用户体验的新路径
  • 【数据集】【YOLO】【目标检测】树木倒塌识别数据集 9957 张,YOLO道路树木断裂识别算法实战训练教程!
  • 让AI帮我用java实现EasyExel读取图片—支持WPS嵌入图片
  • python externally-managed-environment 外部管理环境
  • Kotlin 协程使用及其详解
  • 【go从零单排】结构嵌套struct embedding
  • C# 独立线程
  • 【Qwen2技术报告分析】解读模型架构 pre/post数据构建和模型评估
  • [Android] Graphic Buffer 的申请
  • C++:线程(thread)的创建、调用及销毁
  • Halcon resistor.hedv 使用多个对焦级别提取深度
  • .NET 黑名单上传 突破WAF防护的SoapShell (免杀版)
  • MySQL数据库专栏(五)连接MySQL数据库C API篇
  • Unity3D ASTC贴图压缩格式详解
  • MacOs上如何彻底卸载DevEco Studio?
  • VSCode 多工程联合调试
  • PythonBase02
  • MFC图形函数学习06——画椭圆弧线函数