網(wǎng)站建設方案選公司十大電商代運營公司
文章本天成,妙手偶得之。粹然無疵瑕,豈復須人為?君看古彝器,巧拙兩無施。漢最近先秦,固已殊淳漓。胡部何為者,豪竹雜哀絲。后夔不復作,千載誰與期?
——《文章》宋·陸游
【哲理】文章本是不加人工,天然而成的,是技藝高超的人在偶然間所得到的。其實作者所說的“天成”,并不就是大自然的恩賜,而是基于長期積累起來的感性印象和深入的思考,由于偶然出發(fā)而捕捉到靈感。
靈感就是長時間的積累和瞬間的爆發(fā),人品也是,成就亦如是。
一、AOP 的概念
面向方面編程(Aspect-Oriented Programming,AOP)是一種編程范式,旨在通過將橫切關注點(cross-cutting concerns)分離出來,從而提高代碼的模塊化和可維護性。橫切關注點是指那些影響多個模塊的功能,比如日志記錄、安全檢查、事務管理等。在傳統(tǒng)的面向對象編程(OOP)中,這些關注點往往會散布在各個類中,導致代碼重復和難以維護。
AOP 通過引入“方面”(aspect)的概念,將這些橫切關注點集中到一個地方進行管理。主要的 AOP 概念包括:
- Aspect(方面):封裝橫切關注點的模塊。
- Join Point(連接點):程序執(zhí)行過程中可以插入方面的具體點,比如方法調用或異常拋出。
- Advice(通知):在特定的連接點上執(zhí)行的代碼,可以分為前置通知(Before)、后置通知(After)和環(huán)繞通知(Around)。
- Pointcut(切入點):定義在哪些連接點上應用通知的表達式。
- Weaving(織入):將方面應用到目標對象的過程,可以在編譯時、加載時或運行時進行。
二、使用 Rust 實現(xiàn) AOP
雖然 Rust 沒有直接支持 AOP,但我們可以通過宏和閉包來實現(xiàn)類似的效果。
1、聲明宏實現(xiàn) AOP
1.1、定義宏和函數(shù)
首先,我們定義一個宏,用于在函數(shù)調用前后執(zhí)行一些額外的邏輯:
macro_rules! aop {($func:expr, $before:expr, $after:expr) => {{$before();let result = $func();$after();result}};
}
這個宏接受三個參數(shù):
$func
:要調用的函數(shù)。$before
:在函數(shù)調用前執(zhí)行的閉包。$after
:在函數(shù)調用后執(zhí)行的閉包。
1.2、使用宏實現(xiàn) AOP
接下來,我們定義一些示例函數(shù)和通知,并使用?aop!
?宏來包裝函數(shù)調用:
fn main() {// 定義前置通知let before = || println!("Before function call");// 定義后置通知let after = || println!("After function call");// 定義一個示例函數(shù)let my_function = || {println!("Inside the function");42 // 返回一些值};// 使用 aop! 宏包裝函數(shù)調用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、環(huán)繞通知
為了更好地展示 AOP 的靈活性,我們可以擴展示例,添加更多的通知類型,比如環(huán)繞通知:
macro_rules! aop_around {($func:expr, $around:expr) => {{$around($func)}};
}fn main() {// 定義環(huán)繞通知let around = |func: fn() -> i32| {println!("Before function call (around)");let result = func();println!("After function call (around)");result};// 定義一個示例函數(shù)let my_function = || {println!("Inside the function");42 // 返回一些值};// 使用 aop_around! 宏包裝函數(shù)調用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、更精確的切入點定義
定義宏和函數(shù)
首先,我們定義一個宏,用于在函數(shù)調用前后執(zhí)行一些額外的邏輯,并允許通過條件判斷來決定是否應用通知:
macro_rules! aop {($func:expr, $before:expr, $after:expr, $pointcut:expr) => {{if $pointcut() {$before();}let result = $func();if $pointcut() {$after();}result}};
}
這個宏接受四個參數(shù):
$func
:要調用的函數(shù)。$before
:在函數(shù)調用前執(zhí)行的閉包。$after
:在函數(shù)調用后執(zhí)行的閉包。$pointcut
:一個返回布爾值的閉包,用于決定是否應用通知。
使用宏實現(xiàn)更精確的切入點
接下來,我們定義一些示例函數(shù)和通知,并使用?aop!
?宏來包裝函數(shù)調用,同時定義切入點條件:
fn main() {// 定義前置通知let before = || println!("Before function call");// 定義后置通知let after = || println!("After function call");// 定義一個示例函數(shù)let my_function = || {println!("Inside the function");42 // 返回一些值};// 定義切入點條件let pointcut = || true; // 可以根據(jù)需要修改條件// 使用 aop! 宏包裝函數(shù)調用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");// 定義一個示例函數(shù)let my_function = || {println!("Inside the function");42 // 返回一些值};// 定義切入點條件let pointcut = || false; // 修改條件// 使用 aop! 宏包裝函數(shù)調用let result = aop!(my_function, before, after, pointcut);println!("Function returned: {}", result);
}
運行這個程序,你會看到以下輸出:
Inside the function
Function returned: 42
擴展切入點條件
為了更靈活地定義切入點條件,我們可以將條件邏輯擴展為更加復雜的表達式。例如,我們可以根據(jù)函數(shù)名稱、參數(shù)類型或其他上下文信息來決定是否應用通知。
fn main() {// 定義前置通知let before = || println!("Before function call");// 定義后置通知let after = || println!("After function call");// 定義多個示例函數(shù)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! 宏包裝函數(shù)調用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
?滿足切入點條件,因此只有它的調用前后會執(zhí)行通知,而?my_function2
?則不會。
1.5、簡化切入點的定義過程
為了簡化切入點的定義過程,我們可以通過封裝和抽象來減少重復代碼,并使切入點的定義更加直觀和易于管理。以下是一些方法,可以幫助我們簡化切入點的定義過程:
- 使用宏進行封裝:將切入點邏輯封裝在宏中,使其更易于復用。
- 使用函數(shù)指針或閉包:將切入點條件作為參數(shù)傳遞給宏,以便靈活地定義不同的切入點。
- 定義通用的切入點條件:創(chuàng)建一些常見的切入點條件函數(shù),以便在不同場景中復用。
下面是一個示例,展示如何通過這些方法簡化切入點的定義過程:
定義通用的切入點條件
首先,我們定義一些通用的切入點條件函數(shù),這些函數(shù)可以根據(jù)需要進行擴展:
fn always_true() -> bool {true
}fn always_false() -> bool {false
}fn function_name_is(target: &str, func_name: &str) -> bool {target == func_name
}
封裝宏
接下來,我們定義一個宏,用于在函數(shù)調用前后執(zhí)行通知,并接受切入點條件作為參數(shù):
macro_rules! aop {($func:expr, $before:expr, $after:expr, $pointcut:expr) => {{if $pointcut() {$before();}let result = $func();if $pointcut() {$after();}result}};
}
使用宏和通用切入點條件
最后,我們使用這些通用的切入點條件和宏來包裝函數(shù)調用:
fn main() {// 定義前置通知let before = || println!("Before function call");// 定義后置通知let after = || println!("After function call");// 定義多個示例函數(shù)let my_function1 = || {println!("Inside function 1");42 // 返回一些值};let my_function2 = || {println!("Inside function 2");24 // 返回一些值};// 使用 aop! 宏包裝函數(shù)調用,并使用通用切入點條件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
?函數(shù)來定義切入點條件,從而簡化了切入點的定義過程。通過這種方式,我們可以輕松地復用通用的切入點條件,并且使代碼更加簡潔和易于維護。
2、過程宏實現(xiàn) AOP
在 Rust 中,過程宏(procedural macros)是一種強大的工具,可以用來生成代碼、修改代碼結構以及實現(xiàn)復雜的編譯時邏輯。通過使用過程宏屬性,我們可以實現(xiàn)類似 AOP 的功能,并且使切入點的定義更加簡潔和直觀。
下面是一個示例,展示如何使用過程宏屬性來實現(xiàn) AOP 編程范例。
2.1、創(chuàng)建過程宏
首先,我們需要創(chuàng)建一個新的 Rust 庫項目,用于定義我們的過程宏。你可以使用以下命令創(chuàng)建一個新的庫項目:
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)
}
這個過程宏接受兩個參數(shù):前置通知和后置通知的函數(shù)名。它會在目標函數(shù)調用前后插入這些通知。
2.2、使用過程宏
接下來,我們在一個新的二進制項目中使用這個過程宏。你可以使用以下命令創(chuàng)建一個新的二進制項目:
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 中實現(xiàn)類似 AOP 的功能,并且使切入點的定義更加簡潔和直觀。過程宏允許我們在編譯時生成和修改代碼,從而實現(xiàn)復雜的編譯時邏輯。
2.3、優(yōu)化過程宏的性能
優(yōu)化過程宏的性能主要涉及減少編譯時間和生成高效的代碼。以下是一些優(yōu)化過程宏性能的方法:
- 避免不必要的解析和轉換。在編寫過程宏時,盡量避免不必要的解析和轉換操作。只解析和處理你需要的部分,以減少開銷。
- 使用更高效的數(shù)據(jù)結構。在處理過程中,選擇合適的數(shù)據(jù)結構可以提高效率。例如,使用?
Vec
?而不是?HashMap
,如果你只需要順序訪問元素。 - 緩存結果。如果你的過程宏需要進行重復計算,可以考慮緩存中間結果以減少重復計算的開銷。
- 減少依賴。盡量減少對外部 crate 的依賴,特別是那些會增加編譯時間的依賴。對于必須使用的依賴,確保它們是最新版本,因為新版本通常包含性能改進。
- 優(yōu)化生成的代碼。確保生成的代碼是高效的,不引入不必要的開銷。例如,避免生成多余的閉包或函數(shù)調用。
- 使用?
syn
?和?quote
?的高級特性。syn
?和?quote
?提供了許多高級特性,可以幫助你更高效地解析和生成代碼。熟悉這些特性并加以利用,可以顯著提高過程宏的性能。
三、與Spring Boot的AOP機制對比
Spring Boot 的 AOP(面向切面編程)機制和 Rust 中使用過程宏實現(xiàn)的 AOP 機制在概念上有相似之處,但在實現(xiàn)方式和應用場景上有顯著的不同。以下是對這兩種機制的詳細對比:
1、實現(xiàn)方式
Spring Boot AOP:
- 基于代理:Spring AOP 主要通過動態(tài)代理(JDK 動態(tài)代理或 CGLIB 代理)來實現(xiàn)。這意味著 Spring AOP 在運行時生成代理對象,并在調用目標方法之前和之后插入通知邏輯。
- 注解驅動:Spring AOP 使用注解(如?
@Aspect
、@Before
、@After
?等)來定義切面和通知。開發(fā)者可以通過這些注解輕松地將橫切關注點(如日志記錄、事務管理等)應用到目標方法上。
@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 的過程宏在編譯時生成代碼。這意味著所有的切面邏輯在編譯時就已經(jīng)確定,不會在運行時引入額外的開銷。
- 宏屬性:通過自定義的宏屬性,開發(fā)者可以在編譯時插入通知邏輯。這個過程需要解析和修改抽象語法樹(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 基于動態(tài)代理,在運行時可能會引入一些性能開銷,特別是在創(chuàng)建代理對象和方法調用時。
- 靈活性:盡管有一定的運行時開銷,Spring AOP 提供了極大的靈活性,可以在運行時動態(tài)地應用和移除切面。
Rust 過程宏 AOP:
- 編譯時開銷:Rust 的過程宏在編譯時進行代碼生成,因此不會在運行時引入額外的開銷。編譯時的處理可能會增加編譯時間,但生成的代碼在運行時非常高效。
- 靜態(tài)性:由于所有的切面邏輯在編譯時就已經(jīng)確定,缺乏運行時的靈活性。這意味著無法在運行時動態(tài)地改變切面邏輯。
3、應用場景
Spring Boot AOP:
- 企業(yè)級應用:Spring AOP 廣泛應用于企業(yè)級 Java 應用中,用于處理橫切關注點,如事務管理、日志記錄、安全性檢查等。
- 動態(tài)配置:適用于需要在運行時動態(tài)配置和調整切面的場景。
Rust 過程宏 AOP:
- 系統(tǒng)編程:Rust 更適合系統(tǒng)編程和性能關鍵的應用場景。通過過程宏實現(xiàn)的 AOP 可以在不引入運行時開銷的情況下實現(xiàn)類似的功能。
- 編譯時保證:適用于需要在編譯時確定所有邏輯的場景,提供更高的性能和安全性保證。
4、易用性
Spring Boot AOP:
- 易于使用:Spring AOP 提供了豐富的注解和配置選項,使得開發(fā)者可以輕松地定義和應用切面。
- 強大的生態(tài)系統(tǒng):Spring 框架本身提供了大量的工具和庫,與 AOP 緊密集成,進一步簡化了開發(fā)過程。
Rust 過程宏 AOP:
- 學習曲線:編寫過程宏需要深入了解 Rust 的宏系統(tǒng)和編譯器插件,具有一定的學習曲線。
- 定制化:雖然過程宏提供了強大的功能,但需要開發(fā)者手動編寫和維護宏代碼,增加了復雜性。
5、總結
Spring Boot 的 AOP 機制和 Rust 中使用過程宏實現(xiàn)的 AOP 機制各有優(yōu)劣。Spring AOP 提供了極大的靈活性和易用性,適用于企業(yè)級應用和動態(tài)配置的場景。而 Rust 的過程宏 AOP 則在性能和編譯時保證方面具有優(yōu)勢,更適合系統(tǒng)編程和性能關鍵的應用。
四、AOP 實現(xiàn)記錄日志(線程安全)
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、實現(xiàn) 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
?環(huán)境變量,你可以控制顯示哪些日志信息。例如:?
# RUST_LOG=info ./your_executable
RUST_LOG=info ./target/debug/aop_example
這將展示所有級別為?info
?及其以上的日志條目。日志級別包括?error
、warn
、info
、debug
?和?trace
,不區(qū)分大小寫。
[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: ()
五、根據(jù)配置生成代碼
我們可以編寫一個過程宏來解析配置文件,并為指定的函數(shù)添加前置和后置操作。我們將通過以下步驟來實現(xiàn)這個功能:
- 定義一個配置文件,包含函數(shù)名、前置操作函數(shù)和后置操作函數(shù)。
- 編寫一個過程宏,讀取配置文件并為指定的函數(shù)添加前置和后置操作。
- 使用過程宏修飾目標函數(shù)。
Step1、創(chuàng)建宏
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
?中使用這個過程宏,并實現(xiàn)?fun_rule
?和?fun_log
?函數(shù):
[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、配置文件
在主項目根目錄下創(chuàng)建一個配置文件?config.json
,內(nèi)容如下:
[{"func_name": "fun1","before": "fun_rule","after": "fun_log"},{"func_name": "fun2","before": "fun_rule","after": "fun_log"}
]
在這個示例中,我們定義了三個函數(shù)?fun1
,?fun2
?和?fun3
,并使用 AOP 宏來記錄它們的參數(shù)值、返回值和耗時時間。我們還實現(xiàn)了一個簡單的調度算法,根據(jù)預定義的執(zhí)行順序依次調用這些函數(shù),并將每個函數(shù)的返回值作為下一個函數(shù)的參數(shù)。
此外,我們引入了?fun_rule
?和?fun_log
?函數(shù)。fun_rule
?用于判斷是否執(zhí)行函數(shù)主體,如果不滿足條件,則返回一個默認值。fun_log
?用于在函數(shù)執(zhí)行后進行額外的日志操作。
通過這種方式,我們實現(xiàn)了一個通用的函數(shù)調度算法,同時使用 AOP 記錄每個函數(shù)的參數(shù)值、返回值和耗時時間,并根據(jù)配置條件決定是否執(zhí)行某些函數(shù)。在多線程環(huán)境中,這種方法也是適用的,因為我們使用了線程安全的日志庫?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 過程宏作用于函數(shù),而該過程宏的邏輯取決于?JSON 配置文件的定義,從而影響編譯源碼。過程宏是在編譯時執(zhí)行的,它們可以根據(jù)輸入生成新的代碼。以此類推,如果過程宏的行為依賴于系統(tǒng)變量,那么這些變量的值會直接影響生成的代碼。