暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

JDK的国际化(i18n)原来得这么玩

开发自由行 2021-06-29
1188

备注:JDK版本:1.8.0,系统环境:Ubuntu16.04 LTS


借助JDK进行国际化(多语言适配)操作,通常是在类路径(classpath)下定义不同语言的属性文件,以语言代码为后缀,将配置文件名称与语言代码以下划线的形式进行连接,比如简体中文的语言代码是zh_CN,自定义的属性文件名称为xxx_zh_CN.properties,然后借助java.util.ResourceBundle类自动基于不同的语言代码进行加载配置文件,以获取不同语言的配置信息。

下面我们先结合一个案例来引出使用ResourceBundle需要注意的点。

1. ResourceBundle的使用案例

1.1 语言代码的获取

JDKjava.util.Locale类,描述了常用的语言代码对应的英文编码信息,可以借助这个类,快速获取对应各语言代码信息

    package com.zhru.wechat.jdk.international;


    import java.util.Locale;
    import java.util.stream.Stream;


    /**
    * Desp: 获取各个语言及其代码信息
    * 2019-10-25 21:35
    * Created by zhru.
    */
    public class LocalInformationsDemo {


    public static void main(String[] args) {
    Stream.of(Locale.getAvailableLocales()).forEach(local ->
    System.out.println(local.getDisplayName() + ":" + local)
    );
    }
    }

    得到的结果如下:

      ......
      西班牙文 (波多黎哥):es_PR
      越南文 (越南):vi_VN
      英文 (美国):en_US
      中文 (中国):zh_CN
      日文 (日本):ja_JP
      德文 (希腊):de_GR
      塞尔维亚文 (拉丁文,塞尔维亚):sr_RS_#Latn
      希伯来文:iw
      英文 (印度):en_IN
      阿拉伯文 (黎巴嫩):ar_LB
      西班牙文 (尼加拉瓜):es_NI
      中文:zh
      ......

      1.2 定义属性文件

      在系统类路径下定义不同语言的属性文件(UTF-8编码

        ## myconfig_zh_CN.properties


        address=北京市
          ## myconfig_es_US.properties


          address=BeiJing Province

          1.3 借助ResourceBundle进行加载

            package com.zhru.wechat.jdk.international;


            import java.util.Locale;
            import java.util.ResourceBundle;


            /**
            * Desp: {@link ResourceBundle}加载配置信息
            * 2019-10-25 21:43
            * Created by zhru.
            */
            public class ResourceBundleDemo {


            public static void main(String[] args) {


            /**
            * 以en_US语言编码进行加载配置
            */
            ResourceBundle bundle = ResourceBundle.getBundle("myconfig", Locale.US);
            String address = bundle.getString("address");
            System.out.println("当前的语言编码是:" + Locale.US
            + ",获取的address值:" + address);


            /**
            * 以zh_CN语言编码进行加载配置
            */
            bundle = ResourceBundle.getBundle("myconfig", Locale.SIMPLIFIED_CHINESE);
            address = bundle.getString("address");
            System.out.println("当前的语言编码是:" + Locale.SIMPLIFIED_CHINESE
            + ",获取的address值:" + address);
            }


            }


            得到的结果如下:

              /usr/lib/jdk1.8.0_171/bin/java
              当前的语言编码是:en_US,获取的address值:BeiJing Province
              当前的语言编码是:zh_CN,获取的address值:北京市


              Process finished with exit code 0

              很明显,我们发现,即便使用了JDK提供的ResourceBundle的,对中文的获取依然会出现乱码的现象。


              2. ResourceBundle加载中文配置乱码的原因分析

              一切从代码出发,我们查看JDK源代码,一探究竟:


              1)java.util.ResourceBundle

                ......


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


                private static ResourceBundle getBundleImpl(String baseName, Locale locale,
                ClassLoader loader, Control control) {
                    // 检查locale和control信息,control接下来会讲述
                if (locale == null || control == null) {
                throw new NullPointerException();
                }


                   // 初始化cacheKey,用于从缓存中查找是否有相应的ResourceBundle
                   // 在bundle加载的过程中baseName和classloader都不能变化
                   // 在使用这个变量之前locale必须已经设置
                CacheKey cacheKey = new CacheKey(baseName, locale, loader);
                ResourceBundle bundle = null;


                    // 从缓存中查找
                BundleReference bundleRef = cacheList.get(cacheKey);
                if (bundleRef != null) {
                bundle = bundleRef.get();
                bundleRef = null;
                }


                   // 校验bundle,通过直接返回
                if (isValidBundle(bundle) && hasValidParentChain(bundle)) {
                return bundle;
                }


                // 缓存中没有有效的bundle,我们需要自行加载
                boolean isKnownControl = (control == Control.INSTANCE) ||
                (control instanceof SingleFormatControl);
                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)) {
                List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
                if (!isKnownControl && !checkList(candidateLocales)) {
                throw new IllegalArgumentException("Invalid Control: getCandidateLocales");
                }
                            // 获取bundle,需要关注的方法
                            bundle = findBundle(cacheKey, candidateLocales, formats, 0, control, baseBundle);
                            ......
                }
                return bundle;
                }


                /**
                * 查找Bundle
                */
                private static ResourceBundle findBundle(CacheKey cacheKey,
                List<Locale> candidateLocales,
                List<String> formats,
                int index,
                Control control,
                                                             ResourceBundle baseBundle) {


                ......
                    // 先从缓存中查找
                ResourceBundle bundle = findBundleInCache(cacheKey, control);
                if (isValidBundle(bundle)) {
                expiredBundle = bundle.expired;
                if (!expiredBundle) {
                                ......
                }
                }


                if (bundle != NONEXISTENT_BUNDLE) {
                CacheKey constKey = (CacheKey) cacheKey.clone();


                try {
                // 加载bundle
                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;
                }


                /**
                 * 加载Bundle
                */     
                private static ResourceBundle loadBundle(CacheKey cacheKey,
                List<String> formats,
                Control control,
                boolean reload) {


                   // 加载bundle,获取locale信息
                Locale targetLocale = cacheKey.getLocale();


                ResourceBundle bundle = null;
                int size = formats.size();
                for (int i = 0; i < size; i++) {
                String format = formats.get(i);
                try {
                // 借助control生成bundle,核心方法
                bundle = control.newBundle(cacheKey.getName(), targetLocale, format,
                cacheKey.getLoader(), reload);
                            } catch (LinkageError error) {
                cacheKey.setCause(error);
                } catch (Exception cause) {
                cacheKey.setCause(cause);
                }
                if (bundle != null) {
                            // 设置cacheKey到format中,以便下次加载使用
                cacheKey.setFormat(format);
                bundle.name = cacheKey.getName();
                bundle.locale = targetLocale;


                bundle.expired = false;
                break;
                }
                }


                return bundle;
                }
                ......

                上面的代码我们可以发现,真正读取配置文件信息的类实际上是java.util.ResourceBundle.Control.Control对象,通过调用java.util.ResourceBundle.Control#newBundle()方法,加载配置文件信息。


                2) java.util.ResourceBundle.Control.java

                  /** 
                    * 加载配置文件
                    **/
                  public ResourceBundle newBundle(String baseName, Locale locale, String format,
                  ClassLoader loader, boolean reload)
                  throws IllegalAccessException, InstantiationException, IOException
                  {
                  // 基于locale信息、baseName,生成bundleName
                  String bundleName = toBundleName(baseName, locale);
                  ResourceBundle bundle = null;
                  if (format.equals("java.class")) {
                  ......
                  } else if (format.equals("java.properties")) {
                  // 如果是包路径下的,这个可以将包路径变成文件路径
                  // 比如com/zhru/wechat/jdk/international/myconfig.properties
                  final String resourceName = toResourceName0(bundleName, "properties");
                  if (resourceName == null) {
                  return bundle;
                  }
                  final ClassLoader classLoader = loader;
                  final boolean reloadFlag = reload;
                  InputStream stream = null;
                  try {
                  stream = AccessController.doPrivileged(
                  new PrivilegedExceptionAction<InputStream>() {
                  public InputStream run() throws IOException {
                  InputStream is = null;
                  if (reloadFlag) {
                  // 加载配置文件
                  .......
                  } else {
                  // 加载配置文件
                  is = classLoader.getResourceAsStream(resourceName);
                  }
                  return is;
                  }
                  });
                  } catch (PrivilegedActionException e) {
                  throw (IOException) e.getException();
                  }
                  if (stream != null) {
                  try {
                  // PropertyResourceBundle加载从配置文件中读取的字节流信息
                  bundle = new PropertyResourceBundle(stream);
                  } finally {
                  stream.close();
                  }
                  }
                  } else {
                  throw new IllegalArgumentException("unknown format: " + format);
                  }
                  return bundle;
                  }


                  代码跟到这里我们发现,读取类路径下的properties文件获取的字节流信息最终会被java.util.PropertyResourceBundle进行加载,进入这个类:


                  3)java.util.PropertyResourceBundle

                  依次跟踪PropertyResourceBundle的源代码

                    // java.util.PropertyResourceBundle 


                    /**
                     *  从流信息{@link java.io.InputStream
                     *  InputStream}中创建一个PropertyResourceBundle.
                     *  这个配置文件必须是ISO-8859-1的编码格式
                    */
                    @SuppressWarnings({"unchecked", "rawtypes"})
                    public PropertyResourceBundle(InputStream stream) throws IOException {
                        // 实际调用的是java.util.Properties加载配置信息
                    Properties properties = new Properties();
                    properties.load(stream);
                    lookup = new HashMap(properties);
                    }  


                    /**
                    * Creates a property resource bundle from a {@link java.io.Reader
                    * Reader}. Unlike the constructor
                    * {@link #PropertyResourceBundle(java.io.InputStream) PropertyResourceBundle(InputStream)},
                    * there is no limitation as to the encoding of the input property file.
                    *
                    * @param reader a Reader that represents a property file to
                    * read from.
                    * @throws IOException if an I/O error occurs
                    * @throws NullPointerException if <code>reader</code> is null
                    * @throws IllegalArgumentException if a malformed Unicode escape sequence appears
                    * from {@code reader}.
                    * @since 1.6
                    */
                    public PropertyResourceBundle(Reader reader) throws IOException {
                    Properties properties = new Properties();
                    properties.load(reader);
                    lookup = new HashMap(properties);
                    }


                    java.util.PropertyResourceBundle#PropertyResourceBundle(java.io.InputStream)的注释信息我们可以发现,虽然我们创建的properties文件的编码格式的UTF-8,但是PropertyResourceBundle在加载的时候,是以ISO-8859-1的格式进行读取,因此,必然会出现中文乱码的问题。


                    3. ResourceBundle加载中文配置乱码的解决方案


                    3.1 基于ResourceBundle.Control解决方案

                    3.1.1 基于ResourceBundle.Control解决方案的实现分析

                    • java.util.PropertyResourceBundle的源代码中我们可以发现,java.util.PropertyResourceBundle真正是调用的java.util.Properties类进行properties文件的读取,而java.util.Properties同时支持字节流(InputStream)和字符流(Reader)形式的加载,而字符流是可以手动的设置编码格式。


                    • 从上一小节分析的的源代码中,我们也可以清楚的看到,java.util.PropertyResourceBundle对象,不仅可以通过传递字节流创建,也可以通过传递字符流创建。


                    • 基于java.util.ResourceBundle代码的跟踪,我们知道java.util.ResourceBundle加载配置文件最终会调用java.util.ResourceBundle.Control#newBundle()方法,而java.util.ResourceBundle.Control对象是在java.util.ResourceBundle#getBundleImpl()方法调用的时候显示传递。因此,自然可以继承java.util.ResourceBundle.Control类,自定义java.util.ResourceBundle.Control,并在Control.newBundle()方法中将加载的配置文件信息以字符流的形式传递给java.util.PropertyResourceBundle中即可。

                    我们先来看下java.util.ResourceBundle重载的getBundle()方法

                      // java.util.ResourceBundle 


                      /**
                       * 只需要baseName
                       * @param baseName 
                       */
                      @CallerSensitive
                      public static final ResourceBundle getBundle(String baseName)
                      {
                      return getBundleImpl(baseName, Locale.getDefault(),
                      getLoader(Reflection.getCallerClass()),
                      getDefaultControl(baseName));
                      }


                      /**
                       * 需要baseName和control
                       * @param baseName 
                       * @param control 
                      */
                      @CallerSensitive
                      public static final ResourceBundle getBundle(String baseName,
                      Control control) {
                      return getBundleImpl(baseName, Locale.getDefault(),
                      getLoader(Reflection.getCallerClass()),
                      control);
                      }
                       
                      /**
                      * 需要baseName和locale
                      * @param baseName
                       * @param locale
                      */
                      @CallerSensitive
                      public static final ResourceBundle getBundle(String baseName,
                      Locale locale)
                      {
                      return getBundleImpl(baseName, locale,
                      getLoader(Reflection.getCallerClass()),
                      getDefaultControl(baseName));
                      }  


                      /**
                       * 需要baseName、locale和control
                      * @param baseName
                      * @param locale
                      * @param control
                      */
                      @CallerSensitive
                      public static final ResourceBundle getBundle(String baseName, Locale targetLocale,
                      Control control) {
                      return getBundleImpl(baseName, targetLocale,
                      getLoader(Reflection.getCallerClass()),
                      control);
                      }


                      /**
                       * 需要baseName、locale和classLoader
                      * @param baseName
                       * @param locale
                       * @param classLoader 
                      */
                      public static ResourceBundle getBundle(String baseName, Locale locale,
                      ClassLoader loader)
                      {
                      if (loader == null) {
                      throw new NullPointerException();
                      }
                      return getBundleImpl(baseName, locale, loader, getDefaultControl(baseName));
                      }


                      /**
                       * 需要baseName、locale、classLoader和control
                      * @param baseName
                      * @param locale
                      * @param classLoader
                      * @param control
                      */
                      public static ResourceBundle getBundle(String baseName, Locale targetLocale,
                      ClassLoader loader, Control control) {
                      if (loader == null || control == null) {
                      throw new NullPointerException();
                      }
                      return getBundleImpl(baseName, targetLocale, loader, control);
                      }

                      从上面的代码中,我们可以得出这样的结论:当调用java.util.ResourceBundle#getBundle()方法时,如果提供了control对象则以提供的为准,否则用默认的ResourceBundle.Control对象。

                      3.1.2 基于ResourceBundle.Control解决方案的代码实现

                      可以模仿java.util.ResourceBundle.Control类,实现自定义的ResourceBundle.Control类,并复写它的newBundle()方法:

                        /** 
                        * Desp: 自定义 {@link ResourceBundle.Control}
                        * 2019-10-27 14:31
                        * Created by zhru.
                        */
                        public class CustomControl extends ResourceBundle.Control {
                            // properties文件读取时的编码格式
                            private String encoding;
                            
                        public CustomControl() {
                        this.encoding = "UTF-8";
                        }


                        public CustomControl(String encoding) {
                        this.encoding = encoding;
                        }


                        @Override
                        public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException, InstantiationException, IOException {
                        String bundleName = toBundleName(baseName, locale);
                        ResourceBundle bundle = null;
                        if (format.equals("java.class")) {
                        ......
                        } else if (format.equals("java.properties")) {
                        final String resourceName = toResourceName0(bundleName, "properties");
                        if (resourceName == null) {
                        return bundle;
                        }
                        final ClassLoader classLoader = loader;
                        final boolean reloadFlag = reload;
                        InputStream stream = null;
                        try {
                        stream = ......
                        } catch (PrivilegedActionException e) {
                        throw (IOException) e.getException();
                        }
                        if (stream != null) {
                        // 若encoding没有设置或者设置为null,则默认为UTF-8
                        try {
                        if (this.encoding == null) {
                        this.encoding = "UTF-8";
                        }


                        // 以字符流的形式进行加载
                        bundle = new PropertyResourceBundle(
                        new InputStreamReader(stream, encoding));
                        } finally {
                        stream.close();
                        }
                        }
                        } else {
                        throw new IllegalArgumentException("unknown format: " + format);
                        }
                        return bundle;
                        }


                        private String toResourceName0(String bundleName, String suffix) {
                        // application protocol check
                        if (bundleName.contains("://")) {
                        return null;
                        } else {
                        return toResourceName(bundleName, suffix);
                        }
                        }
                        }

                        核心的逻辑是在获取到properties文件的字节流信息后,将字节流转换成字符流,用于创建java.util.PropertyResourceBundle对象。

                        3.1.3 基于ResourceBundle.Control解决方案的测试

                          package com.zhru.wechat.jdk.international;


                          import com.zhru.wechat.jdk.international.reslove.CustomControl;


                          import java.util.Locale;
                          import java.util.ResourceBundle;


                          /**
                           * Desp: 使用自定义的{@link java.util.ResourceBundle.Control}测试国际化操作
                           * 2019-10-27 14:38 
                           * Created by zhru. 
                          */
                          public class ResourceBundleUseCustonControlDemo {


                          public static void main(String[] args) {
                                  // 初始化Control对象
                          ResourceBundle.Control control = new CustomControl("UTF-8");


                          /**
                                   * 以en_US语言编码进行加载配置
                                   * 使用自定义的ResourceBundle.Control对象  
                                   */
                          ResourceBundle bundle = ResourceBundle.getBundle("myconfig",
                                  Locale.US, control);
                          String address = bundle.getString("address");
                          System.out.println("当前的语言编码是:" + Locale.US
                          + ",获取的address值:" + address);


                          /**
                                   * 以zh_CN语言编码进行加载配置
                                   * 使用自定义的ResourceBundle.Control对象  
                          */
                          bundle = ResourceBundle.getBundle("myconfig",
                          Locale.SIMPLIFIED_CHINESE, control);
                          address = bundle.getString("address");
                          System.out.println("当前的语言编码是:" + Locale.SIMPLIFIED_CHINESE
                          + ",获取的address值:" + address);
                          }
                          }

                          使用自定义的ResourceBundle.Control对象测试,运行结果:

                            /usr/lib/jdk1.8.0_171/bin/java
                            当前的语言编码是:en_US,获取的address值:BeiJing Province
                            当前的语言编码是:zh_CN,获取的address值:北京市


                            Process finished with exit code 0

                            从上面的运行结果可以看出来,已经解决了ResourceBundle加载配置文件中文乱码的问题,但是,上面的逻辑存在一个很严重的不足:每次调用ResourceBundle.getBundle()方法的时候,必须显示的传递自定义的ResourceBundle.Control对象,增大了编程的工作量,代码的移植性也比较低,是否有更好的方式去解决ResourceBundle加载中文乱码的问题呢,是否可以直接调用ResourceBundle.getBundle()方法的时候不传递ResourceBundle.Control对象呢?

                            既然我都这么说了,自然是有调用时更简便的方式^


                            3.2 基于java.util.spi.ResourceBundleControlProvider解决方案


                            备注:jdk版本需要在1.8以上

                            3.2.1 基于java.util.spi.ResourceBundleControlProvider解决方案分析


                            从 3.1.1 小节的代码中可以看出来,java.util.ResourceBundle重载了多个getBundle()方法,最简单的getBundle()方法如下面的代码所示:

                              // java.util.ResourceBundle


                              /**
                               * 只需要baseName
                               * control对象由java.util.ResourceBundle#getDefaultControl()方法获取 
                              * @param baseName
                              */
                              @CallerSensitive
                              public static final ResourceBundle getBundle(String baseName)
                              {
                              return getBundleImpl(baseName, Locale.getDefault(),
                              getLoader(Reflection.getCallerClass()),
                              getDefaultControl(baseName));
                              }

                              从上面的代码,我们可以发现,当没有提供control对象的时候,实际调用的是java.util.ResourceBundle#getDefaultControl(String)方法获取control对象

                                // java.util.ResourceBundle 


                                private static Control getDefaultControl(String baseName) {
                                if (providers != null) {
                                for (ResourceBundleControlProvider provider : providers) {
                                Control control = provider.getControl(baseName);
                                if (control != null) {
                                return control;
                                }
                                }
                                }
                                return Control.INSTANCE;
                                }

                                遍历providers对象,那么providers对象是从哪里来的呢?我们继续看代码: 

                                  // java.util.ResourceBundle 


                                  private static final List<ResourceBundleControlProvider> providers;


                                  static {
                                  List<ResourceBundleControlProvider> list = null;
                                         // 借助SPI,从ServiceLoader中加载ResourceBundleControlProvider
                                  ServiceLoader<ResourceBundleControlProvider> serviceLoaders
                                  = ServiceLoader.loadInstalled(ResourceBundleControlProvider.class);
                                  // 遍历SPI获取的ResourceBundleControlProvider集合,
                                  // 添加到List<ResourceBundleControlProvider>集合
                                  for (ResourceBundleControlProvider provider : serviceLoaders) {
                                  if (list == null) {
                                  list = new ArrayList<>();
                                  }
                                  list.add(provider);
                                  }
                                        // 将List<ResourceBundleControlProvider>集合赋值给providers
                                  providers = list;
                                  }

                                  代码已经注释的很清楚,java.util.spi.ResourceBundleControlProvider是借助于JDK的SPI机制进行加载的(关于SPI机制,以后再讲,不了解的同学可以google学习)

                                  因此,借助SPI机制,只需要复写(继承) java.util.spi.ResourceBundleControlProvider类,然后在类路径下的META-INF/services目录下进行注册即可,这样java.util.ServiceLoader类就可以自动加载。


                                  注意点:java.util.ResourceBundle的代码中ServiceLoader是调用的loadInstalled()方法,我们来看java.util.ServiceLoader这部分的代码:
                                    // java.util.ServiceLoader  
                                    /**
                                     * 使用ExtClassLoader加载Service 
                                     *
                                     * 这个方法先找到ExtClassLoader,然后调用
                                     * ServiceLoader.load(service, extClassLoader)
                                    *
                                     * 如果没有找到ExtClassLoader,那么使用System ClassLoader加载,
                                     * 如果没有找到 System ClassLoader,则使用Bootstrap ClassLoader
                                    *
                                     * 这个方法只查找已经在JVM虚拟机中进行过装载的Service,应用程序
                                     * classpath路径下的将被忽略
                                     * @param  <S> the class of the service type
                                    * @param service
                                     *         The interface or abstract class representing the service
                                    * @return A new service loader
                                    */
                                    public static <S> ServiceLoader<S> loadInstalled(Class<S> service) {
                                    ClassLoader cl = ClassLoader.getSystemClassLoader();
                                    ClassLoader prev = null;
                                    while (cl != null) {
                                    prev = cl;
                                    cl = cl.getParent();
                                    }
                                    return ServiceLoader.load(service, prev);
                                    }


                                    从上面的代码注释中,看出java.util.ServiceLoader#loadInstalled(Class<S> service)方法不会查找当前应用classpath路径下的Service服务,而是优先借助ExtClassLoader进行加载,ExtClassLoader会查找jdk扩展目录(jdk安装目录/jre/lib/ext)下的jar文件,加载到JVM中。因此,为了能够让java.util.ResourceBundle使用自定义的ResourceBundleProvider,可以将自定义的ResourceBundleProvider类所在的应用编译后的jar包复制在jdk安装目录下相应的路径。
                                    比如我的jdk安装路径是:/usr/lib/jdk1.8.0_171,将jar移动到/usr/lib/jdk1.8.0_171/jre/lib/ext/目录下

                                    3.2.2 基于java.util.spi.ResourceBundleControlProvider解决方案实现
                                    1)定义java.util.spi.ResourceBundleControlProvider的实现类
                                      package com.zhru.wechat.jdk.international.reslove.spi;


                                      import com.zhru.wechat.jdk.international.reslove.control.CustomControl;


                                      import java.util.ResourceBundle;
                                      import java.util.spi.ResourceBundleControlProvider;


                                      /**
                                      * Desp: 自定义的{@link java.util.spi.ResourceBundleControlProvider}
                                      * 2019-10-27 16:44
                                      * Created by zhru.
                                      */
                                      public class CustomResourceBundleControlProvider implements ResourceBundleControlProvider {


                                      @Override
                                      public ResourceBundle.Control getControl(String baseName) {
                                              // file.encoding作为入参传递进来
                                              String encoding = System.getProperty("file.encoding");
                                      if (encoding == null) {
                                      encoding = "UTF-8";
                                      }


                                      return new CustomControl(encoding);
                                      }
                                      }
                                      2)在META-INF/services下注册自定义的ResourceBundleProvider
                                      定义的目录结构如下:
                                        META-INF
                                        ... services
                                        ...... java.util.spi.ResourceBundleControlProvider
                                        在文件java.util.spi.ResourceBundleControlProvider中将自定义的java.util.spi.ResourceBundleControlProvider全路径写入
                                          ## java.util.spi.ResourceBundleControlProvider实现类
                                          com.zhru.wechat.jdk.international.reslove.spi.CustomResourceBundleControlProvider
                                          3)打包应用,移动到jdk安装目录
                                          ① 执行mvn package命令,将上述代码所在的应用进行打包
                                          ② 将打包后的jar文件,移动到jdk安装路径/jre/lib/ext/中
                                          如下图:



                                          3.2.3 基于java.util.spi.ResourceBundleControlProvider解决方案测试
                                            /**
                                            * Desp: {@link java.util.ResourceBundle}加载配置信息
                                            * 2019-10-25 21:43
                                            * Created by zhru.
                                            */
                                            public class ResourceBundleDemo {


                                            public static void main(String[] args) {


                                            /**
                                            * 以en_US语言编码进行加载配置
                                            */
                                            ResourceBundle bundle = ResourceBundle.getBundle("myconfig", Locale.US);
                                            String address = bundle.getString("address");
                                            System.out.println("当前的语言编码是:" + Locale.US
                                            + ",获取的address值:" + address);


                                            /**
                                            * 以zh_CN语言编码进行加载配置
                                            */
                                            bundle = ResourceBundle.getBundle("myconfig", Locale.SIMPLIFIED_CHINESE);
                                            address = bundle.getString("address");
                                            System.out.println("当前的语言编码是:" + Locale.SIMPLIFIED_CHINESE
                                            + ",获取的address值:" + address);
                                                }
                                            }
                                            使用自定义的java.util.spi.ResourceBundleControlProvider ,借助SPI机制自动加载相应类。
                                            在运行之前,配置file.encoding参数,-Dfile.encoding=UTF-8


                                            (run)运行结果如下:
                                              /usr/lib/jdk1.8.0_171/bin/java  
                                              当前的语言编码是:en_US,获取的address值:BeiJing Province
                                              当前的语言编码是:zh_CN,获取的address值:北京市


                                              Process finished with exit code 0
                                              因作者能力有限,文中可能出现描述不清的地方,不足之处请指出!希望与你一起共进步^

                                              本公众号旨在记录人开发工作、学习过程中的所得、所感和所想,欢迎一起交流学习



                                              文章转载自开发自由行,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                              评论