通过将 aspect 定义为抽象类并声明抽象 pointcut 方法,可让下游项目在继承时提供具体包路径匹配逻辑,从而实现编译后 aspect 的灵活复用与定制。
通过将 aspect 定义为抽象类并声明抽象 pointcut 方法,可让下游项目在继承时提供具体包路径匹配逻辑,从而实现编译后 aspect 的灵活复用与定制。
在使用 AspectJ 进行横切日志增强时,一个常见需求是:将通用日志切面(如 LogAspect)打包为独立的 Maven 依赖(JAR),供多个业务项目引用;同时允许各业务方按需指定“需自动织入的日志包路径”,而无需重新编译或修改原始切面代码。由于 Java 注解属性必须是编译期常量,无法直接参数化 @Pointcut 字符串,因此需借助面向对象机制绕过该限制。
✅ 推荐方案:抽象 Aspect + 具体子类覆盖
核心思想是将切面设计为抽象基类,把可变的 Pointcut 声明为抽象方法,由下游项目继承并实现:
// 在共享 JAR 中定义(不可实例化)
@Aspect
public abstract class AutoLogAspect {
private static final Logger log = Logger.getLogger(AutoLogAspect.class);
// 固定注解驱动的切点(如 @Log)
@Pointcut("execution(@com.stellariver.milky.aspectj.tool.log.Log * *(..))")
public void annotationPointcut() {}
// 抽象切点:由子类提供具体包路径
@Pointcut("")
public abstract void packagePointcut();
// 组合两个切点,统一处理
@Around("annotationPointcut() || packagePointcut()")
public Object logAround(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
boolean success = true;
Object result = null;
try {
result = pjp.proceed();
return result;
} catch (Throwable t) {
success = false;
throw t;
} finally {
long cost = System.currentTimeMillis() - start;
String signature = pjp.getSignature().toShortString();
log.info(String.format(
"[success:%s] [cost:%dms] [result:%s]\n====args: %s",
success, cost, Objects.toString(result, ""),
Arrays.stream(pjp.getArgs())
.map(Objects::toString)
.collect(Collectors.joining("] [", "[arg0:", "]"))
));
}
}
}
⚠️ 注意:抽象 @Pointcut("") 是合法语法,仅作占位;实际织入时由子类非空实现覆盖。
下游业务项目只需新建一个具体子类,注入目标包路径即可:
// 在业务模块中定义(需参与编译)
package com.example.myapp;
import com.stellariver.milky.aspectj.tool.log.AutoLogAspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class MyAppLogAspect extends AutoLogAspect {
// ✅ 动态指定本项目需拦截的包:com.example.myapp.service.*
@Pointcut("within(com.example.myapp.service..*) && execution(* *(..))")
public void packagePointcut() {}
}
此时,Maven 编译阶段(配合 aspectj-maven-plugin)会自动识别 MyAppLogAspect 对 packagePointcut() 的具体实现,并将所有匹配 com.example.myapp.service 下的方法调用织入日志逻辑——原始 AutoLogAspect 无需任何改动,也无需重新发布 JAR。
? 补充:运行时织入(LTW)场景
若业务项目采用 Load-Time Weaving(LTW),还可完全跳过源码继承步骤,直接在 src/main/resources/META-INF/aop.xml 中声明:
<aspectj><aspects><aspect name="com.stellariver.milky.aspectj.tool.log.AutoLogAspect"></aspect></aspects><weaver options="-verbose -showWeaveInfo"><!-- 动态绑定 packagePointcut --><include within="com.example.myapp.service..*"></include></weaver></aspectj>
? 提示:LTW 方式下需确保 JVM 启动时添加 -javaagent:/path/to/aspectjweaver.jar,且 aop.xml 必须位于类路径根目录。
✅ 总结
| 方式 | 是否需编译子类 | 配置位置 | 适用阶段 |
|---|---|---|---|
| 抽象 Aspect + 子类继承 | ✅ 是 | Java 源码 | 编译时织入(CTW) |
| LTW + aop.xml | ❌ 否 | XML 配置文件 | 加载时织入(LTW) |
该方案兼顾了复用性、可维护性与灵活性:上游提供稳定契约(抽象切面),下游按需定制行为(具体切点),彻底规避硬编码包路径导致的耦合问题。










