搞搞ResourceBundle 热加载(一)

ResourceBundle热加载这个历史悠久的问题和陈年的老酒的唯一区别就是:老酒越老越香,ResourceBundle是越老越头疼。ReadOnly 老早之前就批判过ResourceBundle 愚蠢的设计,虽然当时有人认为那么设计合理,甚至还拿出了 SoftCache 这样的东西来辩驳,不过正确的东西永远是正确的,jdk1.6 中 ResourceBundle具备了 clearCache 的功能。那么对于要兼容 jdk1.6 以下的东西来说该怎么办呢?

Spring 中就有热加载的国际化方案,它是怎么做的?原来Spring 完全抛弃了 ResourceBundle,自己实现了一个国际化方案。好是挺好,不过我不能拿来直接用,因为目前的接口仍然是需要 ResourceBundle。

使用反射来修改ResourceBundle的cacheList的读取权限,然后清空cacheList,也是解决方案之一,不过并不是完全可靠。

好吧,不管怎么样,是重新写一个ResourceBundle,还是别的方式,在这之前我们都应该对 ResourceBundle有一个充分的了解。所以,我们还是先来看看ResourceBundle的源代码。考虑到兼容jdk1.4,所以我们就看 jdk1.4 的 ResourceBundle!

http://javaresearch.gro.clinux.org/jdk140/java/util/ResourceBundle.java.html 这个在线的代码非常好,大家可以参考这个。

ResourceBundle中首先是一堆变量的定义,一个一个来看看。

private static final ResourceCacheKey cacheKey =
                                        new ResourceCacheKey();


这个 ResourceCacheKey 是什么呢?这是一个 private 的 class,看看它的定义:
private static final class ResourceCacheKey implements Cloneable {
....
}


我们需要关注的是这个方法:

public void setKeyValues(ClassLoader loader, String searchName, 
                                             Locale defaultLocale) {
            this.searchName = searchName;
            hashCodeCache = searchName.hashCode();
            this.defaultLocale = defaultLocale;
            if (defaultLocale != null) {
                hashCodeCache ^= defaultLocale.hashCode();
            }
            if (loader == null) {
                this.loaderRef = null;
            } else {
                loaderRef = new SoftReference(loader);
                hashCodeCache ^= loader.hashCode();
            }
        }


似乎有点难懂...... 其实我们都知道ResourceBundle会缓存读取的properties文件,那么究竟是根据什么缓存的呢?其实就是靠这个 ResourceCacheKey 来缓存。

通过这个 setKeyValues 方法,可以知道 ResourceCacheKey 通过 ClassLoader, searchName(其实就是 bundle的名字),Locale 三个 hashcode 来合并成自己的 hashcode。然后, ResourceBundle 再用这个 ResourceCacheKey 来作为 key 缓存读取的信息。

我们也可以看看这个 ResourceCacheKey 的一些其他方法,如 equals, 如果以后我们也有这样的需求,这个 ResourceCacheKey 无疑是很好的范例。

现在再回到前面来:
private static final ResourceCacheKey cacheKey =
                                        new ResourceCacheKey();


既然定义了一个 cacheKey ,而且是 static 的,就意味着 ResourceBundle 中不会再生成额外的 cacheKey 对象了。这一个 cacheKey 对象正是通过 setKeyValues 和 clear方法来不断重用的。

再继续看代码:
private static final int INITIAL_CACHE_SIZE = 25;
private static final float CACHE_LOAD_FACTOR = (float)1.0;
private static final int MAX_BUNDLES_SEARCHED = 3;


这是一堆 cache 相关的数字,没什么意义,掠过~~

private static final Hashtable underConstruction =
              new Hashtable(MAX_BUNDLES_SEARCHED, CACHE_LOAD_FACTOR);


这个变量从名字上看来很难明白它的作用,没关系,到下面再看,掠过~~

private static final Integer DEFAULT_NOT_FOUND = new Integer(-1);


一个常量的定义,根据名字就知道,“默认没有对象”的定义,没什么意义,掠过~~

private static SoftCache cacheList =
              new SoftCache (INITIAL_CACHE_SIZE, CACHE_LOAD_FACTOR);


终于看到关键的变量了,cacheList 正是缓存国际化信息的东西。

protected ResourceBundle parent = null;


parent?看来ResourceBundle还具有父子的关系(这个属性平时很少用哦)

private Locale locale = null;


很普通的定义,掠过~~


