From f3712890b93d667a9738558f6a353463507bedca Mon Sep 17 00:00:00 2001 From: ViewWay <834740219@qq.com> Date: Sun, 28 Jun 2026 21:32:57 +0800 Subject: [PATCH] fix(aop): advice/pointcut macros generate valid Rust + uppercase aliases The published hiver-aop 0.1.0-alpha.6 was unusable from downstream crates (#60, #61): every advice/pointcut macro emitted `impl #func_name { ... }` where `#func_name` is a *function*, not a type. This produced: - inside an `impl` block: error: implementation is not supported in traits or impls - on a free function: error[E0573]: expected type, found function Fix: the macros now emit the annotated `fn` unchanged plus a uniquely-named companion `const _HIVER___META: (&str, &str) = (pointcut, kind)`. A bare `const` (not a nested `impl`) is valid Rust both at module scope and inside an `impl` block, so advice now works in both contexts. Also: - Re-export Spring-familiar uppercase aliases (Aspect/Before/After/Around/ AfterReturning/AfterThrowing/Pointcut) so the README import shape compiles. - Rewrite README + doc examples to the real, compiling API (lowercase macros, JoinPoint/ProceedingJoinPoint in scope, Rust pointcut semantics instead of JVM package patterns). - Add trybuild regression tests covering free-fn advice, impl-block advice (the original failing case), uppercase aliases, and a compile_fail fixture. - Re-enable examples/logging_aspect.rs as a buildable, runnable example. - Add hiver-aop-macros/README.md (Cargo.toml referenced it but it was missing, which blocked `cargo package`/publish). Verified: both #60 reproductions now `cargo check` from a fresh downstream crate; all 56 unit tests, 4 trybuild cases, and 2 compiled doc-tests pass; nightly `cargo fmt --check` and clippy clean for the touched crates. Fixes #60 Fixes #61 --- crates/hiver-aop-macros/README.md | 76 ++ crates/hiver-aop-macros/src/advice.rs | 357 +++++----- crates/hiver-aop-macros/src/aspect.rs | 6 +- crates/hiver-aop-macros/src/lib.rs | 12 +- crates/hiver-aop-macros/src/pointcut.rs | 108 ++- crates/hiver-aop/README.md | 647 +++++------------- crates/hiver-aop/examples/logging_aspect.rs | 143 ++++ .../examples/logging_aspect.rs.disabled | 89 --- crates/hiver-aop/src/lib.rs | 57 +- crates/hiver-aop/src/runtime.rs | 23 +- crates/hiver-aop/tests/downstream_compile.rs | 42 ++ crates/hiver-aop/tests/ui/free_fn_advice.rs | 67 ++ .../hiver-aop/tests/ui/impl_block_advice.rs | 58 ++ .../hiver-aop/tests/ui/uppercase_aliases.rs | 53 ++ .../tests/ui/wrong_macro_name_fail.rs | 17 + .../tests/ui/wrong_macro_name_fail.stderr | 5 + 16 files changed, 899 insertions(+), 861 deletions(-) create mode 100644 crates/hiver-aop-macros/README.md create mode 100644 crates/hiver-aop/examples/logging_aspect.rs delete mode 100644 crates/hiver-aop/examples/logging_aspect.rs.disabled create mode 100644 crates/hiver-aop/tests/downstream_compile.rs create mode 100644 crates/hiver-aop/tests/ui/free_fn_advice.rs create mode 100644 crates/hiver-aop/tests/ui/impl_block_advice.rs create mode 100644 crates/hiver-aop/tests/ui/uppercase_aliases.rs create mode 100644 crates/hiver-aop/tests/ui/wrong_macro_name_fail.rs create mode 100644 crates/hiver-aop/tests/ui/wrong_macro_name_fail.stderr diff --git a/crates/hiver-aop-macros/README.md b/crates/hiver-aop-macros/README.md new file mode 100644 index 00000000..3b3c2c41 --- /dev/null +++ b/crates/hiver-aop-macros/README.md @@ -0,0 +1,76 @@ +# hiver-aop-macros + +[![Crates.io](https://img.shields.io/crates/v/hiver-aop-macros)](https://crates.io/hiver-aop-macros) +[![Documentation](https://docs.rs/hiver-aop-macros/badge.svg)](https://docs.rs/hiver-aop-macros) +[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](../../LICENSE) + +> Procedural macros for `hiver-aop` — Spring AOP style aspects for the Hiver Framework. +> +> `hiver-aop` 的过程宏 — Hiver 框架的 Spring AOP 风格切面支持。 + +--- + +## Overview / 概述 + +This is the **proc-macro** half of [`hiver-aop`](https://crates.io/hiver-aop). You +normally do **not** depend on it directly — depend on `hiver-aop`, which +re-exports everything. + +这是 [`hiver-aop`](https://crates.io/hiver-aop) 的**过程宏**部分。 +通常**不需要**直接依赖本 crate — 请依赖 `hiver-aop`,它会重新导出全部内容。 + +It provides these attribute macros / 它提供以下属性宏: + +| Lowercase / 小写 | Uppercase alias / 大写别名 | Purpose / 用途 | +|------------------|----------------------------|----------------| +| `aspect` | `Aspect` | Mark a struct as an aspect / 标记切面 | +| `before` | `Before` | Before advice / 前置通知 | +| `after` | `After` | After advice / 后置通知 | +| `around` | `Around` | Around advice / 环绕通知 | +| `after_returning`| `AfterReturning` | After-returning advice / 返回后通知 | +| `after_throwing` | `AfterThrowing` | After-throwing advice / 异常后通知 | +| `pointcut` | `Pointcut` | Reusable pointcut definition / 可重用切点 | + +## How it works / 工作原理 + +Each advice/pointcut macro emits the annotated function unchanged plus a +uniquely-named companion `const` that records the pointcut expression and advice +kind. A companion `const` (not a nested `impl`) is used so the macros are valid +both at module scope **and** inside an `impl` block. + +每个通知/切点宏会原样输出被注解的函数,并附加一个唯一命名的伴生 `const`, +记录切点表达式与通知类型。使用伴生 `const`(而非嵌套 `impl`)是为了让宏在 +模块作用域**以及** `impl` 块内部都合法。 + +## Usage / 用法 + +```toml +[dependencies] +hiver-aop = "0.1" +``` + +```rust +use hiver_aop::{aspect, before, JoinPoint}; + +#[aspect] +struct LoggingAspect; + +impl LoggingAspect { + #[before("execution(* *..*.*(..))")] + fn log_before(&self, join_point: &JoinPoint) { + println!("Entering: {}", join_point.method_name()); + } +} +``` + +See the [`hiver-aop` documentation](https://docs.rs/hiver-aop) for the full API, +runtime types, and pointcut semantics. + +## License / 许可证 + +Licensed under either of: + +- Apache License, Version 2.0 ([LICENSE-APACHE](../../LICENSE-APACHE) or https://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](../../LICENSE-MIT) or https://opensource.org/licenses/MIT) + +at your option. / 由您选择。 diff --git a/crates/hiver-aop-macros/src/advice.rs b/crates/hiver-aop-macros/src/advice.rs index 2c6b2d82..f3fcd0d9 100644 --- a/crates/hiver-aop-macros/src/advice.rs +++ b/crates/hiver-aop-macros/src/advice.rs @@ -1,16 +1,46 @@ -//! Advice attribute macros (@Before, @After, @Around, @AfterReturning, @AfterThrowing) +//! Advice attribute macros (`before`, `after`, `around`, `after_returning`, `after_throwing`) //! 通知属性宏 +//! +//! # How advice metadata is attached / 通知元数据如何附加 +//! +//! Each advice macro is an attribute placed on a function, for example: +//! 每个通知宏都是放置在函数上的属性,例如: +//! +//! ```rust,ignore +//! use hiver_aop::before; +//! +//! #[before("execution(* *..*.*(..))")] +//! fn log_before() {} +//! ``` +//! +//! The macro emits the annotated function unchanged plus a **companion `const`** +//! that records the pointcut expression and advice kind: +//! 宏会原样输出被注解的函数,并附加一个**伴生 `const`**, +//! 记录切点表达式和通知类型: +//! +//! ```rust,ignore +//! fn log_before() {} +//! const _HIVER_BEFORE_LOG_BEFORE_META: (&'static str, &'static str) = +//! ("execution(* *..*.*(..))", "before"); +//! ``` +//! +//! A companion `const` (rather than an `impl` block) is used on purpose: it is +//! valid Rust both at module scope **and** when the advice is a method inside an +//! `impl Aspect { ... }` block (where a nested `impl` would be illegal). +//! 使用伴生 `const`(而非 `impl` 块)是刻意为之: +//! 它在模块作用域**以及** `impl Aspect { ... }` 内部作为方法时都是合法的 Rust 代码 +//! (在后者中嵌套 `impl` 是非法的)。 use proc_macro::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use syn::{ ItemFn, LitStr, Result as SynResult, parse::{Parse, ParseStream}, parse_macro_input, }; -/// Parses pointcut expression from advice annotation -/// 解析通知注解中的切点表达式 +/// Parses a pointcut expression from an advice annotation. +/// 从通知注解中解析切点表达式。 struct PointcutExpr { expression: String, @@ -20,8 +50,8 @@ impl Parse for PointcutExpr { fn parse(input: ParseStream) -> SynResult { - // Try to parse a string literal - // 尝试解析字符串字面量 + // Parse a string literal: `#[before("execution(...")]` + // 解析字符串字面量:`#[before("execution(...")]` let expr_lit: LitStr = input.parse()?; Ok(PointcutExpr { @@ -30,249 +60,192 @@ impl Parse for PointcutExpr } } -/// Implements #[Before] attribute macro -/// 实现 #[Before] 属性宏 +/// Shared expansion for all advice macros. +/// 所有通知宏共享的展开逻辑。 /// -/// Marks a method as before advice (executed before the join point) -/// 将方法标记为前置通知(在连接点之前执行) +/// Emits the original `fn` item followed by a uniquely-named companion `const` +/// holding `(pointcut_expression, advice_kind)`. The `advice_kind` string is +/// passed in by the caller and is one of `"before"`, `"after"`, `"around"`, +/// `"after_returning"`, `"after_throwing"`. /// -/// # Pointcut Expressions / 切点表达式 -/// -/// Common patterns / 常用模式: -/// - `execution(* com.example..*.*(..))` - All methods in com.example package -/// - `execution(* com.example.Service.*(..))` - All methods in Service class -/// - `execution(public * *(..))` - All public methods -/// - `execution(@org.annotation.Transactional * *(..))` - Methods with @Transactional -/// -/// # Example / 示例 -/// -/// ```rust,no_run,ignore -/// use hiver_aop::Before; -/// -/// #[Before("execution(* com.example..*.*(..))")] -/// fn log_before(&self, join_point: &JoinPoint) { -/// println!("Entering: {}", join_point.method_name()); -/// } -/// ``` -pub(crate) fn impl_before(attr: TokenStream, item: TokenStream) -> TokenStream +/// 输出原始 `fn` 项,并紧跟一个唯一命名的伴生 `const`, +/// 其值为 `(切点表达式, 通知类型)`。`advice_kind` 字符串由调用方传入, +/// 取值为 `"before"`、`"after"`、`"around"`、`"after_returning"`、`"after_throwing"` 之一。 +fn expand_advice(attr: TokenStream, item: TokenStream, advice_kind: &str) -> TokenStream { let input = parse_macro_input!(item as ItemFn); let func_name = &input.sig.ident; - // Parse pointcut expression from attribute - // 从属性中解析切点表达式 let args = parse_macro_input!(attr as PointcutExpr); let pointcut = args.expression; + // Build a unique, mangled name so multiple advice methods can coexist inside + // the same `impl` block (or module) without colliding. + // 构造唯一的混淆名称,使同一 `impl` 块(或模块)内的多个通知方法不会冲突。 + let meta_ident = format_ident!( + "_HIVER_{}_{}_META", + advice_kind.to_uppercase(), + func_name.to_string().to_uppercase() + ); + let expanded = quote! { #input - impl #func_name { - /// Returns the pointcut expression for this advice - /// 返回此通知的切点表达式 - const POINTCUT: &str = #pointcut; - - /// Returns the advice type - /// 返回通知类型 - const ADVICE_TYPE: &str = "before"; - - #[doc(hidden)] - fn _hiver_get_pointcut() -> &'static str { - POINTCUT - } - } + /// Hiver AOP metadata: `(pointcut_expression, advice_kind)`. + /// Hiver AOP 元数据:`(切点表达式, 通知类型)`。 + #[doc(hidden)] + const #meta_ident: (&'static str, &'static str) = (#pointcut, #advice_kind); }; TokenStream::from(expanded) } -/// Implements #[After] attribute macro -/// 实现 #[After] 属性宏 +/// Implements the `#[before]` attribute macro. +/// 实现 `#[before]` 属性宏。 +/// +/// Marks a method as before advice (executed before the join point). +/// 将方法标记为前置通知(在连接点之前执行)。 +/// +/// # Pointcut Expressions / 切点表达式 /// -/// Marks a method as after advice (executed after the join point) -/// 将方法标记为后置通知(在连接点之后执行) +/// Common patterns / 常用模式: +/// - `execution(* *..*.*(..))` — All methods / 所有方法 +/// - `execution(* *.Service.*(..))` — All methods in a class ending with `Service` / 类名以 +/// `Service` 结尾的所有方法 +/// - `within(*)` — Within any type / 任意类型内 +/// - `execution(* get*(..))` — Methods whose name matches `get*` / 方法名匹配 `get*` 的方法 /// /// # Example / 示例 /// -/// ```rust,no_run,ignore -/// use hiver_aop::After; +/// ```rust,ignore +/// use hiver_aop::{aspect, before, JoinPoint}; +/// +/// #[aspect] +/// struct LoggingAspect; /// -/// #[After("execution(* com.example..*.*(..))")] -/// fn log_after(&self, join_point: &JoinPoint) { -/// println!("Exiting: {}", join_point.method_name()); +/// impl LoggingAspect { +/// #[before("execution(* *..*.*(..))")] +/// fn log_before(&self, join_point: &JoinPoint) { +/// println!("Entering: {}", join_point.method_name()); +/// } /// } /// ``` -pub(crate) fn impl_after(attr: TokenStream, item: TokenStream) -> TokenStream +pub(crate) fn impl_before(attr: TokenStream, item: TokenStream) -> TokenStream { - let input = parse_macro_input!(item as ItemFn); - let func_name = &input.sig.ident; - - // Parse pointcut expression from attribute - // 从属性中解析切点表达式 - let args = parse_macro_input!(attr as PointcutExpr); - let pointcut = args.expression; - - let expanded = quote! { - #input - - impl #func_name { - /// Returns the pointcut expression for this advice - /// 返回此通知的切点表达式 - const POINTCUT: &str = #pointcut; - - /// Returns the advice type - /// 返回通知类型 - const ADVICE_TYPE: &str = "after"; - - #[doc(hidden)] - fn _hiver_get_pointcut() -> &'static str { - POINTCUT - } - } - }; + expand_advice(attr, item, "before") +} - TokenStream::from(expanded) +/// Implements the `#[after]` attribute macro. +/// 实现 `#[after]` 属性宏。 +/// +/// Marks a method as after advice (executed after the join point, like `finally`). +/// 将方法标记为后置通知(在连接点之后执行,类似 `finally`)。 +/// +/// # Example / 示例 +/// +/// ```rust,ignore +/// use hiver_aop::{aspect, after, JoinPoint}; +/// +/// #[aspect] +/// struct LoggingAspect; +/// +/// impl LoggingAspect { +/// #[after("execution(* *..*.*(..))")] +/// fn log_after(&self, join_point: &JoinPoint) { +/// println!("Exiting: {}", join_point.method_name()); +/// } +/// } +/// ``` +pub(crate) fn impl_after(attr: TokenStream, item: TokenStream) -> TokenStream +{ + expand_advice(attr, item, "after") } -/// Implements #[Around] attribute macro -/// 实现 #[Around] 属性宏 +/// Implements the `#[around]` attribute macro. +/// 实现 `#[around]` 属性宏。 /// -/// Marks a method as around advice (wraps the join point execution) -/// 将方法标记为环绕通知(包装连接点的执行) +/// Marks a method as around advice (wraps the join point execution). Use a +/// [`hiver_aop::ProceedingJoinPoint`](https://docs.rs/hiver-aop) and call +/// `proceed()` to let the target run. +/// 将方法标记为环绕通知(包装连接点的执行)。使用 +/// [`hiver_aop::ProceedingJoinPoint`](https://docs.rs/hiver-aop), +/// 调用 `proceed()` 以放行目标方法。 /// /// # Example / 示例 /// -/// ```rust,no_run,ignore -/// use hiver_aop::Around; +/// ```rust,ignore +/// use hiver_aop::{aspect, around, ProceedingJoinPoint}; +/// +/// #[aspect] +/// struct LoggingAspect; /// -/// #[Around("execution(* com.example..*.*(..))")] -/// fn log_around(&self, join_point: JoinPoint) -> Result<(), Error> { -/// println!("Before: {}", join_point.method_name()); -/// let result = join_point.proceed()?; -/// println!("After: {}", join_point.method_name()); -/// Ok(result) +/// impl LoggingAspect { +/// #[around("execution(* *..*.*(..))")] +/// fn log_around(join_point: &mut ProceedingJoinPoint) { +/// println!("Before: {}", join_point.method_name()); +/// join_point.proceed(); // let the target method run / 放行目标方法 +/// println!("After: {}", join_point.method_name()); +/// } /// } /// ``` pub(crate) fn impl_around(attr: TokenStream, item: TokenStream) -> TokenStream { - let input = parse_macro_input!(item as ItemFn); - let func_name = &input.sig.ident; - - // Parse pointcut expression from attribute - // 从属性中解析切点表达式 - let args = parse_macro_input!(attr as PointcutExpr); - let pointcut = args.expression; - - let expanded = quote! { - #input - - impl #func_name { - /// Returns the pointcut expression for this advice - /// 返回此通知的切点表达式 - const POINTCUT: &str = #pointcut; - - /// Returns the advice type - /// 返回通知类型 - const ADVICE_TYPE: &str = "around"; - - #[doc(hidden)] - fn _hiver_get_pointcut() -> &'static str { - POINTCUT - } - } - }; - - TokenStream::from(expanded) + expand_advice(attr, item, "around") } -/// Implements #[AfterReturning] attribute macro -/// 实现 #[AfterReturning] 属性宏 +/// Implements the `#[after_returning]` attribute macro. +/// 实现 `#[after_returning]` 属性宏。 /// -/// Marks a method as after-returning advice (executed after the join point returns successfully) -/// 将方法标记为返回后通知(在连接点成功返回后执行) +/// Marks a method as after-returning advice (executed after the join point +/// returns successfully). Registered at runtime via +/// `AspectRegistry::register_after_returning`. +/// 将方法标记为返回后通知(在连接点成功返回后执行)。运行时通过 +/// `AspectRegistry::register_after_returning` 注册。 /// /// # Example / 示例 /// -/// ```rust,no_run,ignore -/// use hiver_aop::AfterReturning; +/// ```rust,ignore +/// use hiver_aop::{aspect, after_returning, JoinPoint}; +/// use std::sync::Arc; /// -/// #[AfterReturning("execution(* com.example..*.*(..))")] -/// fn log_success(&self, join_point: &JoinPoint, result: &ReturnValue) { -/// println!("Method returned successfully: {}", join_point.method_name()); +/// #[aspect] +/// struct LoggingAspect; +/// +/// impl LoggingAspect { +/// #[after_returning("execution(* *..*.*(..))")] +/// fn log_success(join_point: &JoinPoint, _result: &Arc) { +/// println!("Method returned successfully: {}", join_point.method_name()); +/// } /// } /// ``` pub(crate) fn impl_after_returning(attr: TokenStream, item: TokenStream) -> TokenStream { - let input = parse_macro_input!(item as ItemFn); - let func_name = &input.sig.ident; - - let args = parse_macro_input!(attr as PointcutExpr); - let pointcut = args.expression; - - let expanded = quote! { - #input - - impl #func_name { - /// Returns the pointcut expression for this advice - /// 返回此通知的切点表达式 - const POINTCUT: &str = #pointcut; - - /// Returns the advice type - /// 返回通知类型 - const ADVICE_TYPE: &str = "after_returning"; - - #[doc(hidden)] - fn _hiver_get_pointcut() -> &'static str { - POINTCUT - } - } - }; - - TokenStream::from(expanded) + expand_advice(attr, item, "after_returning") } -/// Implements #[AfterThrowing] attribute macro -/// 实现 #[AfterThrowing] 属性宏 +/// Implements the `#[after_throwing]` attribute macro. +/// 实现 `#[after_throwing]` 属性宏。 /// -/// Marks a method as after-throwing advice (executed when the join point throws an error) -/// 将方法标记为异常后通知(在连接点抛出异常时执行) +/// Marks a method as after-throwing advice (executed when the join point throws +/// an error). +/// 将方法标记为异常后通知(在连接点抛出异常时执行)。 /// /// # Example / 示例 /// -/// ```rust,no_run,ignore -/// use hiver_aop::AfterThrowing; +/// ```rust,ignore +/// use hiver_aop::{aspect, after_throwing, JoinPoint}; +/// +/// #[aspect] +/// struct ErrorAspect; /// -/// #[AfterThrowing("execution(* com.example..*.*(..))")] -/// fn log_error(&self, join_point: &JoinPoint, error: &Error) { -/// eprintln!("Method threw error: {} - {}", join_point.method_name(), error); +/// impl ErrorAspect { +/// #[after_throwing("execution(* *..*.*(..))")] +/// fn log_error(join_point: &JoinPoint) { +/// eprintln!("Method threw an error: {}", join_point.method_name()); +/// } /// } /// ``` pub(crate) fn impl_after_throwing(attr: TokenStream, item: TokenStream) -> TokenStream { - let input = parse_macro_input!(item as ItemFn); - let func_name = &input.sig.ident; - - let args = parse_macro_input!(attr as PointcutExpr); - let pointcut = args.expression; - - let expanded = quote! { - #input - - impl #func_name { - /// Returns the pointcut expression for this advice - /// 返回此通知的切点表达式 - const POINTCUT: &str = #pointcut; - - /// Returns the advice type - /// 返回通知类型 - const ADVICE_TYPE: &str = "after_throwing"; - - #[doc(hidden)] - fn _hiver_get_pointcut() -> &'static str { - POINTCUT - } - } - }; - - TokenStream::from(expanded) + expand_advice(attr, item, "after_throwing") } diff --git a/crates/hiver-aop-macros/src/aspect.rs b/crates/hiver-aop-macros/src/aspect.rs index 1880dab8..27ffc8d2 100644 --- a/crates/hiver-aop-macros/src/aspect.rs +++ b/crates/hiver-aop-macros/src/aspect.rs @@ -13,10 +13,10 @@ use syn::{DeriveInput, parse_macro_input}; /// /// # Example / 示例 /// -/// ```rust,no_run,ignore -/// use hiver_aop::Aspect; +/// ```rust,ignore +/// use hiver_aop::aspect; // or the uppercase alias: `use hiver_aop::Aspect;` /// -/// #[Aspect] +/// #[aspect] /// struct LoggingAspect; /// ``` pub(crate) fn impl_aspect(_attr: TokenStream, item: TokenStream) -> TokenStream diff --git a/crates/hiver-aop-macros/src/lib.rs b/crates/hiver-aop-macros/src/lib.rs index f29c8699..12439bcc 100644 --- a/crates/hiver-aop-macros/src/lib.rs +++ b/crates/hiver-aop-macros/src/lib.rs @@ -1,10 +1,16 @@ //! AOP procedural macros for Hiver Framework. //! Hiver框架的AOP过程宏。 //! -//! Provides `#[Aspect]`, `#[Before]`, `#[After]`, `#[Around]`, -//! `#[AfterReturning]`, `#[AfterThrowing]`, `#[Pointcut]`. +//! Provides `aspect`, `before`, `after`, `around`, `after_returning`, +//! `after_throwing`, and `pointcut` attribute macros. They are usually imported +//! via the `hiver_aop` facade crate, which additionally re-exports them under +//! Spring-familiar uppercase aliases (`Aspect`, `Before`, `After`, `Around`, +//! `AfterReturning`, `AfterThrowing`, `Pointcut`). //! -//! Usually imported via `hiver_aop` re-exports rather than directly. +//! 提供过程宏:`aspect`、`before`、`after`、`around`、`after_returning`、 +//! `after_throwing`、`pointcut`。通常通过 `hiver_aop` 门面 crate 导入, +//! 该 crate 还会以 Spring 风格的大写别名重新导出 +//! (`Aspect`、`Before`、`After`、`Around`、`AfterReturning`、`AfterThrowing`、`Pointcut`)。 #![warn(missing_docs)] #![warn(unreachable_pub)] diff --git a/crates/hiver-aop-macros/src/pointcut.rs b/crates/hiver-aop-macros/src/pointcut.rs index cbc6853a..c294a85e 100644 --- a/crates/hiver-aop-macros/src/pointcut.rs +++ b/crates/hiver-aop-macros/src/pointcut.rs @@ -1,16 +1,24 @@ -//! @Pointcut attribute macro -//! @Pointcut 属性宏 +//! `pointcut` attribute macro +//! `pointcut` 属性宏 +//! +//! Like the advice macros, the pointcut macro emits the annotated function +//! unchanged plus a uniquely-named companion `const` recording the expression +//! string. This is valid Rust both at module scope and inside an `impl` block +//! (where a nested `impl` would be illegal). +//! 与通知宏一样,切点宏会原样输出被注解的函数,并附加一个唯一命名的伴生 `const` +//! 记录表达式字符串。它在模块作用域和 `impl` 块内部都是合法的 Rust 代码 +//! (在后者中嵌套 `impl` 是非法的)。 use proc_macro::TokenStream; -use quote::quote; +use quote::{format_ident, quote}; use syn::{ ItemFn, LitStr, Result as SynResult, parse::{Parse, ParseStream}, parse_macro_input, }; -/// Parses pointcut expression from @Pointcut annotation -/// 解析 @Pointcut 注解中的切点表达式 +/// Parses a pointcut expression from the `pointcut` annotation. +/// 从 `pointcut` 注解中解析切点表达式。 struct PointcutExpr { expression: String, @@ -20,8 +28,8 @@ impl Parse for PointcutExpr { fn parse(input: ParseStream) -> SynResult { - // Try to parse a string literal - // 尝试解析字符串字面量 + // Parse a string literal: `#[pointcut("execution(...")]` + // 解析字符串字面量:`#[pointcut("execution(...")]` let expr_lit: LitStr = input.parse()?; Ok(PointcutExpr { @@ -30,88 +38,68 @@ impl Parse for PointcutExpr } } -/// Implements #[Pointcut] attribute macro -/// 实现 #[Pointcut] 属性宏 +/// Implements the `#[pointcut]` attribute macro. +/// 实现 `#[pointcut]` 属性宏。 /// -/// Defines a reusable pointcut expression that can be referenced by advice methods -/// 定义可重用的切点表达式,可以被通知方法引用 +/// Defines a reusable pointcut expression that can be referenced by advice +/// methods. The macro records the expression as a companion `const` next to the +/// annotated function. +/// 定义可重用的切点表达式,可被通知方法引用。宏将表达式记录为被注解函数旁的伴生 `const`。 /// /// # Pointcut Designators / 切点指示符 /// -/// - **execution** - Method execution join point 方法执行连接点 -/// - **call** - Method call join point 方法调用连接点 -/// - **within** - Limits to within certain types 限制在特定类型内 -/// - **this** - Limit to match bean reference 限制匹配 bean 引用 -/// - **target** - Limit to match target object 限制匹配目标对象 -/// - **args** - Limit to match arguments 限制匹配参数 -/// - **@annotation** - Limit to join points with subject annotation 限制带有特定注解的连接点 +/// - **execution** — Method execution join point / 方法执行连接点 +/// - **call** — Method call join point / 方法调用连接点 +/// - **within** — Limits to within certain types / 限制在特定类型内 +/// - **this** — Limit to match bean reference / 限制匹配 bean 引用 +/// - **target** — Limit to match target object / 限制匹配目标对象 +/// - **args** — Limit to match arguments / 限制匹配参数 +/// - **@annotation** — Limit to join points with subject annotation / 限制带有特定注解的连接点 /// /// # Example / 示例 /// -/// ```rust,no_run,ignore -/// use hiver_aop::Pointcut; +/// ```rust,ignore +/// use hiver_aop::pointcut; /// /// // Define a reusable pointcut /// // 定义可重用的切点 -/// #[Pointcut("execution(* com.example.service.*.*(..))")] -/// fn service_layer() -> PointcutExpression { -/// // Returns a reusable pointcut for all service layer methods -/// } -/// -/// // Use the pointcut in advice -/// // 在通知中使用切点 -/// #[Before("service_layer()")] -/// fn log_service_methods(&self, join_point: &JoinPoint) { -/// println!("Calling service method: {}", join_point.method_name()); -/// } +/// #[pointcut("execution(* *.service.*.*(..))")] +/// fn service_layer() {} /// ``` /// /// # Complex Expressions / 复杂表达式 /// -/// ```rust,no_run,ignore -/// use hiver_aop::Pointcut; -/// -/// // Combine multiple pointcuts -/// // 组合多个切点 -/// #[Pointcut("execution(public * *(..))")] -/// fn public_methods() -> PointcutExpression {} +/// Pointcuts may be combined with `&&` (all must match) or `||` (any may match): +/// 切点可通过 `&&`(全部匹配)或 `||`(任一匹配)组合: /// -/// #[Pointcut("execution(* com.example..*.*(..))")] -/// fn in_package() -> PointcutExpression {} +/// ```rust,ignore +/// use hiver_aop::pointcut; /// -/// // AND combination -/// // AND 组合 -/// #[Pointcut("public_methods() && in_package()")] -/// fn public_methods_in_package() -> PointcutExpression {} +/// #[pointcut("execution(public * *(..))")] +/// fn public_methods() {} /// -/// // OR combination -/// // OR 组合 -/// #[Pointcut("execution(* com.example.Service.*(..)) || execution(* com.example.Repository.*(..))")] -/// fn service_or_repository() -> PointcutExpression {} +/// #[pointcut("execution(* *..*.*(..)) || within(*.Service)")] +/// fn broad_match() {} /// ``` pub(crate) fn impl_pointcut(attr: TokenStream, item: TokenStream) -> TokenStream { let input = parse_macro_input!(item as ItemFn); let func_name = &input.sig.ident; - // Parse pointcut expression from attribute - // 从属性中解析切点表达式 let args = parse_macro_input!(attr as PointcutExpr); let pointcut = args.expression; + // Uniquely-named companion const so multiple pointcut definitions never clash. + // 唯一命名的伴生 const,使多个切点定义永不冲突。 + let meta_ident = format_ident!("_HIVER_POINTCUT_{}_EXPR", func_name.to_string().to_uppercase()); + let expanded = quote! { #input - impl #func_name { - /// Returns the pointcut expression - /// 返回切点表达式 - const EXPRESSION: &str = #pointcut; - - #[doc(hidden)] - fn _hiver_get_expression() -> &'static str { - EXPRESSION - } - } + /// Hiver AOP pointcut expression recorded by the `pointcut` macro. + /// 由 `pointcut` 宏记录的 Hiver AOP 切点表达式。 + #[doc(hidden)] + const #meta_ident: &'static str = #pointcut; }; TokenStream::from(expanded) diff --git a/crates/hiver-aop/README.md b/crates/hiver-aop/README.md index 4f2b8ef1..7a47702d 100644 --- a/crates/hiver-aop/README.md +++ b/crates/hiver-aop/README.md @@ -4,7 +4,7 @@ [![Documentation](https://docs.rs/hiver-aop/badge.svg)](https://docs.rs/hiver-aop) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](../../LICENSE) -> Spring AOP style annotations for Hiver Framework +> Spring AOP style annotations for the Hiver Framework > > Hiver 框架的 Spring AOP 风格注解 @@ -12,17 +12,29 @@ ## 📋 Overview / 概述 -`hiver-aop` provides Spring AOP-style procedural macros for aspect-oriented programming in the Hiver Framework. +`hiver-aop` brings Spring AOP / AspectJ-style aspect-oriented programming to +Rust, built on top of the Hiver Framework. It provides procedural macros to +*annotate* advice, and a small runtime to *weave* that advice around method +calls at run time. -`hiver-aop` 为 Hiver 框架提供 Spring AOP 风格的过程宏,支持面向切面编程。 +`hiver-aop` 为 Rust 带来 Spring AOP / AspectJ 风格的面向切面编程,构建于 Hiver 框架之上。 +它提供过程宏用于*标注*通知,并提供一个小型运行时在方法调用周围*织入*这些通知。 **Key Features / 核心特性**: -- ✅ **`#[Aspect]`** - Marks a struct as an aspect / 标记切面 -- ✅ **`@Before`** - Before advice / 前置通知 -- ✅ **`@After`** - After advice / 后置通知 -- ✅ **`@Around`** - Around advice / 环绕通知 -- ✅ **`@Pointcut`** - Pointcut definitions / 切点定义 +- ✅ **`#[aspect]`** (alias `#[Aspect]`) — Marks a struct as an aspect / 标记切面 +- ✅ **`#[before]`** / **`#[Before]`** — Before advice / 前置通知 +- ✅ **`#[after]`** / **`#[After]`** — After advice / 后置通知 +- ✅ **`#[around]`** / **`#[Around]`** — Around advice / 环绕通知 +- ✅ **`#[after_returning]`** / **`#[AfterReturning]`** — After-returning advice / 返回后通知 +- ✅ **`#[after_throwing]`** / **`#[AfterThrowing]`** — After-throwing advice / 异常后通知 +- ✅ **`#[pointcut]`** / **`#[Pointcut]`** — Pointcut definition / 切点定义 +- ✅ Runtime types: `JoinPoint`, `ProceedingJoinPoint`, `AspectRegistry`, `InterceptChain` + +Both the lowercase Rust names and the Spring-familiar uppercase names are +exported, so `use hiver_aop::{aspect, before}` and +`use hiver_aop::{Aspect, Before}` both work. The examples below use the +lowercase names (idiomatic Rust); substitute the uppercase aliases freely. --- @@ -39,552 +51,233 @@ hiver-aop = "0.1" ### Basic Usage / 基本用法 +The macros are **attribute macros placed on functions**. They work both on +free functions and on methods inside an `impl` block: + +这些宏是**放置在函数上的属性宏**,既可用于自由函数,也可用于 `impl` 块内的方法: + ```rust -use hiver_aop::{Aspect, Before, After, Around}; +use hiver_aop::{aspect, before, after, around, JoinPoint, ProceedingJoinPoint}; -#[Aspect] +#[aspect] struct LoggingAspect; impl LoggingAspect { - #[Before("execution(* com.example..*.*(..))")] + #[before("execution(* *..*.*(..))")] fn log_before(&self, join_point: &JoinPoint) { println!("Entering: {}", join_point.method_name()); } - #[After("execution(* com.example..*.*(..))")] + #[after("execution(* *..*.*(..))")] fn log_after(&self, join_point: &JoinPoint) { println!("Exiting: {}", join_point.method_name()); } - #[Around("execution(* com.example..*.*(..))")] - fn log_around(&self, join_point: JoinPoint) -> Result<(), Error> { + #[around("execution(* *..*.*(..))")] + fn log_around(join_point: &mut ProceedingJoinPoint) { println!("Before: {}", join_point.method_name()); - let result = join_point.proceed()?; - println!("After: {}", join_point.method_name()); - Ok(result) + join_point.proceed(); // let the target method run / 放行目标方法 + println!("After: {}", join_point.method_name()); } } -``` - ---- - -## 📖 Available Annotations / 可用注解 - -### `#[Aspect]` - -Marks a struct as an AOP aspect. -将结构体标记为 AOP 切面。 - -```rust -#[Aspect] -struct LoggingAspect; -``` - -### `@Before("pointcut")` - -Before advice - executes before the method. -前置通知 - 在方法执行前执行。 - -```rust -#[Before("execution(* com.example..*.*(..))")] -fn log_before(&self, join_point: &JoinPoint) { - println!("Entering: {}", join_point.method_name()); -} -``` - -### `@After("pointcut")` - -After advice - executes after the method (normal or exceptional exit). -后置通知 - 在方法执行后执行(正常或异常退出)。 -```rust -#[After("execution(* com.example..*.*(..))")] -fn log_after(&self, join_point: &JoinPoint) { - println!("Exiting: {}", join_point.method_name()); +// Free-standing advice is supported too. +// 也支持独立的通知函数。 +#[before("execution(* *..*.*(..))")] +fn free_advice(join_point: &JoinPoint) { + println!("Free advice: {}", join_point.method_name()); } ``` -### `@Around("pointcut")` +Each advice macro records its pointcut expression next to the function as a +companion constant, e.g. the `log_before` example above produces: -Around advice - wraps the method execution, can control execution flow. -环绕通知 - 包装方法执行,可以控制执行流程。 +每个通知宏会在函数旁以伴生常量记录其切点表达式,例如上面的 `log_before` 会产生: -```rust -#[Around("execution(* com.example..*.*(..))")] -fn log_around(&self, join_point: JoinPoint) -> Result<(), Error> { - println!("Before: {}", join_point.method_name()); - let result = join_point.proceed()?; - println!("After: {}", join_point.method_name()); - Ok(result) -} +```rust,ignore +const _HIVER_BEFORE_LOG_BEFORE_META: (&'static str, &'static str) = + ("execution(* *..*.*(..))", "before"); ``` -### `@Pointcut("expression")` - -Defines a reusable pointcut expression. -定义可重用的切点表达式。 - -```rust -#[Pointcut("execution(* com.example.service.*.*(..))")] -fn service_layer() -> PointcutExpression {} - -// Reference the pointcut -// 引用切点 -#[Before("service_layer()")] -fn log_service(&self, join_point: &JoinPoint) { - println!("Service method called"); -} -``` +This is valid both at module scope and inside an `impl` block (a nested `impl` +would not be), so advice can be attached to methods on the aspect struct. --- -## 📚 Pointcut Expressions / 切点表达式 - -### Execution Pattern / 执行模式 - -``` -execution(modifiers-pattern? return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern) throws-pattern?) -``` - -**Examples / 示例**: - -```rust -// All public methods -// 所有 public 方法 -"execution(public * *(..))" +## 🔌 Compile-time annotations vs runtime weaving +## 编译期注解 vs 运行时织入 -// All methods in a package -// 包中的所有方法 -"execution(* com.example..*.*(..))" +`hiver-aop` is split in two: -// All methods in a class -// 类中的所有方法 -"execution(* com.example.Service.*(..))" +1. **`hiver-aop`** — the facade crate you depend on. It re-exports the macros + and the runtime types. +2. **`hiver-aop-macros`** — the proc-macro crate (not depended on directly). -// Specific method -// 特定方法 -"execution(* com.example.UserService.getUser(..))" +The macros *annotate* advice and *record* pointcut expressions; they do **not** +automatically rewrite your business methods (Rust has no class-loading weave +step the way the JVM does). The actual application of advice happens at run +time through the runtime API: -// Methods with specific parameter types -// 带有特定参数类型的方法 -"execution(* com.example.UserService.*(String, ..))" +`hiver-aop` 分为两部分: -// Methods returning void -// 返回 void 的方法 -"execution(void com.example..*.*(..))" -``` +1. **`hiver-aop`** — 你依赖的门面 crate,重新导出宏与运行时类型。 +2. **`hiver-aop-macros`** — 过程宏 crate(不直接依赖)。 -### Combining Pointcuts / 组合切点 +宏负责*标注*通知并*记录*切点表达式;它们**不会**自动改写你的业务方法 +(Rust 没有 JVM 那样的类加载织入步骤)。通知的实际应用通过运行时 API 完成: ```rust -// AND - both conditions must match -// AND - 两个条件都必须匹配 -"execution(* com.example..*.*(..)) && execution(public * *(..))" - -// OR - either condition must match -// OR - 任一条件必须匹配 -"execution(* com.example.Service.*(..)) || execution(* com.example.Repository.*(..))" - -// NOT - negate the condition -// NOT - 否定条件 -"execution(* com.example..*.*(..)) && !execution(* com.example..*.*(..))" +use std::sync::Arc; +use hiver_aop::runtime::{InterceptChain, JoinPoint}; + +let mut chain = InterceptChain::new(); +chain.before(|jp| println!("Before: {}", jp.method_name())); +chain.after( |jp| println!("After: {}", jp.method_name())); + +let jp = JoinPoint::new( + Arc::new("target") as Arc, + "save_user".to_string(), + vec![], + "save_user()".to_string(), + "service.UserService".to_string(), +); + +let result = chain.invoke(jp, || { + println!("target running"); + Some(Arc::new(200_i32) as Arc) +}); +assert_eq!(*result.value::().unwrap(), 200); ``` -### Other Designators / 其他指示符 - -```rust -// Within a certain type -// 在特定类型内 -"within(com.example.service.*)" - -// Match bean reference (Spring) -// 匹配 bean 引用(Spring) -"this(com.example.service.UserService)" - -// Match target object -// 匹配目标对象 -"target(com.example.service.UserService)" - -// Match arguments -// 匹配参数 -"args(String, ..)" -"args(com.example.User)" - -// Methods with specific annotation -// 带有特定注解的方法 -"@annotation(com.example.Transactional)" -"@annotation(org.springframework.web.bind.annotation.PostMapping)" -``` +For registry-based matching (register many advice, match by pointcut), see +`AspectRegistry` in the [API docs](https://docs.rs/hiver-aop). --- -## 📚 Examples / 示例 +## 📖 Available Annotations / 可用注解 -### Example 1: Logging Aspect / 日志切面 +### `#[aspect]` — mark an aspect / 标记切面 ```rust -use hiver_aop::{Aspect, Before, After}; -use tracing::{info, instrument}; +use hiver_aop::aspect; -#[Aspect] +#[aspect] struct LoggingAspect; - -impl LoggingAspect { - #[Before("execution(* com.example..*.*(..))")] - fn log_method_entry(&self, join_point: &JoinPoint) { - info!( - "Entering method: {} with args: {:?}", - join_point.method_name(), - join_point.args() - ); - } - - #[After("execution(* com.example..*.*(..))")] - fn log_method_exit(&self, join_point: &JoinPoint) { - info!( - "Exiting method: {}", - join_point.method_name() - ); - } -} ``` -### Example 2: Transaction Management Aspect / 事务管理切面 +### `#[before("pointcut")]` — before advice / 前置通知 ```rust -use hiver_aop::{Aspect, Around}; - -#[Aspect] -struct TransactionAspect; - -impl TransactionAspect { - #[Around("execution(* com.example.service.*.*(..)) && @annotation(com.example.Transactional)")] - fn manage_transaction(&self, join_point: JoinPoint) -> Result<(), Error> { - // Begin transaction - // 开始事务 - let tx = Transaction::begin()?; - - match join_point.proceed() { - Ok(result) => { - tx.commit()?; - Ok(result) - } - Err(e) => { - tx.rollback()?; - Err(e) - } - } - } +#[before("execution(* *..*.*(..))")] +fn log_before(join_point: &JoinPoint) { + println!("Entering: {}", join_point.method_name()); } ``` -### Example 3: Caching Aspect / 缓存切面 - -```rust -use hiver_aop::{Aspect, Around}; -use std::collections::HashMap; - -#[Aspect] -struct CachingAspect { - cache: HashMap, -} - -impl CachingAspect { - #[Around("execution(* com.example.repository.*.find*(..))")] - fn cache_result(&self, join_point: JoinPoint) -> Result, Error> { - let cache_key = format!("{:?}", join_point.args()); - - // Check cache - // 检查缓存 - if let Some(value) = self.cache.get(&cache_key) { - return Ok(value.clone()); - } - - // Execute method - // 执行方法 - let result = join_point.proceed()?; +### `#[after("pointcut")]` — after advice / 后置通知 - // Cache result - // 缓存结果 - self.cache.insert(cache_key, result.clone()); +Executes after the join point (normal or exceptional exit), like `finally`. +在连接点之后执行(正常或异常退出),类似 `finally`。 - Ok(result) - } +```rust +#[after("execution(* *..*.*(..))")] +fn log_after(join_point: &JoinPoint) { + println!("Exiting: {}", join_point.method_name()); } ``` -### Example 4: Security Aspect / 安全切面 - -```rust -use hiver_aop::{Aspect, Before}; +### `#[around("pointcut")]` — around advice / 环绕通知 -#[Aspect] -struct SecurityAspect; +Wraps the join point. Take a `&mut ProceedingJoinPoint` and call `proceed()` to +let the target run. +包装连接点。接收 `&mut ProceedingJoinPoint`,调用 `proceed()` 以放行目标方法。 -impl SecurityAspect { - #[Before("execution(* com.example.controller.*.*(..))")] - fn check_authorization(&self, join_point: &JoinPoint) { - let user = get_current_user(); - - if !user.has_permission(join_point.method_name()) { - panic!("Unauthorized access to {}", join_point.method_name()); - } - } +```rust +#[around("execution(* *..*.*(..))")] +fn log_around(join_point: &mut ProceedingJoinPoint) { + println!("Before: {}", join_point.method_name()); + join_point.proceed(); + println!("After: {}", join_point.method_name()); } ``` -### Example 5: Performance Monitoring / 性能监控 +### `#[pointcut("expression")]` — reusable pointcut / 可重用切点 ```rust -use hiver_aop::{Aspect, Around}; -use std::time::Instant; - -#[Aspect] -struct PerformanceMonitoringAspect; - -impl PerformanceMonitoringAspect { - #[Around("execution(* com.example.service.*.*(..))")] - fn monitor_performance(&self, join_point: JoinPoint) -> Result<(), Error> { - let start = Instant::now(); - - let result = join_point.proceed(); - - let duration = start.elapsed(); - - if duration.as_millis() > 1000 { - warn!( - "Slow method: {} took {}ms", - join_point.method_name(), - duration.as_millis() - ); - } - - result - } -} +#[pointcut("execution(* *.service.*.*(..))")] +fn service_layer() {} ``` --- -## 🔀 Annotation vs Plain Rust / 注解版本 vs 原生 Rust +## 📚 Pointcut Expressions in Rust / Rust 中的切点表达式 -### Logging Aspect Example / 日志切面示例 - -#### ❌ Without Annotations (Plain Rust) / 不使用注解 - -```rust -// Must manually call logging before/after each method -// 必须在每个方法前后手动调用日志记录 -struct UserService { - db: Database, -} - -impl UserService { - async fn get_user(&self, id: i64) -> Result, Error> { - // Manual logging - repetitive and error-prone - // 手动日志记录 - 重复且容易出错 - println!("Entering: get_user with id={}", id); - - let result = self.db.find_user(id).await; - - println!("Exiting: get_user"); - result - } - - async fn create_user(&self, user: User) -> Result { - println!("Entering: create_user with user={:?}", user); - - let result = self.db.insert_user(&user).await?; - - println!("Exiting: create_user"); - Ok(result) - } - - async fn update_user(&self, id: i64, user: User) -> Result { - println!("Entering: update_user with id={}, user={:?}", id, user); - - let result = self.db.update_user(id, &user).await?; - - println!("Exiting: update_user"); - Ok(result) - } - - async fn delete_user(&self, id: i64) -> Result<(), Error> { - println!("Entering: delete_user with id={}", id); - - let result = self.db.delete_user(id).await; +> **Important / 重要** — Hiver AOP pointcuts are **string patterns matched at +> run time** against a `JoinPoint`'s `method_name()` and `target_class()`. +> There are **no JVM packages**. Patterns like `com.example..*` work as generic +> dotted-name patterns, but they carry no Java package semantics. The idiomatic +> Rust analog is to match module paths / type names such as +> `crate::service::...`, `*.service.*`, etc. +> +> Hiver AOP 的切点是**运行时匹配 `JoinPoint` 的 `method_name()` 与 `target_class()` +> 的字符串模式**。**没有 JVM 包的概念**。`com.example..*` 之类的模式可作为通用的点号名称 +> 模式工作,但并不具备 Java 包语义。Rust 惯用的等价物是匹配模块路径/类型名, +> 例如 `crate::service::...`、`*.service.*` 等。 - println!("Exiting: delete_user"); - result - } -} +### Execution pattern / 执行模式 -// Problems: -// - Logging code repeated in every method / 日志代码在每个方法中重复 -// - Easy to forget logging / 容易忘记记录日志 -// - Cannot centrally manage logging / 无法集中管理日志 -// - Mixes business logic with cross-cutting concerns / 混合业务逻辑和横切关注点 ``` - -#### ✅ With Annotations (Hiver AOP) / 使用注解 - -```rust -use hiver_aop::{Aspect, Before, After}; - -// Define aspect once - applies to all matching methods -// 定义切面一次 - 应用于所有匹配的方法 -#[Aspect] -struct LoggingAspect; - -impl LoggingAspect { - #[Before("execution(* UserService.*(..))")] - fn log_before(&self, join_point: &JoinPoint) { - println!("Entering: {}", join_point.method_name()); - } - - #[After("execution(* UserService.*(..))")] - fn log_after(&self, join_point: &JoinPoint) { - println!("Exiting: {}", join_point.method_name()); - } -} - -// Clean business logic - no logging code mixed in -// 清晰的业务逻辑 - 没有混合日志代码 -struct UserService { - db: Database, -} - -impl UserService { - async fn get_user(&self, id: i64) -> Result, Error> { - // Logging is applied automatically by AOP! - // 日志由 AOP 自动应用! - self.db.find_user(id).await - } - - async fn create_user(&self, user: User) -> Result { - self.db.insert_user(&user).await - } - - async fn update_user(&self, id: i64, user: User) -> Result { - self.db.update_user(id, &user).await - } - - async fn delete_user(&self, id: i64) -> Result<(), Error> { - self.db.delete_user(id).await - } -} - -// Benefits: -// - Logging separated from business logic / 日志与业务逻辑分离 -// - Consistent logging across all methods / 所有方法的日志一致 -// - Easy to modify logging in one place / 在一个地方轻松修改日志 -// - Business logic remains clean / 业务逻辑保持清晰 +execution(return-type-pattern declaring-type-pattern? method-name-pattern(param-pattern)) ``` -**Code Reduction / 代码减少**: Eliminates 50%+ repetitive logging code / 消除 50%+ 的重复日志代码 - ---- - -### Transaction Management Example / 事务管理示例 - -#### ❌ Without Annotations / 不使用注解 +- `*` matches a single name segment / 匹配单个名称段 +- `..` matches zero or more segments / 匹配零或多个段 -```rust -impl PaymentService { - async fn process_payment(&self, from: i64, to: i64, amount: i64) -> Result<(), Error> { - // Manual transaction management - error-prone - // 手动事务管理 - 容易出错 - let tx = self.begin_transaction().await?; - - match self.debit(&tx, from, amount).await { - Ok(_) => match self.credit(&tx, to, amount).await { - Ok(_) => { - tx.commit().await?; - Ok(()) - } - Err(e) => { - tx.rollback().await?; - Err(e) - } - } - Err(e) => { - tx.rollback().await?; - Err(e) - } - } - } +**Examples / 示例**: - // Must repeat this pattern for every transactional method - // 必须为每个事务方法重复此模式 - async fn transfer_funds(&self, from: i64, to: i64, amount: i64) -> Result<(), Error> { - let tx = self.begin_transaction().await?; - // ... same pattern ... - } -} +```text +"execution(* *..*.*(..))" // any method / 任意方法 +"execution(* *.Service.*(..))" // methods on types whose name is `.Service` +"execution(* *.UserService.save_user(..))" // a specific method on a specific type +"execution(* get*(..))" // methods whose name matches `get*` ``` -#### ✅ With Annotations / 使用注解 - -```rust -use hiver_aop::{Aspect, Around}; -use hiver_data_annotations::Transactional; - -#[Aspect] -struct TransactionAspect; - -impl TransactionAspect { - #[Around("execution(* PaymentService.*(..))")] - async fn manage_transaction(&self, join_point: JoinPoint) -> Result<(), Error> { - let tx = self.begin_transaction().await?; - - match join_point.proceed().await { - Ok(_) => { - tx.commit().await?; - Ok(()) - } - Err(e) => { - tx.rollback().await?; - Err(e) - } - } - } -} +### Other designators / 其他指示符 -impl PaymentService { - // Transaction is managed automatically! - // 事务自动管理! - async fn process_payment(&self, from: i64, to: i64, amount: i64) -> Result<(), Error> { - self.debit(from, amount).await?; - self.credit(to, amount).await?; - Ok(()) - } -} +```text +"within(*)" // within any type / 任意类型内 +"within(service.UserService)" // within a specific type / 特定类型内 +"@annotation(my::Transactional)" // (parsed; runtime annotation check is not supported) ``` ---- +### Combining pointcuts / 组合切点 -### Comparison Table / 对比表 +```text +"execution(* *..*.*(..)) && within(*.Service)" // AND — both must match / 都须匹配 +"execution(* *.Service.*(..)) || execution(* *.Repository.*(..))" // OR — either / 任一 +``` -| Feature / 功能 | Plain Rust / 原生 Rust | With AOP Annotations / 使用 AOP 注解 | -|----------------|----------------------|----------------------------------| -| **Code Duplication** / 代码重复 | ❌ High / 高 | ✅ Low / 低 | -| **Separation of Concerns** / 关注点分离 | ❌ Mixed / 混合 | ✅ Separated / 分离 | -| **Maintainability** / 可维护性 | ❌ Changes in many places / 多处修改 | ✅ Change in one place / 一处修改 | -| **Business Logic Clarity** / 业务逻辑清晰度 | ❌ Obsured by cross-cutting code / 被横切代码模糊 | ✅ Clear and focused / 清晰专注 | -| **Consistency** / 一致性 | ❌ Easy to miss / 容易遗漏 | ✅ Automatically applied / 自动应用 | -| **Flexibility** / 灵活性 | ❌ Hard to modify behavior | ✅ Easy to change aspect / 易于修改切面 | +See `PointcutExpression` in the [API docs](https://docs.rs/hiver-aop) for the +full matching semantics. --- ## 🧪 Testing / 测试 -Run tests: +Run the unit and runtime tests: ```bash cargo test --package hiver-aop ``` -Run examples: +Downstream compile regression tests (trybuild) guard the documented API shape +against breaking again: + +```bash +cargo test --package hiver-aop --test downstream_compile +``` + +Run the example: ```bash cargo run --package hiver-aop --example logging_aspect @@ -595,7 +288,7 @@ cargo run --package hiver-aop --example logging_aspect ## 📖 Documentation / 文档 - **API Documentation**: [docs.rs/hiver-aop](https://docs.rs/hiver-aop) -- **Spring AOP Documentation**: [https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop](https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop) +- **Spring AOP Reference**: [https://docs.spring.io/spring-framework/reference/core/aop.html](https://docs.spring.io/spring-framework/reference/core/aop.html) --- @@ -604,41 +297,27 @@ cargo run --package hiver-aop --example logging_aspect ### Java / Spring AOP ```java -@Aspect -@Component +@Aspect @Component public class LoggingAspect { - private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class); - @Before("execution(* com.example..*.*(..))") public void logBefore(JoinPoint joinPoint) { logger.info("Entering: {}", joinPoint.getSignature().toShortString()); } - - @After("execution(* com.example..*.*(..))") - public void logAfter(JoinPoint joinPoint) { - logger.info("Exiting: {}", joinPoint.getSignature().toShortString()); - } } ``` ### Rust / Hiver AOP ```rust -use hiver_aop::{Aspect, Before, After}; -use tracing::info; +use hiver_aop::{aspect, before, JoinPoint}; -#[Aspect] +#[aspect] struct LoggingAspect; impl LoggingAspect { - #[Before("execution(* com.example..*.*(..))")] + #[before("execution(* *..*.*(..))")] fn log_before(&self, join_point: &JoinPoint) { - info!("Entering: {}", join_point.method_name()); - } - - #[After("execution(* com.example..*.*(..))")] - fn log_after(&self, join_point: &JoinPoint) { - info!("Exiting: {}", join_point.method_name()); + println!("Entering: {}", join_point.method_name()); } } ``` @@ -653,9 +332,3 @@ Licensed under either of: - MIT license ([LICENSE-MIT](../../LICENSE-MIT) or https://opensource.org/licenses/MIT) at your option. / 由您选择。 - ---- - -**Built with ❤️ for Java developers transitioning to Rust** - -**为从 Java 转向 Rust 的开发者构建 ❤️** diff --git a/crates/hiver-aop/examples/logging_aspect.rs b/crates/hiver-aop/examples/logging_aspect.rs new file mode 100644 index 00000000..f8618245 --- /dev/null +++ b/crates/hiver-aop/examples/logging_aspect.rs @@ -0,0 +1,143 @@ +//! hiver AOP Macro Example / hiver AOP 宏示例 +//! +//! This example demonstrates the AOP (Aspect-Oriented Programming) attribute +//! macros compiling end-to-end — both as free functions and as methods on an +//! aspect struct — and shows how the runtime registry wires advice to a join +//! point. +//! +//! 此示例演示 AOP(面向切面编程)属性宏的端到端编译——既可作为自由函数, +//! 也可作为切面结构体的方法——并展示运行时注册表如何将通知连接到连接点。 +//! +//! Run / 运行: +//! ```bash +//! cargo run -p hiver-aop --example logging_aspect +//! ``` + +// Advice methods in this example are demonstrative (their shape matters, not +// their calls), so silence the expected dead-code warnings. +// 本示例中的通知方法仅作演示(重要的是其形式而非调用), +// 因此屏蔽预期的死代码告警。 +#![allow(dead_code)] + +use std::sync::Arc; + +use hiver_aop::{ + AdviceType, After, Around, Aspect, Before, InterceptChain, JoinPoint, Pointcut, + PointcutExpression, ProceedingJoinPoint, +}; + +// ============================================================================ +// Compile-time: aspect + pointcut + advice definitions +// 编译期:切面 + 切点 + 通知定义 +// ============================================================================ + +/// Logging aspect — applies to service-layer methods. +/// 日志切面 — 应用于服务层方法。 +#[Aspect] +struct LoggingAspect; + +impl LoggingAspect +{ + /// Companion metadata recorded by the `before` macro is reachable as an + /// associated constant. + /// 由 `before` 宏记录的伴生元数据可作为关联常量被访问。 + const META: (&'static str, &'static str) = Self::_HIVER_BEFORE_LOG_BEFORE_META; + + /// Run before matched methods. + /// 在匹配的方法之前执行。 + #[Before("execution(* *..*.*(..))")] + fn log_before(&self, join_point: &JoinPoint) + { + println!("[before] entering: {}", join_point.method_name()); + } + + /// Run after matched methods (always, like `finally`). + /// 在匹配的方法之后执行(始终执行,类似 `finally`)。 + #[After("execution(* *..*.*(..))")] + fn log_after(&self, join_point: &JoinPoint) + { + println!("[after] exiting: {}", join_point.method_name()); + } + + /// Wrap matched methods; call `proceed()` to let the target run. + /// 包装匹配的方法;调用 `proceed()` 以放行目标方法。 + #[Around("execution(* *..*.*(..))")] + fn log_around(join_point: &mut ProceedingJoinPoint) + { + println!("[around] before: {}", join_point.method_name()); + join_point.proceed(); // let the target run / 放行目标方法 + println!("[around] after: {}", join_point.method_name()); + } + + /// A reusable pointcut expression. + /// 可重用的切点表达式。 + #[Pointcut("execution(* *..*.*(..))")] + fn service_layer() {} +} + +/// A free-standing advice function (module scope) — also supported. +/// 模块作用域的自由通知函数 — 同样受支持。 +#[Before("execution(* *..*.*(..))")] +fn free_advice(join_point: &JoinPoint) +{ + println!("[free] {}", join_point.method_name()); +} + +// ============================================================================ +// Runtime: build a join point and run an InterceptChain around a target +// 运行时:构造连接点,并在目标周围运行拦截链 +// ============================================================================ + +fn main() +{ + println!("=== hiver AOP Macro Demo ===\n"); + + // 1. The aspect compiles and is constructible. + // 切面编译通过且可构造。 + let _ = LoggingAspect; + println!("LoggingAspect created; recorded metadata = {:?}", LoggingAspect::META); + + // 2. Pointcut matching works against a join point's method/class names. + // 切点匹配针对连接点的方法名/类名工作。 + let wildcard = PointcutExpression::new("execution(* *..*.*(..))".to_string()); + let jp = JoinPoint::new( + Arc::new("user_service") as Arc, + "save_user".to_string(), + vec![Arc::new(42_i64) as Arc], + "save_user(i64)".to_string(), + "service.UserService".to_string(), + ); + println!( + "pointcut \"execution(* *..*.*(..))\" matches save_user? {}", + wildcard.matches(&jp) + ); + + // 3. Build a runtime InterceptChain (the actual weaving mechanism) and invoke a target through + // it. + // 构建运行时拦截链(真正的织入机制)并通过它调用目标。 + let mut chain = InterceptChain::new(); + chain.before(|join_point| println!("[chain before] {}", join_point.method_name())); + chain.around(|pjp| { + println!("[chain around->proceed]"); + pjp.proceed(); + }); + chain.after(|join_point| println!("[chain after] {}", join_point.method_name())); + + println!("\n--- invoking target through InterceptChain ---"); + let result = chain.invoke(jp, || { + println!("[target] save_user running"); + Some(Arc::new(200_i32) as Arc) + }); + println!("target returned: {:?}", result.value::()); + + // 4. The registry exposes `AdviceType` and registers callable advice too. + // 注册表还暴露 `AdviceType` 并可注册可调用通知。 + println!("\nAdviceType::Before == {:?}", AdviceType::Before); + println!("\n=== Demo Complete ==="); + + // Silence unused warnings for the compile-time-only advice items. + // 抑制仅编译期使用的通知项的未使用告警。 + let _ = free_advice; + let _ = |_jp: &JoinPoint| {}; + let _ = LoggingAspect::service_layer; +} diff --git a/crates/hiver-aop/examples/logging_aspect.rs.disabled b/crates/hiver-aop/examples/logging_aspect.rs.disabled deleted file mode 100644 index de9fdcd3..00000000 --- a/crates/hiver-aop/examples/logging_aspect.rs.disabled +++ /dev/null @@ -1,89 +0,0 @@ -//! hiver AOP Macro Examples / hiver AOP 宏示例 -//! -//! This example demonstrates AOP (Aspect-Oriented Programming) macro usage in hiver. -//! Note: hiver-aop is a proc-macro crate; runtime types are available at compile time -//! via the generated code. The macros below compile successfully and generate -//! aspect-related boilerplate. -//! -//! 此示例演示了 hiver 中 AOP 宏的使用。 -//! 注意:hiver-aop 是过程宏 crate;运行时类型通过生成的代码在编译期可用。 - -use hiver_aop::{After, Around, Aspect, Before, Pointcut}; - -// ============================================================================ -// Example: Aspect Definitions / 切面定义 -// ============================================================================ - -/// Logging aspect — applies to service layer methods -/// 日志切面 — 应用于服务层方法 -#[Aspect] -struct LoggingAspect; - -#[Aspect] -struct SecurityAspect; - -// ============================================================================ -// Example: Pointcut Definitions / 切点定义 -// ============================================================================ - -/// Pointcut for all service methods -/// 所有服务方法的切点 -#[Pointcut] -struct ServicePointcut; - -// ============================================================================ -// Example: Before/After/Around Advices / 前置/后置/环绕通知 -// ============================================================================ - -impl LoggingAspect { - /// Log before method execution - /// 在方法执行前记录日志 - #[Before("execution(* com.example.service..*.*(..))")] - fn log_before(&self) { - // Generated code will inject JoinPoint at compile time - // 生成的代码将在编译时注入 JoinPoint - } - - /// Log after method execution - /// 在方法执行后记录日志 - #[After("execution(* com.example.service..*.*(..))")] - fn log_after(&self) { - // Post-execution logging logic - // 方法执行后日志逻辑 - } - - /// Around advice for performance monitoring - /// 性能监控的环绕通知 - #[Around("execution(* com.example.service..*.*(..))")] - fn monitor_performance(&self) { - // Wraps the target method with timing logic - // 使用计时逻辑包装目标方法 - } -} - -impl SecurityAspect { - /// Security check before execution - /// 执行前的安全检查 - #[Before("execution(* com.example.admin..*.*(..))")] - fn check_auth(&self) { - // Authentication verification - // 身份验证 - } -} - -fn main() { - println!("=== hiver AOP Macro Demo ===\n"); - - // The aspect structs compile and are usable - // 切面结构体编译通过且可使用 - let _logging = LoggingAspect; - let _security = SecurityAspect; - let _pointcut = ServicePointcut; - - println!("LoggingAspect: created"); - println!("SecurityAspect: created"); - println!("ServicePointcut: created"); - - println!("\nAspects are compiled and ready for runtime weaving."); - println!("=== Demo Complete ==="); -} diff --git a/crates/hiver-aop/src/lib.rs b/crates/hiver-aop/src/lib.rs index e20828bc..d7629f1f 100644 --- a/crates/hiver-aop/src/lib.rs +++ b/crates/hiver-aop/src/lib.rs @@ -13,43 +13,59 @@ //! //! ## Macros (re-exported from `hiver-aop-macros`) //! -//! - **`#[Aspect]`** - Marks a struct as an aspect -//! - **`@Before`** - Before advice -//! - **`@After`** - After advice -//! - **`@Around`** - Around advice -//! - **`@AfterReturning`** - After-returning advice -//! - **`@AfterThrowing`** - After-throwing advice -//! - **`@Pointcut`** - Pointcut definition +//! Both the lowercase proc-macro names and Spring-familiar uppercase aliases are +//! exported, so either import style works: +//! 同时导出小写的过程宏名和 Spring 风格的大写别名,两种导入方式均可: +//! +//! - **`#[aspect]` / `#[Aspect]`** — Marks a struct as an aspect / 标记切面 +//! - **`#[before]` / `#[Before]`** — Before advice / 前置通知 +//! - **`#[after]` / `#[After]`** — After advice / 后置通知 +//! - **`#[around]` / `#[Around]`** — Around advice / 环绕通知 +//! - **`#[after_returning]` / `#[AfterReturning]`** — After-returning advice / 返回后通知 +//! - **`#[after_throwing]` / `#[AfterThrowing]`** — After-throwing advice / 异常后通知 +//! - **`#[pointcut]` / `#[Pointcut]`** — Pointcut definition / 切点定义 //! //! ## Runtime types //! -//! - `JoinPoint` - Method execution join point -//! - `ProceedingJoinPoint` - Proceedable join point for around advice -//! - `AspectRegistry` - Aspect registration and management -//! - `AdviceChain` - Advice execution chain +//! - `JoinPoint` - Method execution join point / 方法执行连接点 +//! - `ProceedingJoinPoint` - Proceedable join point for around advice / 可继续连接点 +//! - `AspectRegistry` - Aspect registration and management / 切面注册与管理 +//! - `InterceptChain` - Runtime interception chain / 运行时拦截链 +//! - `AdviceChain` - Advice execution chain / 通知执行链 //! //! ## Example / 示例 //! -//! ```rust,no_run,ignore -//! use hiver_aop::{Aspect, Before, After, Around}; +//! ```rust,no_run +//! use hiver_aop::{JoinPoint, aspect, before}; //! -//! #[Aspect] +//! #[aspect] //! struct LoggingAspect; //! -//! impl LoggingAspect { -//! #[Before("execution(* com.example..*.*(..))")] -//! fn log_before(&self, join_point: &JoinPoint) { +//! impl LoggingAspect +//! { +//! #[before("execution(* *..*.*(..))")] +//! fn log_before(&self, join_point: &JoinPoint) +//! { //! println!("Entering: {}", join_point.method_name()); //! } //! } +//! # +//! # fn main() {} //! ``` #![warn(missing_docs)] #![warn(unreachable_pub)] -// Re-export procedural macros from hiver-aop-macros +// Re-export procedural macros from hiver-aop-macros, under their lowercase names. +// 从 hiver-aop-macros 重新导出过程宏(小写名)。 +// Spring-familiar uppercase aliases. `pub use foo as Foo` re-exports a proc-macro +// under an additional name so users can write either `#[before]` or `#[Before]`. +// Spring 风格的大写别名。`pub use foo as Foo` 将过程宏以额外名称重新导出, +// 用户可使用 `#[before]` 或 `#[Before]` 任一形式。 pub use hiver_aop_macros::{ - after, after_returning, after_throwing, around, aspect, before, pointcut, + after as After, after, after_returning, after_returning as AfterReturning, after_throwing, + after_throwing as AfterThrowing, around, around as Around, aspect, aspect as Aspect, before, + before as Before, pointcut, pointcut as Pointcut, }; pub mod runtime; @@ -64,7 +80,8 @@ pub mod runtime; )] mod tests; -// Re-export runtime types at crate root for convenience +// Re-export runtime types at crate root for convenience. +// 为方便使用,在 crate 根重新导出运行时类型。 pub use runtime::{ AdviceChain, AdviceType, AspectRegistry, InterceptChain, InterceptResult, JoinPoint, PointcutExpression, ProceedingJoinPoint, global_registry, diff --git a/crates/hiver-aop/src/runtime.rs b/crates/hiver-aop/src/runtime.rs index ac5f9df2..5fed529d 100644 --- a/crates/hiver-aop/src/runtime.rs +++ b/crates/hiver-aop/src/runtime.rs @@ -36,13 +36,12 @@ use tokio::sync::RwLock; /// /// # Example / 示例 /// -/// ```rust,no_run,ignore -/// use hiver_aop::runtime::JoinPoint; +/// ```rust,ignore +/// use hiver_aop::{before, runtime::JoinPoint}; /// -/// #[Before("execution(* com.example..*.*(..))")] +/// #[before("execution(* *..*.*(..))")] /// fn log_before(join_point: &JoinPoint) { /// println!("Calling: {}", join_point.method_name()); -/// println!("Args: {:?}", join_point.args()); /// } /// ``` pub struct JoinPoint @@ -1137,15 +1136,25 @@ impl fmt::Debug for InterceptResult /// /// # Example / 示例 /// -/// ```rust,no_run,ignore +/// ```rust,no_run +/// use std::sync::Arc; +/// /// use hiver_aop::runtime::{InterceptChain, JoinPoint}; /// /// let mut chain = InterceptChain::new(); /// chain.before(|jp| println!("Before: {}", jp.method_name())); /// chain.after(|jp| println!("After: {}", jp.method_name())); /// -/// let jp = JoinPoint::new(/* ... */); -/// let result = chain.invoke(jp, || Some(Arc::new(42))); +/// let jp = JoinPoint::new( +/// Arc::new("target") as Arc, +/// "do_work".to_string(), +/// vec![], +/// "do_work()".to_string(), +/// "Worker".to_string(), +/// ); +/// let result = +/// chain.invoke(jp, || Some(Arc::new(42_i32) as Arc)); +/// assert_eq!(*result.value::().unwrap(), 42); /// ``` pub struct InterceptChain { diff --git a/crates/hiver-aop/tests/downstream_compile.rs b/crates/hiver-aop/tests/downstream_compile.rs new file mode 100644 index 00000000..bf3166dd --- /dev/null +++ b/crates/hiver-aop/tests/downstream_compile.rs @@ -0,0 +1,42 @@ +//! Downstream compile regression tests / 下游编译回归测试 +//! +//! These tests use [`trybuild`] to compile small programs as if they were +//! written in a *downstream* crate that depends on `hiver-aop`. They guard +//! against the regression documented in issues #60 and #61, where the advice +//! and pointcut macros generated an invalid `impl #func_name { ... }` that: +//! +//! 1. Failed inside an `impl` block with `error: implementation is not supported in traits or +//! impls`; and +//! 2. Failed on a free function with `error[E0573]: expected type, found function`. +//! +//! 这些测试使用 [`trybuild`],以"下游 crate 依赖 `hiver-aop`"的方式编译小程序, +//! 用于防范 issue #60 和 #61 中记录的回归:通知与切点宏曾生成非法的 +//! `impl #func_name { ... }`,导致: +//! +//! 1. 在 `impl` 块内报 `error: implementation is not supported in traits or impls`; +//! 2. 在自由函数上报 `error[E0573]: expected type, found function`。 +//! +//! Run with: `cargo test -p hiver-aop --test downstream_compile` +//! +//! [`trybuild`]: https://docs.rs/trybuild + +#[allow( + clippy::indexing_slicing, + clippy::items_after_statements, + clippy::assertions_on_constants +)] +#[test] +fn downstream_compile_tests() +{ + let t = trybuild::TestCases::new(); + + // These fixtures MUST compile after the fix. + // 这些夹具在修复后必须能编译通过。 + t.pass("tests/ui/free_fn_advice.rs"); + t.pass("tests/ui/impl_block_advice.rs"); + t.pass("tests/ui/uppercase_aliases.rs"); + + // Documents the compile error when a non-existent macro name is imported. + // 记录导入不存在的宏名时的编译错误。 + t.compile_fail("tests/ui/wrong_macro_name_fail.rs"); +} diff --git a/crates/hiver-aop/tests/ui/free_fn_advice.rs b/crates/hiver-aop/tests/ui/free_fn_advice.rs new file mode 100644 index 00000000..e70e5bd5 --- /dev/null +++ b/crates/hiver-aop/tests/ui/free_fn_advice.rs @@ -0,0 +1,67 @@ +//! Regression: advice macros on free functions (module scope). +//! 回归:自由函数(模块作用域)上的通知宏。 +//! +//! Before the fix this failed with +//! `error[E0573]: expected type, found function ` because the macro +//! generated `impl #func_name { ... }` where `#func_name` is a function. +//! 修复前此用例失败,报 +//! `error[E0573]: expected type, found function `, +//! 因为宏生成了 `impl #func_name { ... }`,而 `#func_name` 是函数而非类型。 + +use hiver_aop::{after, after_returning, after_throwing, around, before, pointcut, JoinPoint}; + +#[before("execution(* *..*.*(..))")] +fn free_before(join_point: &JoinPoint) +{ + let _ = join_point.method_name(); +} + +#[after("execution(* *..*.*(..))")] +fn free_after(join_point: &JoinPoint) +{ + let _ = join_point.method_name(); +} + +#[around("execution(* *..*.*(..))")] +fn free_around(join_point: &mut hiver_aop::ProceedingJoinPoint) +{ + join_point.proceed(); +} + +#[after_returning("execution(* *..*.*(..))")] +fn free_after_returning(join_point: &JoinPoint) +{ + let _ = join_point.method_name(); +} + +#[after_throwing("execution(* *..*.*(..))")] +fn free_after_throwing(join_point: &JoinPoint) +{ + let _ = join_point.method_name(); +} + +#[pointcut("execution(* *..*.*(..))")] +fn free_pointcut() +{} + +// The companion consts exist at module scope and are reachable. +// 伴生 const 存在于模块作用域,且可访问。 +const _: (&str, &str) = _HIVER_BEFORE_FREE_BEFORE_META; +const _: (&str, &str) = _HIVER_AFTER_FREE_AFTER_META; +const _: (&str, &str) = _HIVER_AROUND_FREE_AROUND_META; +const _: (&str, &str) = _HIVER_AFTER_RETURNING_FREE_AFTER_RETURNING_META; +const _: (&str, &str) = _HIVER_AFTER_THROWING_FREE_AFTER_THROWING_META; +const _: &str = _HIVER_POINTCUT_FREE_POINTCUT_EXPR; + +fn main() +{ + let jp = JoinPoint::new( + std::sync::Arc::new("target") as std::sync::Arc, + "do_work".to_string(), + vec![], + "do_work()".to_string(), + "Worker".to_string(), + ); + free_before(&jp); + free_after(&jp); +} diff --git a/crates/hiver-aop/tests/ui/impl_block_advice.rs b/crates/hiver-aop/tests/ui/impl_block_advice.rs new file mode 100644 index 00000000..6fd63c12 --- /dev/null +++ b/crates/hiver-aop/tests/ui/impl_block_advice.rs @@ -0,0 +1,58 @@ +//! Regression: advice macros on methods inside an `impl` block. +//! 回归:`impl` 块内方法上的通知宏。 +//! +//! This is the primary failing case from issue #60. Before the fix it failed +//! with `error: implementation is not supported in traits or impls` because the +//! macro generated a nested `impl` while already inside one. +//! 这是 issue #60 的主要失败用例。修复前报 +//! `error: implementation is not supported in traits or impls`, +//! 因为宏在已位于 `impl` 内时又生成了嵌套 `impl`。 + +use hiver_aop::{after, around, aspect, before, pointcut, JoinPoint}; + +#[aspect] +struct LoggingAspect; + +impl LoggingAspect +{ + #[before("execution(* *..*.*(..))")] + fn log_before(&self, join_point: &JoinPoint) + { + let _ = join_point.method_name(); + } + + #[after("execution(* *..*.*(..))")] + fn log_after(&self, join_point: &JoinPoint) + { + let _ = join_point.method_name(); + } + + #[around("execution(* *..*.*(..))")] + fn log_around(join_point: &mut hiver_aop::ProceedingJoinPoint) + { + join_point.proceed(); + } + + // A reusable pointcut defined on the aspect. + // 在切面上定义可重用切点。 + #[pointcut("execution(* *..*.*(..))")] + fn service_layer() + {} + + // The companion consts are reachable as associated consts of the aspect. + // 伴生 const 可作为切面的关联常量被访问。 + const _PROBE: (&str, &str) = Self::_HIVER_BEFORE_LOG_BEFORE_META; +} + +fn main() +{ + let aspect = LoggingAspect; + let jp = JoinPoint::new( + std::sync::Arc::new("target") as std::sync::Arc, + "do_work".to_string(), + vec![], + "do_work()".to_string(), + "Worker".to_string(), + ); + aspect.log_before(&jp); +} diff --git a/crates/hiver-aop/tests/ui/uppercase_aliases.rs b/crates/hiver-aop/tests/ui/uppercase_aliases.rs new file mode 100644 index 00000000..89326c62 --- /dev/null +++ b/crates/hiver-aop/tests/ui/uppercase_aliases.rs @@ -0,0 +1,53 @@ +//! Regression: Spring-familiar UPPERCASE macro aliases resolve. +//! 回归:Spring 风格的大写宏别名可正确解析。 +//! +//! The README documents `#[Aspect]`, `#[Before]`, `#[After]`, `#[Around]`, and +//! `#[Pointcut]`. These uppercase aliases must resolve so the documented import +//! shape compiles from downstream crates (issue #60 reproduction 1). +//! README 中使用了 `#[Aspect]`、`#[Before]`、`#[After]`、`#[Around]`、`#[Pointcut]`。 +//! 这些大写别名必须可解析,使文档中的导入形式能从下游 crate 编译(issue #60 复现 1)。 + +use hiver_aop::{After, Around, Aspect, Before, JoinPoint, Pointcut}; + +#[Aspect] +struct LoggingAspect; + +impl LoggingAspect +{ + #[Before("execution(* *..*.*(..))")] + fn log_before(&self, join_point: &JoinPoint) + { + let _ = join_point.method_name(); + } + + #[After("execution(* *..*.*(..))")] + fn log_after(&self, join_point: &JoinPoint) + { + let _ = join_point.method_name(); + } + + #[Around("execution(* *..*.*(..))")] + fn log_around(&self, _join_point: &JoinPoint) + { + // Real around advice takes a &mut ProceedingJoinPoint; this fixture only + // checks the uppercase alias resolves on a method. + // 真正的 around 通知接收 &mut ProceedingJoinPoint;此夹具仅验证大写别名在方法上可解析。 + } + + #[Pointcut("execution(* *..*.*(..))")] + fn service_layer() + {} +} + +fn main() +{ + let aspect = LoggingAspect; + let jp = JoinPoint::new( + std::sync::Arc::new("target") as std::sync::Arc, + "do_work".to_string(), + vec![], + "do_work()".to_string(), + "Worker".to_string(), + ); + aspect.log_before(&jp); +} diff --git a/crates/hiver-aop/tests/ui/wrong_macro_name_fail.rs b/crates/hiver-aop/tests/ui/wrong_macro_name_fail.rs new file mode 100644 index 00000000..d6b7af19 --- /dev/null +++ b/crates/hiver-aop/tests/ui/wrong_macro_name_fail.rs @@ -0,0 +1,17 @@ +//! Documents a stable compile error: importing a non-existent macro. +//! 记录一个稳定的编译错误:导入不存在的宏。 +//! +//! `hiver-aop` exports both lowercase names (`before`, `after`, ...) and +//! uppercase aliases (`Before`, `After`, ...). It does NOT export a +//! `NonExistentAdvice` macro, so this program is expected to fail with an +//! unresolved-import error. +//! `hiver-aop` 同时导出小写名(`before`、`after`、...)和大写别名(`Before`、`After`、...)。 +//! 它不导出 `NonExistentAdvice` 宏,因此该程序应因未解析的导入而编译失败。 + +use hiver_aop::NonExistentAdvice; + +#[NonExistentAdvice("execution(* *..*.*(..))")] +fn broken() +{} + +fn main() {} diff --git a/crates/hiver-aop/tests/ui/wrong_macro_name_fail.stderr b/crates/hiver-aop/tests/ui/wrong_macro_name_fail.stderr new file mode 100644 index 00000000..e3335c0d --- /dev/null +++ b/crates/hiver-aop/tests/ui/wrong_macro_name_fail.stderr @@ -0,0 +1,5 @@ +error[E0432]: unresolved import `hiver_aop::NonExistentAdvice` + --> tests/ui/wrong_macro_name_fail.rs:11:5 + | +11 | use hiver_aop::NonExistentAdvice; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `NonExistentAdvice` in the root