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 配置文件的定义,从而影响编译源码。过程宏是在编译时执行的,它们可以根据输入生成新的代码。以此类推,如果过程宏的行为依赖于系统变量,那么这些变量的值会直接影响生成的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/912434.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于 SSM(Spring + Spring MVC + MyBatis)框架构建电器网上订购系统

基于 SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架构建电器网上订购系统可以为用户提供一个方便快捷的购物平台。以下将详细介绍该系统的开发流程&#xff0c;包括需求分析、技术选型、数据库设计、项目结构搭建、主要功能实现以及前端页面设计。 需求分析 …

Docker部署Oracle 11g

1&#xff0c;拉取镜像&#xff1a; sudo docker pull registry.cn-hangzhou.aliyuncs.com/helowin/oracle_11gsudo docker images 2&#xff0c;启动一个临时容器&#xff0c;用于拷贝数据库文件&#xff0c;挂载到宿主主机&#xff0c;使数据持久化&#xff1a; sudo docke…

Linux操作系统:学习进程_了解并掌握进程的状态

对进程状态之间转换感到头疼&#xff0c;只听书本概念根本无法理解&#xff0c;死记硬背不是什么好的解决方法。只有进行底层操作去了解每一个进程状态&#xff0c;才能彻底弄清楚进程状态是如何转换的。 一、进程的各个状态 我们先从Linux内核数据结构来看&#xff1a; 每一个…

分布式环境下宕机的处理方案有哪些?

大家好&#xff0c;我是锋哥。今天分享关于【分布式环境下宕机的处理方案有哪些&#xff1f;】面试题。希望对大家有帮助&#xff1b; 分布式环境下宕机的处理方案有哪些&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在现代分布式系统中&#xff0c;…

接触角测量(Contact Angle Measurement)

接触角是指液滴在固体表面上的形态&#xff0c;特别是在气、液、固三相交界点处&#xff0c;气-液界面切线与固-液交界线之间的夹角。这个角度是衡量液体对固体表面润湿性的一个重要参数。接触角的大小可以反映液体与固体之间的相互作用强度&#xff0c;从而了解液体在固体表面…

《操作系统 - 清华大学》2 -2:中断、异常和系统调用

文章目录 1. 中断和异常处理机制1.1 中断1.2 异常 2. 系统调用2.1 标志C库的例子2.2 编程接口 3.系统调用的实现4. 程序调用和系统调用的不同处5. 中断、异常和系统调用的开销 1. 中断和异常处理机制 接下来看一看中断和异常的处理过程&#xff0c;看下图就比较清楚&#xff0…

Nginx简易配置将内网网站ssh转发到外网

声明&#xff1a;本内容仅供交流学习使用&#xff0c;部署网站上线还需要根据有关规定申请域名以及备案。 背景 在内网的服务器有一个运行的网页&#xff0c;现使用ssh反向代理&#xff0c;将它转发到外网的服务器。 但是外网的访问ip会被ssh反向代理拦截 所以使用Nginx进行…

Moment.js、Day.js、Miment,日期时间库怎么选?

一直以来&#xff0c;处理时间和日期的JavaScript库&#xff0c;选用的都是Momment.js。它的API清晰简单&#xff0c;使用方便灵巧&#xff0c;功能还特别齐全。 大师兄是Moment.js的重度使用者。凡是遇到时间和日期的操作&#xff0c;就把Moment.js引用上。 直到有天我发现加…

后台管理系统窗体程序:文章管理 > 文章发表

目录 文章列表的的功能介绍&#xff1a; 1、进入页面 2、页面内的各种功能设计 &#xff08;1&#xff09;进入选择 &#xff08;2&#xff09;当获取到唯一标识符时 &#xff08;3&#xff09;当没有标识符时 &#xff08;4&#xff09;发布按钮&#xff0c;存为草稿 一、网…

Linux服务控制及系统基本加固

