这两天被问到一个有意思的问题,就是如果在构造器中拿到匿名对象。 这个问题有意思在,直觉上是可以通过外部放一个成员变量去接,然后后续就可以使用了,但实际不行。
问题复现
下面这个是构造器,当通过 supper 去调用父类构造器。由于问题出现在Spring项目当中,所以我复现的例子也基于Spring重新搭了一个项目来复现这个过程,代码放github上。
复现demo:
代码语言:javascript复制package com.test;
import org.springframework.beans.factory.annotation.Autowired;
public class TransactionCache extends RevokingDB {
@Autowired
public TransactionCache(String dbName) {
// 其他代码不能在 supper 之前,但是又要拿到 TxCacheDB,不能再new一次,否则会初始化两次TxCacheDB。
super(new TxCacheDB(dbName));
// 需要是在这里调一下 txCacheDB.init();
}
}
代码语言:javascript复制public class TxCacheDB {
public TxCacheDB(String dbName) {
System.out.println("TxCacheDB: " dbName);
}
public void init() {
System.out.println("TxCacheDB: init");
}
}
这里可以偿试几种解决方案:
- 方案一 直接成员变量中 new TxCacheDB(dbName); 构造器中再使用
- 方案二 super调用一个方法,而不直接new TxCacheDB();
- 方案三 将对象保在ThreadLocal中,再拿出来
验证方案
方案一
这种在直觉上没有问题,但是实际有问题:
- 成员变量不知道 dbName,传入的具体值是什么,如果写死就失去灵活性。
- 写死。如果写死,你不能确定会传入dbName将会是什么,实际中这个dbName有很多个,没有办法写死
方案二
这个方法看着就可行,来验证一下。
代码语言:javascript复制import org.springframework.beans.factory.annotation.Autowired;
@Slf4j
public class TransactionCache2 extends RevokingDB {
private static TxCacheDB txCacheDB = null;
@Autowired
public TransactionCache2(String dbName) {
// 其他代码不能在 supper 之前
super(getTxCache(dbName));
txCacheDB.init();
}
private static TxCacheDB getTxCache(String dbName) {
txCacheDB = new TxCacheDB(dbName);
return txCacheDB;
}
}
输出结果:
TxCacheDB: trans-cache TxCacheDB: init
结查证明可行。 但是有个问题,我就用一次,还有开辟一段元数据区态内存来放 这个 static TxCacheDB,不划算。 我就想用一次,不想还占用内存。
方案三
将对象放入本地线程中,使用后就移除。 这样即可以使用对象,也不需要一直占用部分内存。
代码语言:javascript复制public class TransactionCache3 extends RevokingDB {
@Autowired
public TransactionCache3(String dbName) {
// 其他代码不能在 supper 之前
super(new TxCacheDB(dbName));
try {
TxCacheDB txCacheDB = ThreadLocalUtil.get();
txCacheDB.init();
} finally {
ThreadLocalUtil.remove();
}
}
}
TxCacheDB: trans-cache3 TxCacheDB: init
总结
java 对象在初始化的时候构造器中 supper 是永远放在第一行不能变。这一特性决定了这个问题的解决只能曲线救国。