包头网站建设平台广和,wordpress集成dz,网站游戏入口,设计师培训计划方案文章本天成#xff0c;妙手偶得之。粹然无疵瑕#xff0c;岂复须人为#xff1f;君看古彝器#xff0c;巧拙两无施。汉最近先秦#xff0c;固已殊淳漓。胡部何为者#xff0c;豪竹杂哀丝。后夔不复作#xff0c;千载谁与期#xff1f; ——《文章》宋陆游 【哲理】文章… 文章本天成妙手偶得之。粹然无疵瑕岂复须人为君看古彝器巧拙两无施。汉最近先秦固已殊淳漓。胡部何为者豪竹杂哀丝。后夔不复作千载谁与期 ——《文章》宋·陆游 【哲理】文章本是不加人工天然而成的是技艺高超的人在偶然间所得到的。其实作者所说的“天成”并不就是大自然的恩赐而是基于长期积累起来的感性印象和深入的思考由于偶然出发而捕捉到灵感。 灵感就是长时间的积累和瞬间的爆发人品也是成就亦如是。 一、AOP 的概念
面向方面编程Aspect-Oriented ProgrammingAOP是一种编程范式旨在通过将横切关注点cross-cutting concerns分离出来从而提高代码的模块化和可维护性。横切关注点是指那些影响多个模块的功能比如日志记录、安全检查、事务管理等。在传统的面向对象编程OOP中这些关注点往往会散布在各个类中导致代码重复和难以维护。
AOP 通过引入“方面”aspect的概念将这些横切关注点集中到一个地方进行管理。主要的 AOP 概念包括
Aspect方面封装横切关注点的模块。Join Point连接点程序执行过程中可以插入方面的具体点比如方法调用或异常抛出。Advice通知在特定的连接点上执行的代码可以分为前置通知Before、后置通知After和环绕通知Around。Pointcut切入点定义在哪些连接点上应用通知的表达式。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、简化切入点的定义过程
为了简化切入点的定义过程我们可以通过封装和抽象来减少重复代码并使切入点的定义更加直观和易于管理。以下是一些方法可以帮助我们简化切入点的定义过程
使用宏进行封装将切入点逻辑封装在宏中使其更易于复用。使用函数指针或闭包将切入点条件作为参数传递给宏以便灵活地定义不同的切入点。定义通用的切入点条件创建一些常见的切入点条件函数以便在不同场景中复用。
下面是一个示例展示如何通过这些方法简化切入点的定义过程
定义通用的切入点条件
首先我们定义一些通用的切入点条件函数这些函数可以根据需要进行扩展
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: Vecstr 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、优化过程宏的性能
优化过程宏的性能主要涉及减少编译时间和生成高效的代码。以下是一些优化过程宏性能的方法
避免不必要的解析和转换。在编写过程宏时尽量避免不必要的解析和转换操作。只解析和处理你需要的部分以减少开销。使用更高效的数据结构。在处理过程中选择合适的数据结构可以提高效率。例如使用 Vec 而不是 HashMap如果你只需要顺序访问元素。缓存结果。如果你的过程宏需要进行重复计算可以考虑缓存中间结果以减少重复计算的开销。减少依赖。尽量减少对外部 crate 的依赖特别是那些会增加编译时间的依赖。对于必须使用的依赖确保它们是最新版本因为新版本通常包含性能改进。优化生成的代码。确保生成的代码是高效的不引入不必要的开销。例如避免生成多余的闭包或函数调用。使用 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: Vecstr 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: Vecstr 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.22use 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_LOGinfo ./your_executable
RUST_LOGinfo ./target/debug/aop_example
这将展示所有级别为 info 及其以上的日志条目。日志级别包括 error、warn、info、debug 和 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: ()
五、根据配置生成代码
我们可以编写一个过程宏来解析配置文件并为指定的函数添加前置和后置操作。我们将通过以下步骤来实现这个功能
定义一个配置文件包含函数名、前置操作函数和后置操作函数。编写一个过程宏读取配置文件并为指定的函数添加前置和后置操作。使用过程宏修饰目标函数。
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: VecConfig 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: HashMapstr, Boxdyn Fn(Veci32) - Veci32 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}
]
在这个示例中我们定义了三个函数 fun1, fun2 和 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 配置文件的定义从而影响编译源码。过程宏是在编译时执行的它们可以根据输入生成新的代码。以此类推如果过程宏的行为依赖于系统变量那么这些变量的值会直接影响生成的代码。