好了,类中定义的变量我们都分析完了,下面我们来看看 ResourceBundle 的方法。我们最常用的就是 bundle.getString 方法,那么就用这个方法当作入口来分析。

public final String getString(String key) {
return (String) getObject(key);
}




原来是继续调用了 getObject ,继续跟踪:

public final Object getObject(String key) {
  Object obj = handleGetObject(key);
  if (obj == null) {
    if (parent != null) {
      obj = parent.getObject(key);
    }
    if (obj == null)
       throw new MissingResourceException("Can't find resource for bundle "
         +this.getClass().getName()
         +", key "+key, this.getClass().getName(), key);
  }
  return obj;
}




看来关键的就是这个 handleGetObject 方法了。

protected abstract Object handleGetObject(String key);




原来是一个抽象方法,头脑冲动,浪费时间!ResourceBundle的 javadoc 清清楚楚地讲了 ResourceBundle 的子类要重写 handleGetObject 方法。看来之前仔细的阅读javadoc还是有必要的阿!那就从 ResourceBundle.getBundle 开始吧!


public static final ResourceBundle getBundle(String baseName)
{
return getBundleImpl(baseName, Locale.getDefault(),
/* must determine loader here, else we break stack invariant */
getLoader());
}

public static final ResourceBundle getBundle(String baseName,
Locale locale)
{
return getBundleImpl(baseName, locale, getLoader());
}

public static ResourceBundle getBundle(String baseName, Locale locale,
ClassLoader loader)
{
if (loader == null) {
throw new NullPointerException();
}
return getBundleImpl(baseName, locale, loader);
}




其中有一个 getLoader 方法,这是一个得到 ClassLoader 的方法,里面又调用了一个 native 的方法,看不到代码,不过不重要,掠过~~

不过我们要注意到,这里出现了 ClassLoader , 不熟悉 ClassLoader 的同学快去补补课


