错误情景

  • 环境:
    • 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) {}
}

本站由 钟意 使用 Stellar 1.28.1 主题创建。
又拍云 提供CDN加速/云存储服务
vercel 提供托管服务
湘ICP备2023019799号-1
总访问 次 | 本页访问