概述

由来

资源(Resource)在Hutool中是一个广泛的概念,凡是存储数据的地方都可以归类到资源,那为何要提供一个如此抽象的接口呢?
在实际编码当中,我们需要读取一些数据,比如配置文件、文本内容、图片甚至是任何二进制流,为此我们要加入很多的重载方法,比如:

  1. read(File file){...}
  2. read(InputStream in){...}
  3. read(byte[] bytes){...}
  4. read(URL url){...}

等等如此,这样会造成整个代码变得非常冗余,查找API也很费劲。其实无论数据来自哪里,最终目的是,我们祥从这些地方读到byte[]或者String。那么,我们就可以抽象一个Resource接口,让代码变得简单:

  1. read(Resource resource){...}

用户只需传入Resource的实现即可。

定义

常见的,我们需要从资源中获取流(getStream),获取Reader来读取文本(getReader),直接读取文本(readStr),于是定义如下:

  1. public interface Resource {
  2. String getName();
  3. URL getUrl();
  4. InputStream getStream();
  5. BufferedReader getReader(Charset charset);
  6. String readStr(Charset charset);
  7. }

关于Resource的详细定义见:Resource.java

定义了Resource,我们就可以预定义一些特别的资源:

  • BytesResource 从byte[]中读取资源
  • InputStreamResource 从流中读取资源
  • StringResource 从String中读取资源
  • UrlResource 从URL中读取资源
  • FileResource 从文件中读取资源
  • ClassPathResource 从classpath(src/resources下)中读取资源
  • WebAppResource 从web root中读取资源
  • MultiResource 从多种资源中混合读取资源
  • MultiFileResource 从多个文件中混合读取资源

当然,我们还可以根据业务需要自己实现Resource接口,完成自定义的资源读取。

为了便于资源的查找,可以使用ResourceUtil快捷工具来获得我们需要的资源。

资源工具-ResourceUtil

介绍

ResourceUtil提供了资源快捷读取封装。

使用

ResourceUtil中最核心的方法是getResourceObj,此方法可以根据传入路径是否为绝对路径而返回不同的实现。比如路径是:file:/opt/test,或者/opt/test都会被当作绝对路径,此时调用FileResource来读取数据。如果不满足以上条件,默认调用ClassPathResource读取classpath中的资源或者文件。
同样,此工具类还封装了readBytesreadStr用于快捷读取bytes和字符串。
举个例子,假设我们在classpath下放了一个test.xml,读取就变得非常简单:

  1. String str = ResourceUtil.readUtf8Str("test.xml");

假设我们的文件存放在src/resources/config目录下,则读取改为:

  1. String str = ResourceUtil.readUtf8Str("config/test.xml");

注意 在IDEA中,新加入文件到src/resources目录下,需要重新import项目,以便在编译时顺利把资源文件拷贝到target目录下。如果提示找不到文件,请去target目录下确认文件是否存在。

ClassPath资源访问-ClassPathResource

什么是ClassPath

简单说来ClassPath就是查找class文件的路径,在Tomcat等容器下,ClassPath一般是WEB-INF/classes,在普通java程序中,我们可以通过定义-cp或者-classpath参数来定义查找class文件的路径,这些路径就是ClassPath。
为了项目方便,我们定义的配置文件肯定不能使用绝对路径,所以需要使用相对路径,这时候最好的办法就是把配置文件和class文件放在一起,便于查找。

由来

在Java编码过程中,我们常常希望读取项目内的配置文件,按照Maven的习惯,这些文件一般放在项目的src/main/resources下,读取的时候使用:

  1. String path = "config.properties";
  2. InputStream in = this.class.getResource(path).openStream();

使用当前类来获得资源其实就是使用当前类的类加载器获取资源,最后openStream()方法获取输入流来读取文件流。

封装