好,接下来我们就来关注一下 getBundleImpl 这个方法。有点复杂,没关系,一点一点看。




    private static ResourceBundle getBundleImpl(String baseName, Locale locale,
                                           ClassLoader loader)
    {
        if (baseName == null) {
            throw new NullPointerException();
        }

        //We use the class loader as the "flag" value that signifies a bundle
        //that could not be found.  This allows the entries to be garbage
        //collected when the loader gets garbage collected.  If we don't
        //have a loader, use a default value for NOTFOUND.
        // 这里定义了一个 NOTFOUND 变量
        final Object NOTFOUND = (loader != null) ? (Object)loader : (Object)DEFAULT_NOT_FOUND;

        //fast path the case where the bundle is cached
        // 这里就是构造了 bundle 的名字,例如 baseName = country ,
        // locale 是 Locale.CHINESE,
        // 那么 bundleName 就会是 country_zh
        String bundleName = baseName;
        String localeSuffix = locale.toString();
        if (localeSuffix.length() > 0) {
            bundleName += "_" + localeSuffix;
        } else if (locale.getVariant().length() > 0) {
            //This corrects some strange behavior in Locale where
            //new Locale("", "", "VARIANT").toString == ""
            bundleName += "___" + locale.getVariant();
        }
        
        // The default locale may influence the lookup result, and
        // it may change, so we get it here once.
        Locale defaultLocale = Locale.getDefault();
        // 从缓存中取 bundle ,根据 ClassLoader, bundleName,
        // locale 来取,细节稍后分析。
        Object lookup = findBundleInCache(loader, bundleName, defaultLocale);
        // 注意:这里并不是 lookup == null, 而是上面定义的 NOTFOUND 变量。
        if (lookup == NOTFOUND) {
            throwMissingResourceException(baseName, locale);
        } else if (lookup != null) {
            // 如果缓存中存在,那么就使用缓存里的ResourceBundle对象。
            return (ResourceBundle)lookup;
        }

        //The bundle was not cached, so start doing lookup at the root
        //Resources are loaded starting at the root and working toward
        //the requested bundle.

        //If findBundle returns null, we become responsible for defining
        //the bundle, and must call putBundleInCache to complete this
        //task.  This is critical because other threads may be waiting
        //for us to finish.
        // 这里定义了一个 parent 变量,根据名字很难判断它的作用,继续分析
        Object parent = NOTFOUND;
        try {
            //locate the root bundle and work toward the desired child
            // 查找 "root" bundle。啥是 root bundle?? 如果 baseName = country,
            // 那么 root 找的就是 country.properties
            Object root = findBundle(loader, baseName, defaultLocale, baseName, null, NOTFOUND);
            if (root == null) {
                // 如果没有找到 root bundle,那么就设置缓存中的 baseName 对应的
                // bundle 对象为 NOTFOUND。
                putBundleInCache(loader, baseName, defaultLocale, NOTFOUND);
                root = NOTFOUND;
            }

            // Search the main branch of the search tree.
            // We need to keep references to the bundles we find on the main path
            // so they don't get garbage collected before we get to propagate().
            // 这里调用了 calculateBundleNames 方法。这个 calculateBundleNames
            // 就是计算有多少个可能存在的bundle 文件。
            // 例如,baseName = country, 那么可能存在的属性文件有 country_zh,
            // country_zh_CN 等等。
            final Vector names = calculateBundleNames(baseName, locale);
            // 初始化一个 Vector ,初始化后,之后对 Vector 的操作使用 addElement
            // 就会提高效率。
            Vector bundlesFound = new Vector(MAX_BUNDLES_SEARCHED);
        // if we found the root bundle and no other bundle names are needed
        // we can stop here. We don't need to search or load anything further.
            // 这个地方简单,英文注释也很清晰:如果找到了 root bundle,而且没有其他
            // 可能存在的 bundle 文件,那么就不用继续往下执行了。
            boolean foundInMainBranch = (root != NOTFOUND && names.size() == 0);
       
        // 如果没有 foundInMainBranch ,挺影响思维是不?
        // 其实上面那个变量叫 notFoundInMainBranch ,而这个地方写成
        // if(notFoundInMainBranch) 多好!
        if (!foundInMainBranch) {
          // 设置了 parent = root。这个 parent 到底干什么的还是没搞明白。
          parent = root;
          // 循环所有可能存在的 bundle 文件名字
          for (int i = 0; i < names.size(); i++) {
                bundleName = (String)names.elementAt(i);
                // 查找 bundle 对象。其实这个地方是我最不明白的,既然这个
                // names vector 是通过 locale 来
                // 生成的,为什么查找 bundle 的时候要使用 defaultLocale 呢??
                lookup = findBundle(loader, bundleName, defaultLocale, baseName, parent, NOTFOUND);
                // 把 lookup 对象放入到 bundlesFound 里。注意:不管是不是
                // 空都会放进去
                bundlesFound.addElement(lookup);
                if (lookup != null) {
                    // 如果 lookup 不是空,那么 parent = lookup, 然后
                    // 再循环... ... 有点迷糊了?没关系,我们假设自己
                    // 就是这个程序,来模拟运行一次就ok了!
                    // 假如 我们输入的 bundleName 是 country,而且
                    // cuntory.properties 存在,那么 root 就不是空,
                    // 那么 parent 也不是空。我们再假设 names 也不是空,
                    // 里面有两个成员,分别是 country_zh, country_zh_CN。
                    // 好,下面进入 这个 “for (int i = 0; i < names.size(); i++)”
                    // 循环。
                    // 先简单说一下 findBundle, findBundle 中会传入一个
                    // parent 参数,这个 parent 会设置成 返回结果的 parent。
                    // 晕了?好,继续。
                    // 第一次循环,假设存在 country_zh.properties。那么
                    // lookup 就不会是空的,
                    // 那么 lookup = country_zh.properties,
                    // parent = country_zh.properties,
                    // 同时 lookup.parent = country.properties。
                    // 第二次循环,假设存在 country_zh_CN.properties。
                    // 那么 lookup 就不会是空的。
                    // 那么 lookup = country_zh_CN.properties ,
                    // parent = country_zh_CN.properties ,
                    // 同时 lookup.parent = country_zh.properties,
                    // 如果我们再往上推溯,
                    // lookup.parent.parent = country.properties。
                    // 俺的头脑实在不发达,也只能推溯到两次循环了。所以,
                    // 在两次循环结束后,我们的 parent 变量成为什么样子了呢?
                    // parent = country_zh_CN.properties
                    // parent.parent = country_zh.properties
                    // parent.parent.parent = country.properties
                    // 怎么样,明白了吧?
                    parent = lookup;
                    foundInMainBranch = true;
                }
              }
            }
            // parent = root ?? 那上面的那些处理不是都白费了吗?
            // 没关系,别忘了 bundlesFound.addElement(lookup);
            parent = root;
            if (!foundInMainBranch) {
                //we didn't find anything on the main branch, so we do the fallback branch
                // 再一次计算可能存在的 bundle 文件,不过这次不是使用 locale,而是 defaultLocale
                final Vector fallbackNames = calculateBundleNames(baseName, defaultLocale);
                for (int i = 0; i < fallbackNames.size(); i++) {
                    bundleName = (String)fallbackNames.elementAt(i);
                    if (names.contains(bundleName)) {
                        //the fallback branch intersects the main branch so we can stop now.
                        break;
                    }
                    lookup = findBundle(loader, bundleName, defaultLocale, baseName, parent, NOTFOUND);
                    if (lookup != null) {
                        parent = lookup;
                    } else {
                        //propagate the parent to the child.  We can do this
                        //here because we are in the default path.
                        // 如果 lookup 等于空,那么就把 parent 作为
                        // bundleName 的缓存。为了形象点,我们
                        // 还是做一个模拟比较好。如果当前的环境是日语系统,
                        // 那么假设 fallbackNames 中有一个
                        // 成员,就是 country_ja ,而如果 country_ja.properties
                        // 不存在,那么就把 parent当成是
                        // 处理 country_ja 的对象。那么 parent 是什么呢?
                        // 如果只循环一次,那么 parent 就是 root,
                        // 如果循环多次呢?自己模拟看看吧 :-)
                        putBundleInCache(loader, bundleName, defaultLocale, parent);
                    }
                }
            }
            //propagate the inheritance/fallback down through the main branch
            // 终于到最后了,这个 propagate 是什么呢?
            parent = propagate(loader, names, bundlesFound, defaultLocale, parent);
        } catch (Exception e) {
            //We should never get here unless there has been a change
            //to the code that doesn't catch it's own exceptions.
            cleanUpConstructionList();
            throwMissingResourceException(baseName, locale);
        } catch (Error e) {
            //The only Error that can currently hit this code is a ThreadDeathError
            //but errors might be added in the future, so we'll play it safe and
            //clean up.
            cleanUpConstructionList();
            throw e;
        }
        if (parent == NOTFOUND) {
            throwMissingResourceException(baseName, locale);
        }
        return (ResourceBundle)parent;
    }





