北屋教程网

专注编程知识分享,从入门到精通的编程学习平台

Jdk国际化中的ResourceBundle原理

ResourceBundle 是 Java 中用于国际化(i18n)和本地化(l10n) 的核心类,它允许应用程序根据不同的语言和地区动态加载外部资源(如文本、图像等),而无需修改代码。

本文章中介绍如何使用,并且会从源码的角度分析它是如何工作的。

    public static void main(String[] args) {
        // 按语言进行加载对应的resourceBundle
        ResourceBundle bundle = ResourceBundle.getBundle("i18n.res", Locale.US);
        // 从对应的resourceBundle中获取对应的key
        System.out.println(bundle.getString("say.hello"));
    }

源码分析

加载语言资源

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

ResourceBundle.getBundle 调用了 方法 getBundleImpl

private static ResourceBundle getBundleImpl(String baseName, Locale locale,
                                                ClassLoader loader, Control control) {
        if (locale == null || control == null) {
            throw new NullPointerException();
        }
        // 使用baseName, locale, loader进行创建缓存key
        CacheKey cacheKey = new CacheKey(baseName, locale, loader);
        ResourceBundle bundle = null;

        // 从缓存中获取数据
        BundleReference bundleRef = cacheList.get(cacheKey);
        if (bundleRef != null) {
            bundle = bundleRef.get();
            bundleRef = null;
        }

        // 这里的bundle有可能空,因为BundleReference是一个软引用
        if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
            return bundle;
        }

        // No valid bundle was found in the cache, so we need to load the
        // resource bundle and its parents.

        boolean isKnownControl = (control == Control.INSTANCE) ||
                                   (control instanceof SingleFormatControl);
        // 当前支持的格式类型:class 与 properties
        // properties文件就是上面的res.properties文件
        List<String> formats = control.getFormats(baseName);
        if (!isKnownControl && !checkList(formats)) {
            throw new IllegalArgumentException("Invalid Control: getFormats");
        }
        // 依据当前传入的语言或是已经配置的回退的语言(如系统语言)
        ResourceBundle baseBundle = null;
        for (Locale targetLocale = locale;
             targetLocale != null;
             targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
            // 从当前的语言获取候选的语言
            // 如当前传入的是简体, 则会candidateLocales的内容为
            // [zh_CN_#Hans, zh__#Hans, zh_CN, zh, ]
            // 后面是前面的父bundle
            List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
            if (!isKnownControl && !checkList(candidateLocales)) {
                throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
            }
            // candidateLocales 倒序加载数据
            // 当前以 PropertyResourceBundle 返回
            bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle);

            // If the loaded bundle is the base bundle and exactly for the
            // requested locale or the only candidate locale, then take the
            // bundle as the resulting one. If the loaded bundle is the base
            // bundle, it's put on hold until we finish processing all
            // fallback locales.
            if (isValidBundle(bundle)) {
                boolean isBaseBundle = Locale.ROOT.equals(bundle.locale);
                if (!isBaseBundle || bundle.locale.equals(locale)
                    || (candidateLocales.size() == 1
                        && bundle.locale.equals(candidateLocales.get(0)))) {
                    break;
                }

                // If the base bundle has been loaded, keep the reference in
                // baseBundle so that we can avoid any redundant loading in case
                // the control specify not to cache bundles.
                if (isBaseBundle && baseBundle == null) {
                    baseBundle = bundle;
                }
            }
        }

        if (bundle == null) {
            if (baseBundle == null) {
                throwMissingResourceException(baseName, locale, cacheKey.getCause());
            }
            bundle = baseBundle;
        }

        keepAlive(loader);
        return bundle;
    }

这里findBundle 方法的逻辑,可以深入再来看下

private static ResourceBundle findBundle(CacheKey cacheKey,
                                             List<Locale> candidateLocales,
                                             List<String> formats,
                                             int index,
                                             Control control,
                                             ResourceBundle baseBundle) {
        Locale targetLocale = candidateLocales.get(index);
        ResourceBundle parent = null;
        // 如果不是candidateLocales最后一个,则递归调用findBundle
        // 也就是会以链表的形式,[zh_CN_#Hans, zh__#Hans, zh_CN, zh, ] 注意最后一个空的数据。
        // 进行数据返回
        // 最终的bundle 是 zh_CN_#Hans所属的bundle,parent会指向zh__#Hans所属的bundle
        if (index != candidateLocales.size() - 1) {
            parent = findBundle(cacheKey, candidateLocales, formats, index + 1,
                                control, baseBundle);
        } else if (baseBundle != null && Locale.ROOT.equals(targetLocale)) {
            return baseBundle;
        }

        // Before we do the real loading work, see whether we need to
        // do some housekeeping: If references to class loaders or
        // resource bundles have been nulled out, remove all related
        // information from the cache.
        Object ref;
        while ((ref = referenceQueue.poll()) != null) {
            cacheList.remove(((CacheKeyReference)ref).getCacheKey());
        }

        // flag indicating the resource bundle has expired in the cache
        boolean expiredBundle = false;

        // First, look up the cache to see if it's in the cache, without
        // attempting to load bundle.
        cacheKey.setLocale(targetLocale);
        // 从缓存中获取bundle
        ResourceBundle bundle = findBundleInCache(cacheKey, control);
        if (isValidBundle(bundle)) {
            expiredBundle = bundle.expired;
            if (!expiredBundle) {
                // If its parent is the one asked for by the candidate
                // locales (the runtime lookup path), we can take the cached
                // one. (If it's not identical, then we'd have to check the
                // parent's parents to be consistent with what's been
                // requested.)
                if (bundle.parent == parent) {
                    return bundle;
                }
                // Otherwise, remove the cached one since we can't keep
                // the same bundles having different parents.
                BundleReference bundleRef = cacheList.get(cacheKey);
                if (bundleRef != null && bundleRef.get() == bundle) {
                    cacheList.remove(cacheKey, bundleRef);
                }
            }
        }
        // 缓存中没有,则从资源中加载
        if (bundle != NONEXISTENT_BUNDLE) {
            CacheKey constKey = (CacheKey) cacheKey.clone();

            try {
                bundle = loadBundle(cacheKey, formats, control, expiredBundle);
                // 加载成功,则放入缓存
                if (bundle != null) {
                    if (bundle.parent == null) {
                        bundle.setParent(parent);
                    }
                    bundle.locale = targetLocale;
                    bundle = putBundleInCache(cacheKey, bundle, control);
                    return bundle;
                }

                // 如果加载没有资源,则放入一个空对象,避免重复加载
                putBundleInCache(cacheKey, NONEXISTENT_BUNDLE, control);
            } finally {
                if (constKey.getCause() instanceof InterruptedException) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        return parent;
    }

findBundle的逻辑:按candidateLocales进行倒序加载数据,并且后面元素是前面元素的parent指向的数据。

获取资源

bundle.getString("say.hello")
    public final String getString(String key) {
        return (String) getObject(key);
    }

    public final Object getObject(String key) {
        // 该方法可以被重载,可以看PropertyResourceBundle
        Object obj = handleGetObject(key);
        // 如果没有
        if (obj == null) {
            // 则从父bundle中找
            // 按以上 [zh_CN_#Hans, zh__#Hans, zh_CN, zh, ]的顺序进行查找
            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;
    }
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言