面对这种复杂的读取操作,我们封装了ClassPathResource类来简化这种资源的读取:

  1. ClassPathResource resource = new ClassPathResource("test.properties");
  2. Properties properties = new Properties();
  3. properties.load(resource.getStream());
  4. Console.log("Properties: {}", properties);

这样就大大简化了ClassPath中资源的读取。

Hutool提供针对properties的封装类Props,同时提供更加强大的配置文件Setting类,这两个类已经针对ClassPath做过相应封装,可以以更加便捷的方式读取配置文件。

配置文件Setting

概述

由来

Setting

众所周知,Java中广泛应用的配置文件Properties存在一个特别大的诟病:不支持中文。每次使用时,如果想存放中文字符,必须借助IDE相关插件才能转为Unicode符号,而这种反人类的符号在命令行下根本没法看(想想部署在服务器上后修改配置文件是一件多么痛苦的事情)
于是,在很多框架中开始渐渐抛弃Properties文件而转向XML配置文件(例如Hibernate和Spring早期版本)。但是XML罗嗦的配置方式实在无法忍受。于是,Setting诞生。

Props

Properties的第二个问题是读取非常不方便,需要我们自己写长长的代码进行load操作:

  1. properties = new Properties();
  2. try {
  3. Class clazz = Demo1.class;
  4. InputStream inputestream = clazz.getResourceAsStream("db.properties");
  5. properties.load( inputestream);
  6. }catch (IOException e) {
  7. //ignore
  8. }Copy to clipboardErrorCopied

而Props则大大简化为:

  1. Props props = new Props("db.properties");Copy to clipboardErrorCopied

考虑到Properties使用依旧广泛,因此封装了Props类以应对兼容性。

Properties扩展-Props

介绍

对于Properties的广泛使用使我也无能为力,有时候遇到Properties文件又想方便的读写也不容易,于是对Properties做了简单的封装,提供了方便的构造方法(与Setting一致),并提供了与Setting一致的getXXX方法来扩展Properties类,Props类继承自Properties,所以可以兼容Properties类。

使用

Props的使用方法和Properties以及Setting一致(同时支持):

  1. Props props = new Props("test.properties");
  2. String user = props.getProperty("user");
  3. String driver = props.getStr("driver");

设置文件-Setting

简介

Setting除了兼容Properties文件格式外,还提供了一些特有功能,这些功能包括:

  • 各种编码方式支持
  • 变量支持
  • 分组支持

首先说编码支持,在Properties中,只支ISO8859-1导致在Properties文件中注释和value没法使用中文,(用日本的那个插件在Eclipse里可以读写,放到服务器上读就费劲了),因此Setting中引入自定义编码,可以很好的支持各种编码的配置文件。
再就是变量支持,在Setting中,支持${key}变量,可以将之前定义的键对应的值做为本条值得一部分,这个特性可以减少大量的配置文件冗余。
最后是分组支持。分组的概念我第一次在Linux的rsync的/etc/rsyncd.conf配置文件中有所了解,发现特别实用,具体大家可以自行百度之。当然,在Windows的ini文件中也有分组的概念,Setting将这一概念引入,从而大大增加配置文件的可读性。

配置文件格式example.setting

  1. # -------------------------------------------------------------
  2. # ----- Setting File with UTF8-----
  3. # ----- 数据库配置文件 -----
  4. # -------------------------------------------------------------
  5. #中括表示一个分组,其下面的所有属性归属于这个分组,在此分组名为demo,也可以没有分组
  6. [demo]
  7. #自定义数据源设置文件,这个文件会针对当前分组生效,用于给当前分组配置单独的数据库连接池参数,没有则使用全局的配置
  8. ds.setting.path = config/other.setting
  9. #数据库驱动名,如果不指定,则会根据url自动判定
  10. driver = com.mysql.jdbc.Driver
  11. #JDBC url,必须
  12. url = jdbc:mysql://fedora.vmware:3306/extractor
  13. #用户名,必须
  14. user = root${demo.driver}
  15. #密码,必须,如果密码为空,请填写 pass =
  16. pass = 123456