先来看看这个简单的 propagate 方法:



    private static Object propagate(ClassLoader loader, Vector names,
            Vector bundlesFound, Locale defaultLocale, Object parent) {

        // 又是一个循环
        for (int i = 0; i < names.size(); i++) {           
            final String bundleName = (String)names.elementAt(i);

            // 拿出与 bundleName对应的ResourceBundle对象
            final Object lookup = bundlesFound.elementAt(i);
            if (lookup == null) {

                // 这个代码应该很熟悉了,如果没有与 bundleName匹配的,那么就用 parent 对象。
                putBundleInCache(loader, bundleName, defaultLocale, parent);
            } else {
                parent = lookup;
            }
        }

        // 最后返回 parent。其实按照我的理解,这个变量叫

        // child 更形象。为什么呢?

        // 正如上边模拟的,如果 baseName = country,

        // names = [ country_zh, country_zh_CN ],

        // 而且三个属性文件都存在,那么这里返回的就是 country_zh_CN

        // 属性文件的 ResourceBundle 对象。

        return parent;
    }




好了,我们终于从整体上把这个长长的 getBundleImpl分析完了。现在我们再来分析一下其中的一些方法。


首先是 findBundleInCache 这个方法。



    private static Object findBundleInCache(ClassLoader loader, String bundleName,
            Locale defaultLocale) {
        //Synchronize access to cacheList, cacheKey, and underConstruction
        synchronized (cacheList) {

            // 构造一个 cacheKey
            cacheKey.setKeyValues(loader, bundleName, defaultLocale);

            // 根据 cacheKey 得到缓存的 ResourceBundle 对象
            Object result = cacheList.get(cacheKey);
            cacheKey.clear();
            return result;
        }
    }




