从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.xxxProperties = xxxPropertiesAutowired;
}
}
也可以使用在Spring Boot中使用 @ConfigurationProperties 注解 给出的注解方式,是针对spring-boot的。
- 将静态参数用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;
}
}
- 待你完善
期待有更好的方法一起交流讨论。