配置文件可以放在任意位置,具体Setting类如何寻在在构造方法中提供了多种读取方式,具体稍后介绍。现在说下配置文件的具体格式 Setting配置文件类似于Properties文件,规则如下:

  1. 注释用#开头表示,只支持单行注释,空行和无法正常被识别的键值对也会被忽略,可作为注释,但是建议显式指定注释。同时在value之后不允许有注释,会被当作value的一部分。
  2. 键值对使用key = value 表示,key和value在读取时会trim掉空格,所以不用担心空格。
  3. 分组为中括号括起来的内容(例如配置文件中的[demo]),中括号以下的行都为此分组的内容,无分组相当于空字符分组,即[]。若某个keyname,分组是group,加上分组后的key相当于group.name。
  4. 支持变量,默认变量命名为 ${变量名},变量只能识别读入行的变量,例如第6行的变量在第三行无法读取,例如配置文件中的${driver}会被替换为com.mysql.jdbc.Driver,为了性能,Setting创建的时候构造方法会指定是否开启变量替换,默认不开启。

    代码

    代码具体请见cn.hutool.setting.test.SettingTest

  5. Setting初始化

    1. //读取classpath下的XXX.setting,不使用变量
    2. Setting setting = new Setting("XXX.setting");
    3. //读取classpath下的config目录下的XXX.setting,不使用变量
    4. setting = new Setting("config/XXX.setting");
    5. //读取绝对路径文件/home/looly/XXX.setting(没有就创建,关于touc请查阅FileUtil)
    6. //第二个参数为自定义的编码,请保持与Setting文件的编码一致
    7. //第三个参数为是否使用变量,如果为true,则配置文件中的每个key都以被之后的条目中的value引用形式为 ${key}
    8. setting = new Setting(FileUtil.touc("/home/looly/XXX.setting"), CharsetUtil.CHARSET_UTF_8, true);
    9. //读取与SettingDemo.class文件同包下的XXX.setting
    10. setting = new Setting("XXX.setting", SettingDemo.class,CharsetUtil.CHARSET_UTF_8, true);
  6. Setting读取配置参数

    1. //获取key为name的值
    2. setting.getStr("name");
    3. //获取分组为group下key为name的值
    4. setting.getByGroup("name", "group1");
    5. //当获取的值为空(null或者空白字符时,包括多个空格),返回默认值
    6. setting.getStr("name", "默认值");
    7. //完整的带有key、分组和默认值的获得值得方法
    8. setting.getStr("name", "group1", "默认值");
    9. //如果想获得其它类型的值,可以调用相应的getXXX方法,参数相似
    10. //有时候需要在key对应value不存在的时候(没有这项设置的时候)告知户,故有此方法打印一个debug日志
    11. setting.getWithLog("name");
    12. setting.getByGroupWithLog("name", "group1");
    13. //获取分组下所有配置键值对,组成新的Setting
    14. setting.getSetting("group1")
  7. 重新加载配置和保存配置

    1. //重新读取配置文件
    2. setting.reload();
    3. //在配置文件变更时自动加载
    4. setting.autoLoad(true);
    5. //当通过代码加入新的键值对的时候,调用store会保存到文件,但是会盖原来的文件,并丢失注释
    6. setting.set("name1", "value");
    7. setting.store("/home/looly/XXX.setting");
    8. //获得所有分组名
    9. setting.getGroups();
    10. //将key-value映射为对象,原理是原理是调用对象对应的setXX方法
    11. UserVO userVo = new UserVo();
    12. setting.toBean(userVo);
    13. //设定变量名的正则表达式。
    14. //Setting的变量替换是通过正则查找替换的,如果Setting中的变量名其他冲突,可以改变变量的定义方式
    15. //整个正则匹配变量名,分组1匹配key的名字
    16. setting.setVarRegex("\\$\\{(.*?)\\}");