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