这个方法没什么复杂的,接下来看看 findBundle 方法。



    private static Object findBundle(ClassLoader loader, String bundleName, Locale defaultLocale,
            String baseName, Object parent, final Object NOTFOUND) {
        Object result;
        synchronized (cacheList) {
            //check for bundle in cache

            // 首先也是从 cache 中取缓存对象。为什么这里还要再次从

            // cache 中取呢?赶紧再 看看 getBundleImpl 里调用

            // findBundle 的地方就知道了。有点乱,是不?

            // 为什么这里不用 findBundleInCache 方法呢?

            // 请向上看:synchronized (cacheList)

            // findBundleInCache里也用了 synchronized (cacheList),

            // 所以不能用那个方法了。

            // 确实够乱了。。。。

            cacheKey.setKeyValues(loader, bundleName, defaultLocale);
            result = cacheList.get(cacheKey);
            if (result != null) {
                cacheKey.clear();
                return result;
            }
            // check to see if some other thread is building this bundle.
            // Note that there is a rare chance that this thread is already
            // working on this bundle, and in the process getBundle was called
            // again, in which case we can't wait (4300693)

            // underConstruction 终于登场了!这是一个类的变量,之前咱们看到过。

            // 这个东西作用是什么呢?就是以 cacheKey 作为 key, Thread 作为 value。

            // 如果从 underConstruction中得到的 Thread 和 当前的 Thread 不同,

            // 那就说明别的线程正在构建这个 bundle 对象。

            // 可见编写这个方法的人苦心,如此细微的地方也都考虑到了。

            // 可是,可是,为什么 findBundleInCache方法中不用考虑这个东西呢??

            Thread builder = (Thread) underConstruction.get(cacheKey);
            boolean beingBuilt = (builder != null && builder != Thread.currentThread());
            //if some other thread is building the bundle...
            if (beingBuilt) {
                //while some other thread is building the bundle...
                while (beingBuilt) {
                    cacheKey.clear();
                    try {
                        //Wait until the bundle is complete
                        cacheList.wait();
                    } catch (InterruptedException e) {
                    }

                    // 不要以为 cacheKey 的设置是无用的,因为 cacheKey 是一个 static 变量,

                    // 所以别的线程很可能会在运行中的时候修改这个变量,所以我们必须设置。

                    // 真是复杂深奥的情况阿......想破了我的脑袋

                    cacheKey.setKeyValues(loader, bundleName, defaultLocale);
                    beingBuilt = underConstruction.containsKey(cacheKey);
                }
                //if someone constructed the bundle for us, return it

                // 这又是一个复杂的结局。如果其他线程已经在 cacheList 中存放了 bundle 对象,就

                // 返回这个缓存结果。简直就是在读一本推理小说。

                // 但是,往上看看,synchronized (cacheList) ,cacheList 不是已经同步了吗??

                // 这么说,在我锁住这个资源的时候,别的线程也能偷偷地修改这个资源。如果说别的线程

                // 在修改cacheList 的时候,我只能等待,那么上面的 while (beginBuilt) 又有什么用呢?

                // 嗯,看来到此处应该是玄幻小说了。

                result = cacheList.get(cacheKey);
                if (result != null) {
                    cacheKey.clear();
                    return result;
                }
            }
            //The bundle isn't in the cache, so we are now responsible for
            //loading it and adding it to the cache.

            // 你以为是玄幻的结束?错,这里又是另一个玄幻的开始。

            // 这里该往 underConstruction 里放东西了,告诉其他线程,我正在创建这个bundle对象,

            // 请你们使用 while (beingBuilt) {} 方式进行等待,虽然你们锁住了 cacheList ,但是我

            // 一样能修改.

            final Object key = cacheKey.clone();
            underConstruction.put(key, Thread.currentThread());
            //the bundle is removed from the cache by putBundleInCache
            cacheKey.clear();
        }

        //try loading the bundle via the class loader

        // 好了,好了,推理以及玄幻部分结束,我们回到了真实的世界。

        // 使用 loadBundle 方法载入 bundle 资源。

        result = loadBundle(loader, bundleName, defaultLocale);
        if (result != null) {
            // check whether we're still responsible for construction -
            // a recursive call to getBundle might have handled it (4300693)
            boolean constructing;
            synchronized (cacheList) {
                cacheKey.setKeyValues(loader, bundleName, defaultLocale);

                // 如果 underConstruction 中得到的线程 = 当前的线程,说明不是别的线程构造的。

                // 可是,如果不是别的线程构造的,不是一直会在 while (beingBuilt) {}里呆着吗?

                // 我冥思苦想,终于明白:在那个玄幻的部分中,别的线程没有构造 bundle 对象,等到这里的

                // 时候,恰好别的线程开始构造了。作者的思维真是严密呀!

                constructing = underConstruction.get(cacheKey) == Thread.currentThread();
                cacheKey.clear();
            }
            if (constructing) {
                // set the bundle's parent and put it in the cache

                // 如果是当前线程在构造的话,那么就设置 parent,以及放入到 cache 中。
                final ResourceBundle bundle = (ResourceBundle)result;
                if (parent != NOTFOUND && bundle.parent == null) {
                    bundle.setParent((ResourceBundle) parent);
                }
                bundle.setLocale(baseName, bundleName);
                putBundleInCache(loader, bundleName, defaultLocale, result);
            }
        }

        // 总结一下这个方法,在这个方法里,作者给我们展示了多线程下使用同一个资源时的并行处理方式,

        // 运用了推理,以及玄幻的方式给我们展示了一种新的思路。

        return result;
    }




