惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Engineering at Meta
Engineering at Meta
雷峰网
雷峰网
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
B
Blog
Y
Y Combinator Blog
WordPress大学
WordPress大学
Microsoft Azure Blog
Microsoft Azure Blog
小众软件
小众软件
G
Google Developers Blog
云风的 BLOG
云风的 BLOG
罗磊的独立博客
博客园 - 三生石上(FineUI控件)
博客园 - 叶小钗
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
量子位
The Cloudflare Blog
T
The Blog of Author Tim Ferriss
博客园_首页
B
Blog RSS Feed
Hugging Face - Blog
Hugging Face - Blog
IT之家
IT之家
阮一峰的网络日志
阮一峰的网络日志
L
LangChain Blog
宝玉的分享
宝玉的分享

钟意博客

国家统计局数据查询 MCP 照见·硅·肆 涌现之前 照见·硅·叁 换行重启 照见·硅·贰 钢筋水泥 照见·硅·壹 你好世界 照见·硅·零 序言 大语言模型的不确定性 科艺知识库 ARM64 胶东半岛观察 海光 K100 DCU VLLM 推理环境构建 博客多平台负载均衡方案 浅谈RAG Web技术构建桌面应用方案 Spring WebSocket 错误 SpringBoot WebSocket 代理模式、客户端模式 window 端口占用但是查不到 Coze同插件不同工具之间代码复用 Pachelbel's Greatest Hit: The Ultimate Canon - 纪念帕海贝尔:终极卡农 圣诞快乐,劳伦斯先生 Merry Christmas Mr. Lawrence
Spring AOP 调用自身失效
钟意 · 2025-03-23 · via 钟意博客

错误情景

  • 环境:
    • Spring: 2.7.18
  • 操作:
    1. 顶层方法分析所选数据源
    2. 切换数据源
    3. 调用对应查询
    4. AOP代理失效导致多数据源切换失败
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 数据库元数据服务实现
*/
@Service
public class DataBaseMetaService implements IDataBaseMetaService {

/**
* 获取输入库表在数据库里的字段注释
*/
@Override
public List<DataBaseMateField> selectTableFieldsByScope(String dbName, String tableName) {
List<DataBaseMateField> dataBaseMetaFields = ListUtil.empty();
switch (dbName) {
case "xxx":
dataBaseMetaFields = byMaster(tableName);
break;
case "xxxxxx":
dataBaseMetaFields = byMonitorHm(tableName);
if (CollectionUtils.isEmpty(dataBaseMetaFields))
dataBaseMetaFields = byMonitorWce(tableName);
break;
default:
break;
}
return dataBaseMetaFields.stream().peek(field ->
field.setFieldName(StringUtil.toCamelCase(field.getFieldName()))
).collect(Collectors.toList());
}

@DS("a")
public List<DataBaseMateField> byMaster(String tableName) {}

@DS("b-a")
public List<DataBaseMateField> byMonitorHm(String tableName) {}

@DS("b-b")
public List<DataBaseMateField> byMonitorWce(String tableName) {}
}

错误诱因

  1. Spring AOP 原理:@DS 依赖 AOP 代理,而 this.xxx() 直接调用自身方法,不会经过代理对象。

  2. JDK 代理与 CGLIB 代理的区别:默认情况下,@Transactional 和 @DS 这种 AOP 机制都是基于代理的,需要从代理对象调用方法才能生效。

解决方案

  1. 方式 1:使用 @Lazy 注解注入自身(推荐)

    1
    2
    3
    @Lazy
    @Autowired
    private DataBaseMetaService self;

    这样 self.byXXX() 实际是从代理对象调用,从而触发 AOP,确保 @DS 切换数据源生效。

  2. 方式 2:通过 AopContext 获取代理对象

    1
    2
    DataBaseMetaService proxy = (DataBaseMetaService) AopContext.currentProxy();
    proxy.byMaster(tableName);

    需要开启 exposeProxy = true,在 application.yml 配置:

    1
    2
    3
    4
    spring:
    aop:
    proxy-target-class: true
    expose-proxy: true

    但这种方式代码侵入性较强,不如方式 1 优雅。

  3. 方式 3:将 byXXX() 方法抽取到另一个 @Service。这样 @DS 标注的方法始终在被代理对象上执行,避免 this 调用导致 AOP 失效。

结尾

我这里选择第一种解决方案,注入自身。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* 数据库元数据服务实现
*/
@Service
public class DataBaseMetaService implements IDataBaseMetaService {
// 延迟注入自身,确保 @DS 生效
@Lazy
@Autowired
private DataBaseMetaService self;

private final DataBaseMetaMapper dataBaseMetaMapper;

public DataBaseMetaService(DataBaseMetaMapper dataBaseMetaMapper) {
this.dataBaseMetaMapper = dataBaseMetaMapper;
}

/**
* 获取输入库表在数据库里的字段注释
*/
@Override
public List<DataBaseMateField> selectTableFieldsByScope(String dbName, String tableName) {
List<DataBaseMateField> dataBaseMetaFields = ListUtil.empty();
switch (dbName) {
case "xxx":
dataBaseMetaFields = self.byMaster(tableName);
break;
case "xxxxxx":
dataBaseMetaFields = self.byMonitorHm(tableName);
if (CollectionUtils.isEmpty(dataBaseMetaFields))
dataBaseMetaFields = self.byMonitorWce(tableName);
break;
default:
break;
}
return dataBaseMetaFields.stream().peek(field ->
field.setFieldName(StringUtil.toCamelCase(field.getFieldName()))
).collect(Collectors.toList());
}

@DS("a")
public List<DataBaseMateField> byMaster(String tableName) {}

@DS("b-a")
public List<DataBaseMateField> byMonitorHm(String tableName) {}

@DS("b-b")
public List<DataBaseMateField> byMonitorWce(String tableName) {}
}