從static變數初始化到Spring/Spring boot的工具類靜態變數注入
寫這篇博文,要從java.lang.ExceptionInInitializerError
這個報錯開始。簡單的看上去,這是一個類初始化異常報錯。但事實上並不是這樣,這是由於呼叫某個static
變數屬性時而該屬性沒有初始化而導致的錯誤,所以,在debug
模式下,你第二次再嘗試呼叫操作static
屬性的方法時,可能就會丟擲一個空指標異常了。據聽請看這段程式碼。
Domain.java
public class Domain {
private static Domain domain = new Domain();
private static Map<String,String > domainMap = new HashMap<String
,String>();
private Domain() {
domainMap.put("isTrue","true");
}
public static Domain getInstance() {
return domain;
}
}
TestDomain.java
public class TestDomain {
public static void main(String[] args) {
String domainString = Domain.getInstance().toString();
System.out.println(domainString);
}
}
在執行呼叫的時候就會報錯了!那原因是什麼呢?
原因還是很簡單的,在呼叫Domain靜態方法時,由於兩個變數都是靜態變數,不會對它進行賦值,但會對變數按順序進行初始化。所以先給domain初始化,這個時候呼叫私有建構函式,函式內已經用到了domainMap,而這個變數還沒有初始化,進而丟擲了沒有初始化類的異常。
想要解決這個異常,兩種方案。
- 把domain和domainMap的變數宣告位置調換,這樣在呼叫私有建構函式時domainMap已經初始化了。
- 將domain的建構函式公有化,並使外部呼叫來初始化,而不是宣告時即初始化。當然這樣並不符合工具類的使用方便,會造成浪費記憶體等後果。
漸進
之所以舉上面這個例子,是因為它的構造很像工具類。
首先你得明白什麼是工具類,簡單來說它是一種可以不用初始化物件而直接呼叫其靜態方法而達到某一些功能的以Utils或Helper結尾的類。說到這裡,要宣告一下,並不是所有的靜態類都是推薦使用的,使用靜態類的過程往往只是為了閱讀方便和呼叫簡單,但卻不知會增加程式的耦合度,破壞設計模式等缺點。這裡貼一篇文章寫的很不錯:如何擺脫工具類。
而我們最終的問題是,如何在工具類中初始化賦值靜態屬性呢。專案環境是在spring框架下,舉個例子:
XXXProperties.java
public class XXXProperties {
private String staticParam1;
private String staticParam2;
public String getStaticParam1() {
return staticParam1;
}
public void setStaticParam1(String staticParam1) {
this.staticParam1 = staticParam1;
}
public String getStaticParam2() {
return staticParam2;
}
public void setStaticParam2(String staticParam2) {
this.staticParam2 = staticParam2;
}
}
XXUtils.java
public class XXUtils {
@Autowired //靜態變數通過spring並不能直接注入,所以這樣是會報錯的
private static XXXProperties xxxProperties;
private static String do() {
String xxxPropertiesSize = xxxProperties.getSize();
return xxxPropertiesSize;
}
}
當外部想要呼叫XXUtils
的do()
時,發現xxxProperties並沒有注入值,而我們去呼叫時,程式也自然會丟擲文章開始說的異常。
怎麼去注入呢?
方案
-
採用間接注入方式
//可以換成@Configuration,與@Inject配合使用 @Component public class XXUtils { //可以換成@Inject @Resource private XXXProperties xxxPropertiesAutowired; private static XXXProperties xxxProperties; @PostConstruct public void init() { this.xxxPropertiesAutowired = xxxProperties; } }
-
將靜態引數用beans提取,以鍵值對形式儲存,在工具類中直接呼叫
配置
應用其中
public class org.springframework.beans.factory.config.PropertyPlaceholderConfigurer extends org.springframework.beans.factory.config.PlaceholderConfigurerSupport
-
如果你不想提取bean,也可以直接讀取file.
public class XMLConfig { private static final Logger logger = Logger.getLogger(XMLConfig.class); public static final String FILEPATH = "xmlconf.properties"; public static long fileLastModified = 0; //屬性檔案xmlconf.properties中對應的各個鍵值對 public static HashMap<String, String> paramsMap = new HashMap<String, String>(); public static HashMap<String, String> loadProperties(String file) { HashMap<String, String> map = new HashMap<String, String>(); InputStream in = null; Properties p = new Properties(); try { in = new BufferedInputStream(new FileInputStream(file)); p.load(in); } catch (FileNotFoundException e) { logger.error(file + " is not exists!"); } catch (IOException e) { logger.error("IOException when load " + file); } finally { if (in != null) { try { in.close(); } catch (IOException e) { logger.error("Close IO error!"); } } } Set<Entry<Object, Object>> set = p.entrySet(); Iterator<Entry<Object, Object>> it = set.iterator(); while (it.hasNext()) { Entry<Object, Object> entry = it.next(); String key = (String) entry.getKey(); String value = (String) entry.getValue(); logger.debug(key + "=" + value); // System.out.println(key + "=" + value); if (key != null && value != null) { map.put(key.trim(), value.trim()); } } return map; } }
- 待你完善
期待有更好的方法一起交流討論。