继续来看看其中的 putBundleInCache 方法。



    private static void putBundleInCache(ClassLoader loader, String bundleName,
            Locale defaultLocale, Object value) {
        //we use a static shared cacheKey but we use the lock in cacheList since
        //the key is only used to interact with cacheList.
        synchronized (cacheList) {
            cacheKey.setKeyValues(loader, bundleName, defaultLocale);
            cacheList.put(cacheKey.clone(), value);

            underConstruction.remove(cacheKey);
            cacheKey.clear();
            //notify waiters that we're done constructing the bundle

            // notify 谁呢?整个代码中也没见谁在 wait。难不成有些线程会自动 wait ???

            // 玄幻,又见玄幻

            cacheList.notifyAll();
        }
    }




现在大脑已经进入迷糊状态了,赶紧清醒清醒,看看 loadBundle 是怎么回事。



    private static Object loadBundle(final ClassLoader loader, String bundleName, Locale defaultLocale) {
        // Search for class file using class loader

        // 这部分是为了载入 class 用的。其实 bundle 不仅仅可以是 .properties 文件,使用 class 也可以。

        // 之前一直说的都是 .properties 文件,是因为大部分人都用这种方式。

         try {
            Class bundleClass;
            if (loader != null) {
                bundleClass = loader.loadClass(bundleName);
            } else {
                bundleClass = Class.forName(bundleName);
            }
            if (ResourceBundle.class.isAssignableFrom(bundleClass)) {
                Object myBundle = bundleClass.newInstance();
                // Creating the instance may have triggered a recursive call to getBundle,
                // in which case the bundle created by the recursive call would be in the
                // cache now (4300693). For consistency, we'd then return the bundle from the cache.
                Object otherBundle = findBundleInCache(loader, bundleName, defaultLocale);
                if (otherBundle != null) {
                    return otherBundle;
                } else {
                    return myBundle;
                }
            }
        } catch (Exception e) {

           // 顺便多一句,如果我们使用的是 .properties文件,那么 100% 这里会出 Exception。

           // 既然判断 .properties 文件存在不存在更容易,为什么不把下面的载入文件放到一开始呢?

        } catch (LinkageError e) {
        }

        // Next search for a Properties file.

        // 这里就是我们熟悉的 .properties 文件了
        final String resName = bundleName.replace('.', '/') + ".properties";

        // 安全检查。好多同学写的读取文件从来不干这个
        InputStream stream = (InputStream)java.security.AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                public Object run() {
                    if (loader != null) {
                        return loader.getResourceAsStream(resName);
                    } else {
                        return ClassLoader.getSystemResourceAsStream(resName);
                    }
                }
            }
        );

        if (stream != null) {
            // make sure it is buffered
            stream = new java.io.BufferedInputStream(stream);
            try {

                // PropertyResourceBundle,ResourceBundle 的一个子类。
                return new PropertyResourceBundle(stream);
            } catch (Exception e) {
            } finally {
                try {
                    stream.close();
                } catch (Exception e) {
                    // to avoid propagating an IOException back into the caller
                    // (I'm assuming this is never going to happen, and if it does,
                    // I'm obeying the precedent of swallowing exceptions set by the
                    // existing code above)
                }
            }
        }
        return null;
    }




呼呼,好了,至此我们已经把 ResourceBundle 的主要内容分析完了!根据目前的代码,我们反向推理一下需求:

1、ResourceBundle可以载入 class 或者 .properties,而且可以传递 ClassLoader 来进行载入。

2、具有层次的功能。也就是 country.properties, country_zh.properties, country_zh_CN.properties 的读取方式。

    优先读取最底级的,如果底级不存在,那么依次向上寻找。

3、具有缓存的功能。根据 ClassLoader, bundleName, locale 三个条件来缓存。


此外还具有一些普通人难以掌握和理解的东西:

1、多线程情况下构造 bundle 对象的处理。

2、多线程的并发处理。

(此为玄幻部分,如果不想写玄幻小说,无需钻研)


ResourceBundle代码摆在那,需求我们也能根据代码反推出来,那么解决热加载的方案呢?

很多人心中已经有数了,下次再一起讨论讨论。



你可能感兴趣的:(spring,thread,多线程,cache,Security)