最近研究MyBatisFlex的日志记录配置,用到了Spring的字段注入,发现了很有趣的一点。
字段注入和构造器注入,两种注入方式不能混用,否则会出现编译错误,IDEA编译器会拒绝执行。
最后解决的代码如下:
public class MyBatisFlexConfig {
// 慢SQL阈值,默认1秒
@Value("${mybatis-flex.audit.slow-sql-threshold:1000}")
private long slowSqlThreshold;
public MyBatisFlexConfig(@Value("${mybatis-flex.audit.enable:false}") boolean auditEnable) {
// 1. 设置是否开启审计
AuditManager.setAuditEnable(auditEnable);
// 2. 配置日志收集器
AuditManager.setMessageCollector(auditMessage -> {
long elapsedTime = auditMessage.getElapsedTime();
// 场景 A:慢 SQL 记录 (阈值之上,使用 WARN 级别)
if (elapsedTime > slowSqlThreshold) {
log.warn("[慢SQL告警] 耗时: {}ms, SQL: {}", elapsedTime, auditMessage.getFullSql());
return;
}
// 场景 B:普通 SQL 记录
if (log.isInfoEnabled()) {
log.info("[SQL-info] SQL: {}, 耗时: {}ms", auditMessage.getFullSql(), elapsedTime);
}
});
}
}
下列所有的内容均运行在
MyBatisFlexConfig类内。
例如下面的例子,代码如下:
@Value("${mybatis-flex.audit.enable:false}")
private boolean auditEnable;
public MyBatisFlexConfig(auditEnable) {
AuditManager.setAuditEnable(auditEnable);
}
这里IDEA会报错,提示构造方法参数写法非法。
原因是我们在这里把字段注入和构造器注入混用,同时格式也不符合Java的方法参数标准。
问题现在有两个。
public MyBatisFlexConfig(auditEnable)少了参数类型,Java 方法参数必须写类型,不能独立游离出现。
@Value 标在字段private boolean auditEnable上,但构造方法里又声明了一个同名参数,二者不是同一个东西。
如果想在构造方法中用配置值,推荐把 @Value 放到构造方法参数上。
要么只使用字段注入,要么只使用构造器注入,字段注入和构造器注入绝不能混用。
正确写法:
public MyBatisFlexConfig(@Value("${mybatis-flex.audit.enable:false}") boolean auditEnable) {
AuditManager.setAuditEnable(auditEnable);
AuditManager.setMessageCollector(auditMessage -> log.info("[SQL-info] SQL: {}, 耗时: {}ms",
auditMessage.getFullSql(),
auditMessage.getElapsedTime()));
}
如果坚持要字段注入,则构造方法里不能直接依赖它,应该用 @PostConstruct:
@Value("${mybatis-flex.audit.enable:false}")
private boolean auditEnable;
@PostConstruct
public void init() {
AuditManager.setAuditEnable(auditEnable);
AuditManager.setMessageCollector(auditMessage -> log.info("[SQL-info] SQL: {}, 耗时: {}ms",
auditMessage.getFullSql(),
auditMessage.getElapsedTime()));
}
字段注入和构造器注入的区别在于“配置值什么时候、通过什么方式进入对象”。
但同一个配置值不要一边字段注入、一边又在构造器参数里使用同名变量,否则容易产生语义混乱直接编译失败。
就我而言,更推荐使用构造器注入。字段注入随可用,但不如构造器注入清晰、可测试。
构造器注入
public MyBatisFlexConfig(@Value("${mybatis-flex.audit.enable:false}") boolean auditEnable) {
AuditManager.setAuditEnable(auditEnable);
}
这段构造器注入解释如下
Spring 创建
MyBatisFlexConfig 对象时,先解析${mybatis-flex.audit.enable:false}。把解析后的
boolean值作为构造方法参数传进去。构造方法执行时,
auditEnable已经有值。这个值是构造方法的局部变量,只在构造方法内部有效。
执行顺序是是:
解析配置值 -> 调用构造方法 -> 对象创建完成
所以构造器注入适合这种场景:
public MyBatisFlexConfig(@Value("${mybatis-flex.audit.enable:false}") boolean auditEnable) {
AuditManager.setAuditEnable(auditEnable);
}
因为你正好要在构造方法里用配置值,十分方便简洁。
构造方法里可以安全使用构造器参数,但不能安全依赖字段注入的字段。
构造器注入适合下列情况。
配置值初始化时必须使用。
想让依赖关系更明确。
想减少字段可变状态。
字段注入
@Value("${mybatis-flex.audit.enable:false}")
private boolean auditEnable;
@PostConstruct
public void init() {
AuditManager.setAuditEnable(auditEnable);
}
这段字段注入解释如下
Spring 先调用无参构造方法或其他构造方法创建对象。
对象创建完成后,Spring 再通过反射给字段
auditEnable赋值。字段赋值完成后,再执行
@PostConstruct方法。
执行顺序是:
调用构造方法 -> 给字段注入配置值 -> 执行 @PostConstruct
所以字段注入时,不要在构造方法里依赖这个字段。
例如,这段逻辑就有潜在问题,字段注入不会成功注入内容。
@Value("${mybatis-flex.audit.enable:false}")
private boolean auditEnable;
public MyBatisFlexConfig() {
AuditManager.setAuditEnable(auditEnable);
}
原因是构造方法执行时,字段注入还没有发生,auditEnable 还是 Java 默认值:
private boolean auditEnable; // 默认 false
即使配置里写了注入参数,构造方法执行时也会拿到 false,因为字段此时还没被 Spring 注入。
mybatis-flex:
audit:
enable: true
字段注入适合下列情况
需要等 Spring 完成字段注入后再初始化。
初始化逻辑不适合放构造方法。
老项目已有大量字段注入风格。

Comments NOTHING