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

如何注册域名邮箱/宁波seo教程行业推广

如何注册域名邮箱,宁波seo教程行业推广,做网站专家,做现货黄金网站文章本天成,妙手偶得之。粹然无疵瑕,岂复须人为?君看古彝器,巧拙两无施。汉最近先秦,固已殊淳漓。胡部何为者,豪竹杂哀丝。后夔不复作,千载谁与期? ——《文章》宋陆游 【哲理】文章…

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

——《文章》宋·陆游

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

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

一、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 filelet 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 functionlet 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_fnif !#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 heretrue
}fn fun_log() {// Define your log operation hereprintln!("--------> Executing fun_log after function execution");
}fn main() {// Initialize loggerBuilder::new().filter(None, LevelFilter::Info).init();// Define the execution sequencelet sequence = vec![("fun1", vec![1, 2, 3]), ("fun2", vec![]), ("fun3", vec![])];// Create a map of function pointerslet 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 sequencelet 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 配置文件的定义,从而影响编译源码。过程宏是在编译时执行的,它们可以根据输入生成新的代码。以此类推,如果过程宏的行为依赖于系统变量,那么这些变量的值会直接影响生成的代码。

相关文章:

Rust-AOP编程实战

文章本天成&#xff0c;妙手偶得之。粹然无疵瑕&#xff0c;岂复须人为&#xff1f;君看古彝器&#xff0c;巧拙两无施。汉最近先秦&#xff0c;固已殊淳漓。胡部何为者&#xff0c;豪竹杂哀丝。后夔不复作&#xff0c;千载谁与期&#xff1f; ——《文章》宋陆游 【哲理】文章…...

Flutter鸿蒙next 中的 Expanded 和 Flexible 使用技巧详解

在 Flutter 开发中&#xff0c;Expanded 和 Flexible 是两个非常常用的布局控件&#xff0c;它们可以帮助开发者更加灵活地管理 UI 布局的空间分配。虽然它们看起来非常相似&#xff0c;但它们的功能和使用场景有所不同。理解这两者的区别&#xff0c;能帮助你在构建复杂 UI 布…...

【微信小游戏学习心得】

这里是引用 微信小游戏学习心得 简介了解微信小游戏理解2d游戏原理数据驱动视图总结 简介 本人通过学习了解微信小游戏&#xff0c;学习微信小游戏&#xff0c;加深了对前端框架&#xff0c;vue和react基于数据驱动视图的理解&#xff0c;及浏览器文档模型和javaScript之间的关…...

Python | Leetcode Python题解之第539题最小时间差

题目&#xff1a; 题解&#xff1a; def getMinutes(t: str) -> int:return ((ord(t[0]) - ord(0)) * 10 ord(t[1]) - ord(0)) * 60 (ord(t[3]) - ord(0)) * 10 ord(t[4]) - ord(0)class Solution:def findMinDifference(self, timePoints: List[str]) -> int:n len…...

Zookeeper运维秘籍:四字命令基础、详解及业务应用全解析

文章目录 一、四字命令基础二、四字命令详解三、四字命令的开启与配置四、结合业务解读四字命令confconsenvi命令Stat命令MNTR命令ruok命令dump命令wchswchp ZooKeeper&#xff0c;作为一款分布式协调服务&#xff0c;提供了丰富的四字命令&#xff08;也称为四字短语&#xff…...

Error: `slot-scope` are deprecated报错解决

本人新手菜鸡&#xff0c;文章为自己遇到问题的记录&#xff0c;如有错误或不足还请大佬批评指正 问题描述 在Vue3环境下使用slot插槽&#xff0c;出现‘slot-scope’ are deprecated报错问题&#xff0c;经过查找发现&#xff0c;是因为在slot插槽使用中&#xff0c;vue2和vu…...

Excel(图例)中使用上标下标

单元格中 1、在Excel单元格中刷黑要设置成上标的字符&#xff0c;如m2中的2&#xff1b; 2、单击右键&#xff0c;在弹出的对话框中选择“设置单元格格式”&#xff1b; 3、在弹出的“设置单元格格式”对话框中选择上标&#xff08;或下标&#xff09;&#xff1b; 4、最后…...

熔断和降级

目录 隔离和降级 FeignClient整合Sentinel 通过Feign设置服务降级 1.创建类实现FallbackFactory接口&#xff0c;并让这个类和使用FeignClient的接口类绑定 2.让order-service服务的feign开启sentinel 3.测试&#xff0c;只开启order-service服务&#xff0c;而不开启user-…...

【学习笔记】Linux系统基础知识 6 —— su命令详解

提示&#xff1a;学习Linux系统基础命令 su 命令详解&#xff0c;包含通过 su 命令切换用户实例 一、前期准备 1.已经正确安装并成功进入Linux系统 说明&#xff1a;本实验采用的 Redhat 系统&#xff08;因系统不一致&#xff0c;可能部分显示存在差异&#xff09; 二、学…...

docker-compose命令介绍

docker-compose命令介绍 docker-compose1. docker-compose是什么2. Compose file format3. 命令3.1 服务相关命令upruncreatestartrestartdownstopkillrmpauseunpause 3.2 镜像相关命令3.3 查看相关命令 docker-compose 学了docker&#xff0c;然后就直接去学k8s了。恍恍惚惚几…...

Spring学习笔记_29——@Transactional

Transactional 1. 介绍 Transactional 是 Spring 框架提供的一个注解&#xff0c;用于声明方法或类级别的事务属性。 Spring事务&#xff1a;Spring学习笔记_28——事务-CSDN博客 当你在一个方法或类上使用 Transactional 注解时&#xff0c;Spring 会为该方法或类创建一个…...

github使用基础

要通过终端绑定GitHub账号并进行文件传输&#xff0c;你需要使用Git和SSH密钥来实现安全连接和操作。以下是一个基本流程&#xff1a; 设置GitHub和SSH 检查Git安装 通过终端输入以下命令查看是否安装Git&#xff1a; bash 复制代码 git --version配置Git用户名和邮箱 bash …...

Flink-Kafka-Connector

Apache Flink 是一个用于处理无界和有界数据的开源流处理框架。它支持高吞吐量、低延迟以及精确一次的状态一致性等特性。Flink 社区提供了丰富的连接器&#xff08;Connectors&#xff09;以方便与不同的数据源进行交互&#xff0c;其中就包括了 Apache Kafka 连接器。 Apach…...

远程终端vim里使用系统剪切板

1、本地通过终端远程linux server&#xff0c;由于不是桌面环境/GUI&#xff0c;终端vim里似乎没办法直接使用系统剪切板&#xff0c;即便已经是clipboard。 $ vim --version | grep clipboard clipboard keymap printer vertsplit eval …...

底层视角看C语言

文章目录 main函数很普通main函数之前调用了什么main函数和自定义函数的对比 变量名只为人而存在goto是循环的本质指针变量指针是一个特殊的数字汇编层面看指针 数组和指针数组越界问题低端地址越界高端地址越界 引用就是指针 main函数很普通 main函数是第一个被调用的函数吗&…...

【点云学习笔记】——分割任务学习

3D点云实例分割 vs 3D点云语义分割 1. 功能对比 代码1&#xff08;实例分割&#xff09;&#xff1a;用于3D点云中的实例分割任务&#xff0c;其目标是将点云中的物体分割成独立的实例。每个实例可能属于相同类别但需要被分开&#xff0c;比如在自动驾驶中的多个行人、汽车&am…...

Qt——窗口

一.窗口概述 Qt 窗口是通过 QMainWindow 类来实现的。 QMainWindow是一个为用户提供主窗口程序的类&#xff0c;继承QWidget类&#xff0c;并且提供一个预定义的布局。包含一个菜单栏&#xff08;menu bar&#xff09;&#xff0c;多个工具栏&#xff08;tool bars&#xff0…...

InfluxDB性能优化指南

1. 引言 1.1 InfluxDB的简介与发展背景 InfluxDB是一个开源的时间序列数据库&#xff08;TSDB&#xff09;&#xff0c;由InfluxData公司开发&#xff0c;专门用于处理高频率的数据写入和查询。其设计初衷是为物联网、应用程序监控、DevOps和实时分析等场景提供一个高效的存储…...

负载均衡式在线oj项目开发文档2(个人项目)

judge模块的框架 完成了网页渲染的功能之后&#xff0c;就需要判断用户提交的代码是否是正确的&#xff0c;当用户点击提交之后&#xff0c;就会交给路由模块的/judge模块&#xff0c;然后这个路由模块就需要去调用jude模块了&#xff0c;也就是需要一个新的jude模块&#xff…...

ssm081高校实验室管理系统的设计与实现+vue(论文+源码)_kaic

毕 业 设 计&#xff08;论 文&#xff09; 题目&#xff1a;高校实验室管理系统的设计与实现 摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很…...

GitLab基于Drone搭建持续集成(CI/CD)

本文介绍了如何为 Gitee 安装 Drone 服务器。服务器打包为在 DockerHub 上分发的最小 Docker 映像。 1. 准备工作 创建OAuth应用 创建 GitLab OAuth 应用。Consumer Key 和 Consumer Secret 用于授权访问极狐GitLab 资源。 ps:授权回调 URL 必须与以下格式和路径匹配&…...

用GPB外链打造长期稳定的SEO优势!

很多人在谈外链时&#xff0c;总喜欢纠结是追求数量还是追求质量。其实&#xff0c;最理想的策略是两者兼顾。而在这其中&#xff0c;GPB外链可以说是长期SEO提升的“法宝”。为什么这么说&#xff1f;因为GPB外链不仅保证了高质量&#xff0c;还附带了与网站主题高度相关的原创…...

第11章 内连接与外连接

一、介绍内连接与外连接 &#xff08;1&#xff09;内连接与外连接介绍 1、内连接&#xff1a;合并具有同一列的两个以上的表的行, 结果集中不包含一个表与另一个表不匹配的行。 2、外连接&#xff1a;: 两个表在连接过程中除了返回满足连接条件的行以外还返回左&#xff08…...

C++ 游戏开发:打造高效、性能优越的游戏世界

在游戏开发领域&#xff0c;C 一直是最受欢迎的编程语言之一。其高效的内存管理和对硬件的底层控制&#xff0c;使得 C 成为开发高性能游戏的首选语言。从大型 3D 游戏引擎到独立游戏的制作&#xff0c;C 在游戏开发中发挥了不可替代的作用。 本文将带你了解 C 在游戏开发中的…...

太速科技-440-基于XCVU440的多核处理器多输入芯片验证板卡

基于XCVU440的多核处理器多输入芯片验证板卡 一、板卡概述 本板卡系我司自主研发的基于6U CPCI处理板&#xff0c;适用于多核处理器多输入芯片验证的应用。芯片采用工业级设计。 基于XCVU440T的多核处理器多输入芯片验证板卡基于6U CPCI架构&#xff0c;是单机中的一个…...

澳鹏通过高质量数据支持 Onfido 优化AI反欺诈功能

“Appen 在 Onfido 的发展中发挥了至关重要的作用&#xff0c;并已成为我们运营的重要组成部分。我们很高兴在 Appen 找到了可靠的合作伙伴。” – Onfido 数据和分析总监 Francois Jehl 简介&#xff1a;利用人工智能和机器学习增强欺诈检测 在当今日益数字化的世界&#xff…...

基于ECS实例搭建Hadoop环境

环境搭建&#xff1a; 【ECS生长万物之开源】基于ECS实例搭建Hadoop环境-阿里云开发者社区 搭建Hadoop环境_云服务器 ECS(ECS)-阿里云帮助中心 Hadoop入门基础&#xff08;二&#xff09;&#xff1a;Hadoop集群安装与部署详解&#xff08;超详细教程&#xff09;&#xff0…...

关于vue如何监听route和state以及各自对应的实际场景

一、监听route 场景&#xff1a;监听浏览器地址栏分页参数的变化 // 注意 newPageNum和 oldPageNum是 string类型 $route.query.pageNum(newPageNum, oldPageNum) {if (newPageNum ! oldPageNum && newPageNum ! this.pageNum.toString()) {this.handleCurrentChange(p…...

【计网不挂科】计算机网络期末考试(综合)——【选择题&填空题&判断题&简述题】完整题库

前言 大家好吖&#xff0c;欢迎来到 YY 滴计算机网络 系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 本博客主要内容&#xff0c;收纳了一部门基本的计算机网络题目&#xff0c;供yy应对期中考试复习。大家可以参考 欢迎订阅 YY滴其他专栏&#xff01;…...

Linux(CentOS)设置防火墙开放8080端口,运行jar包,接收请求

1、查看防火墙状态 systemctl status firewalld 防火墙开启状态 2、运行 jar 包&#xff0c;使用8080端口 程序正常启动 3、使用 postman 发送请求&#xff0c;失败 4、检查端口是否开放&#xff08;需更换到 root 用户&#xff09; firewall-cmd --zonepublic --query-por…...