一个java web项目启动时遇到了下面这么个报错:
java.util.prefs.WindowsPreferences.WindowsRegOpenKey1 Trying to recreate Windows registry node Software\JavaSoft\Prefs at root 0x80000002
很诡异的一个报错,怎么会与windows注册表相关,这个报错是一个配置文件属性加载引起的,百度很快找到了解决方式:
在注册表项 HKEY_LOCAL_MACHINE\Software\JavaSoft 下面新建 Prefs 项目即可。
问题是解决了,但是怎么会出现这么个报错?于是找到了配置文件的引入部分:
<bean id="propertyConfigurer" class=
"org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="locations">
<list>
...
</list>
</property>
</bean>
这里解析配置文件用的是 PreferencesPlaceholderConfigurer 这个类,该类扩展了 PropertyPlaceholderConfigurer 类,添加了windows下用户路径和系统路径的解析。
看下该类的解析配置文件的核心方法:
private Preferences systemPrefs;
private Preferences userPrefs;
......
@Override
protected String resolvePlaceholder(String placeholder, Properties props) {
String path = null;
String key = placeholder;
int endOfPath = placeholder.lastIndexOf('/');
if (endOfPath != -1) {
path = placeholder.substring(0, endOfPath);
key = placeholder.substring(endOfPath + 1);
}
// 首先解析用户路径
String value = resolvePlaceholder(path, key, this.userPrefs);
if (value == null) {
// 系统路径
value = resolvePlaceholder(path, key, this.systemPrefs);
if (value == null) {
value = props.getProperty(placeholder);
}
}
return value;
}
Preferences是一个保存系统和用户配置信息的一个类,同时有下面的子类扩展关系:
Preferences <- AbstractPreferences <- WindowsPreferences
上面的方法会判断最后一个 / 位置,其实就是判断key中是否包含路径,如果包含就会截取出路径去解析用户和系统的配置,最后都找不到再从 Properties 中获取。
继续看 resolvePlaceholder 方法:
protected String resolvePlaceholder(String path, String key,
Preferences preferences) {
if (path != null) {
// Do not create the node if it does not exist...
try {
if (preferences.nodeExists(path)) {
return preferences.node(path).get(key, null);
}
else {
return null;
}
}
catch (BackingStoreException ex) {
throw new BeanDefinitionStoreException(
"Cannot access specified node path [" + path + "]", ex);
}
}
else {
return preferences.get(key, null);
}
}
nodeExists 方法是 AbstractPreferences 中的一个判断系统路径是否存在的一个方法,追踪调用链可以发现这个方法最终调用了
定义在 Preferences 中的 childrenNamesSpi() 方法,在 WindowsPreferences 中重写了该方法,该方法中我们可以看到最终抛出 BackingStoreException 的地方:
protected String[] childrenNamesSpi() throws BackingStoreException {
// Open key
int nativeHandle = openKey(KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE);
if (nativeHandle == NULL_NATIVE_HANDLE) {
throw new BackingStoreException(
"Could not open windows registry node " +
byteArrayToString(windowsAbsolutePath()) +
" at root 0x" +
Integer.toHexString(rootNativeHandle()) + ".");
}
// Get number of children
......
// Get children
......
}
同时,在该类的定义中我们可以看到在windows中系统根路径被定义为:
private static final byte[] WINDOWS_ROOT_PATH =
stringToByteArray("Software\\JavaSoft\\Prefs");
这也就是为什么解决方法中需要添加这个注册表项了。
PS:用户路径为 HKEY_CURRENT_USER\Software\JavaSoft\Prefs
回过头再来看这个问题,首先配置文件key中出现了 / 符号,同时解析配置文件的类用了 PreferencesPlaceholderConfigurer 而不是常规的 PropertyPlaceholderConfigurer 导致Spring同时解析用户和系统环境中的配置,而windows中默认系统根路径注册表中不存在,这才导致了问题的产生。
按照道理来说这两个注册表项应该是jdk或者jre安装的时候创建的,于是google之,发现了下面的一个issue:
https://bugs.openjdk.java.net/browse/JDK-8139507
里面提到了出现这个问题的原因:
jre的一个bug,某个版本之前是正常的,没开启windwos UAC。