自定义注解拦截器
使用 InterceptorManager.me().registerInterceptor 实现自定义注解拦截器
在基于 JFinal AOP 的开发中,我们通常使用:
@AopBefore(SomeInterceptor.class)
来为方法添加拦截器。
但这种方式有一个明显问题:
👉 业务语义不清晰
例如事务:
@AopBefore(TransactionInterceptor.class)
从代码上完全看不出:
“这是一个事务方法”
这也是为什么 Spring、Micronaut、Quarkus 等框架都提供:
@Transactional
这种语义化注解。
registerInterceptor 的作用
InterceptorManager.me().registerInterceptor(...) 的目标是:
✅ 将 “业务注解” 自动绑定到拦截器
例如:
@ATransactional -> TransactionInterceptor
让开发者只关注:
“这是事务方法”
而不是:
“这个方法需要某个技术型拦截器”
一、注册拦截器
建议在 Boot 配置阶段注册。
示例:
package demo.jooq.config;
import com.litongjava.context.BootConfiguration;
import com.litongjava.jfinal.aop.InterceptorManager;
import demo.jooq.tx.ATransactional;
import demo.jooq.tx.TransactionInterceptor;
public class JooqBootConfig implements BootConfiguration {
@Override
public void config() throws Exception {
// 注解 -> 拦截器 绑定
InterceptorManager.me().registerInterceptor(ATransactional.class, TransactionInterceptor.class);
}
}
启动:
TioApplication.run(JooqApp.class, new JooqBootConfig(), args);
⚠️ 必须在应用启动早期注册!
原因:
ProxyGenerator 在生成代理类时,需要提前知道哪些方法存在拦截器。
如果注册过晚:
👉 方法可能不会生成代理 👉 拦截器将完全不生效
二、定义事务注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ATransactional {
}
建议改为:
@Target({ElementType.METHOD, ElementType.TYPE})
这样可以支持:
- 方法级事务
- 类级事务(整个 Service)
三、实现拦截器
public class TransactionInterceptor implements AopInterceptor {
@Override
public void intercept(AopInvocation inv) {
TransactionManager txManager =Aop.get(TransactionManager.class);
txManager.tx(() -> {
inv.invoke();
});
}
}
为什么使用 Aop.get?
而不是:
new TransactionManager()
因为:
✅ 支持依赖注入 ✅ 支持代理 ✅ 生命周期统一
四、业务代码使用方式
不推荐:
@AopBefore(TransactionInterceptor.class)
public void changePassword(...) {
}
技术味太重。
推荐:
@ATransactional
public void changePassword2(String loginName, String newPassword) {
systemAdminDao.updatePassword(loginName, newPassword);
}
可读性极强:
这是一个事务方法。
五、运行原理(重要)
很多人会误以为:
registerInterceptor 是运行时才生效。
实际上:
分为两个阶段:
🔥 阶段 1:生成代理类(启动时)
ProxyGenerator 会判断:
方法是否存在拦截器?
包括:
- @AopBefore
- 全局拦截器
- registerInterceptor 映射注解
如果存在:
👉 才生成代理方法。
否则:
👉 直接调用原方法。
这一步极其关键。
🔥 阶段 2:方法调用时
代理代码类似:
AopInvocation inv = new AopInvocation(...);
inv.invoke();
然后:
InterceptorManager
-> 构建拦截器链
-> 顺序执行
你的 TransactionInterceptor 就在这里执行。
六、AopClear 仍然有效
例如:
@AopClear(TransactionInterceptor.class)
public void queryOnly() {
}
效果:
移除事务拦截器。
即使:
@ATransactional
在类上声明。
清除规则:
| 位置 | 行为 |
|---|---|
| class | 清除 global / class |
| method | 清除上层拦截器 |
| method 无参 | 只保留 method 拦截器 |
与你使用 @AopBefore 时完全一致。
无需额外配置。
七、强烈建议的最佳实践
⭐ 1. 永远使用语义化注解
不要写:
@AopBefore(CacheInterceptor.class)
写:
@ACacheable
⭐ 2. registerInterceptor 统一放 Boot 阶段
不要在:
- Service
- DAO
- Controller
中注册。
否则代理可能已经生成。
⭐ 3. 一个注解只做一件事
错误:
@ATransactional + @Cache + @Retry
正确:
- @ATransactional
- @ACacheable
- @Retryable
组合使用。
八、registerInterceptor vs AopBefore
registerInterceptor:
✅ 推荐用于:
- 事务
- 缓存
- 限流
- 重试
- 审计日志
即:
具有明确业务语义的能力
AopBefore:
适合:
- 临时拦截器
- 内部调试
- 框架级增强
但不建议直接暴露给业务层。
九、性能说明
很多人担心:
注解映射会不会降低性能?
答案:
几乎不会。
原因:
- 映射只在启动时解析一次
- 拦截器实例是单例
- 方法调用走的是生成代码(非反射)
性能接近手写代理。
十、一句话总结
registerInterceptor 的本质是:让 AOP 从“技术驱动”升级为“业务语义驱动”。
让代码从:
怎么实现?
变为:
这段代码在做什么?
这就是现代 AOP 的设计方向。
