lostars
发布于 2018-07-03 / 1210 阅读
0

记一次SPRING配置文件读取问题

一个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。