一. liunx操作系统的开机引导的过程 1. 开机自检 根据bios的设置&#xff0c;对cpu,内存&#xff0c;显卡&#xff0c;键盘等等设备进行初步检测如果以上检测设备工作正常&#xff0c;系统会把控制权移交到硬盘 总结:检测出包含系统启动操作系统的设备&#xff0c;硬盘&#…

通过 SSH 隧道将本地端口转发到远程主机

由于服务器防火墙,只开放了22端口,想要通过5901访问服务器上的远程桌面,可以通过下面的方式进行隧道转发。 一、示例命令 这条代码的作用是通过 SSH 创建一个 本地端口转发,将你本地的端口(5901)通过加密的 SSH 隧道连接到远程服务器上的端口(5901)。这种方式通常用于在…

WPF+MVVM案例实战与特效(二十八)- 自定义WPF ComboBox样式:打造个性化下拉菜单

文章目录 1. 引言案例效果3. ComboBox 基础4. 自定义 ComboBox 样式4.1 定义 ComboBox 样式4.2 定义 ComboBoxItem 样式4.3 定义 ToggleButton 样式4.4 定义 Popup 样式5. 示例代码6. 结论1. 引言 在WPF应用程序中,ComboBox控件是一个常用的输入控件,用于从多个选项中选择一…

C#中日期和时间的处理

目录 前言 时间对于我们的作用 一些关于时间的名词说明 格里高利历 格林尼治时间(GMT) 协调世界时(UTC) 时间戳 DateTime 初始化 获取时间 计算时间 字符串转DateTime 存储时间 TimeSpan 初始化它来代表时间间隔 用它相互计算 自带常量方便用于和ticks进行计…

pdb和gdb的双剑合璧,在python中调试c代码

左手编程&#xff0c;右手年华。大家好&#xff0c;我是一点&#xff0c;关注我&#xff0c;带你走入编程的世界。 公众号&#xff1a;一点sir&#xff0c;关注领取python编程资料 问题背景 正常情况下&#xff0c;调试python代码用pdb&#xff0c;调试c代码用gdb&#xff0c;…

【Apache ECharts】<农作物病害发生防治面积>

在vs Code里打开&#xff0c; 实现 1. 首先引入 echarts.min.js 资源 2. 在body部分设一个 div&#xff0c;设置 id 为 main 3. 设置 script 3.1 基于准备好的dom&#xff0c;初始化echarts实例 var myChart echarts.init(document.getElementById(main)); 3.2 指定图表的…

Docker + Jenkins + gitee 实现CICD环境搭建

目录 前言 关于Jenkins 安装Jenkins docker中运行Jenkins注意事项 通过容器中的Jenkins&#xff0c;把服务打包到docker进行部署 启动Jenkins 创建第一个任务 前言 CI/CD&#xff08;持续集成和持续交付/持续部署&#xff09;&#xff0c;它可以实现自动化的构建、测试和部署…

Leetcode 买卖股票的最佳时机 Ⅱ

使用贪心算法来解决此问题&#xff0c;通过在价格上涨的每一天买入并在第二天卖出的方式&#xff0c;累计所有上涨的利润&#xff0c;以实现最大收益。关键点是从第二天开始遍历&#xff0c;并且只要当前比前一天价格高&#xff0c;我们就在前一天买入然后第二天卖出去。下面是…

【Linux系列】命令行中的文本处理:从中划线到下划线与大写转换

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

使用Docker快速部署FastAPI Web应用

Docker是基于 Linux 内核的cgroup、namespace以及 AUFS 类的Union FS 等技术&#xff0c;对进程进行封装隔离&#xff0c;一种操作系统层面的虚拟化技术。Docker中每个容器都基于镜像Image运行&#xff0c;镜像是容器的只读模板&#xff0c;容器是模板的一个实例。镜像是分层结…

【go从零单排】迭代器(Iterators)

&#x1f308;Don’t worry , just coding! 内耗与overthinking只会削弱你的精力&#xff0c;虚度你的光阴&#xff0c;每天迈出一小步&#xff0c;回头时发现已经走了很远。 &#x1f4d7;概念 在 Go 语言中&#xff0c;迭代器的实现通常不是通过语言内置的迭代器类型&#x…