owner

owner介绍

官网:https://owner.aeonbits.org/
github:https://github.com/lviggiano/owner
github中文翻译:https://github.com/cyfonly/owner-doc
OWNER API是一个Java库,其目标是在应用程序中最大程度地减少properties文件处理所需代码。此API的灵感源于GWT i18n(https://developers.google.com/web-toolkit/doc/latest/DevGuideI18nConstants),使用GWT i18n加载属性文件的问题在于,它仅在客户端代码(JavaScript)中有效,而在标准Java类中无效。另外,GWT是一个大型库,它的设计目的与配置不同。

特点:
简单:不需要代码来加载、转换和管理属性文件,仅仅只需要用最简单的方式使用属性
强大:基于注解、自动类型转换、变量扩张、参数化属性、热重载等
灵活:选择你需要的功能,屏蔽你不需要的,容易上手,具有丰富的文档
maven:

<!--jdk1.7及以前版本:-->
<dependency>
    <groupId>org.aeonbits.owner</groupId>
    <artifactId>owner</artifactId>
    <version>1.0.9</version>
</dependency>
 
<!--jdk1.8版本:-->
<dependency>
    <groupId>org.aeonbits.owner</groupId>
    <artifactId>owner-java8</artifactId>
    <version>1.0.9</version>
</dependency>

基本用法:
1、映射机制:
定义一个接口继承org.aeonbits.owner.Config(也称映射接口),然后在同一个包下创建同名的properties文件,属性名和接口方法名相对应,即可实现properties文件中属性的读取。

package owner;
 
import org.aeonbits.owner.Config;
public interface ServerConfig extends Config {
	String name();
	int age();
	String address();
}

ServerConfig.properties:

name=test
age=30
address=bj

注意:映射接口中定义了一个不能与 properties 文件中任何一个属性相匹配的方法,且没有指定defaultValue,那么调用该方法将会返回null。

3、ConfigFactory获取接口实现对象:
上面创建了映射接口,配置了properties文件,我们该如何使用呢?

import org.aeonbits.owner.ConfigFactory;
public class OwnerTest {
 
	public static void main(String[] args) {
		ServerConfig sconfig = ConfigFactory.create(ServerConfig.class);
		System.out.println(sconfig.name());
		System.out.println(sconfig.age());
		System.out.println(sconfig.address());
	}
}

加载策略
默认properties 文件和映射接口的关联是通过 owner API 匹配类名和文件名(.properties)来实现的,当然这个逻辑是可以根通过一些额外的注解来实现用户个性化的需求。

1、@Sources注解:

import org.aeonbits.owner.Config;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.LoadType;
import org.aeonbits.owner.Config.Sources;
@Sources({ "file:~/.myapp.config",
	"file:/etc/myapp.config", 
	"classpath:ServerConfig.properties" })
public interface ServerConfig extends Config {
	String name();
	@DefaultValue("18")
	int age();
	String address();
}

owner 会尝试从不同的 @Sources 中加载属性:

首先,尝试从用户home目录的 ~/.myapp.config 加载属性文件,假如找到,这个文件将会被使用;
假如上一个失败,它就会尝试从 /etc/myapp.config 加载属性文件,假如找到,这个文件将会被使用;
作为最后的手段,它将尝试从类路径下以 foo/bar/baz.properties 标志的文件中加载属性;
假如以上 URL 资源都不存在,java 类将不会与任何文件相关联,只有 @DefaultValue 会被使用。假如没有默认值,将会返回null(就像 java.util.Properties)
注意:默认@Sources 只会从第一个被找到的文件中加载,也可以显式的使用 @LoadPolicy(LoadType.FIRST) 注解来指定。

2、@LoadPolicy(LoadType.MERGE)
可以使用merge策略,这样所有 URL 中指定的文件都将被查询到,并且第一个定义某重载属性的文件中的值将会被采纳。

package owner;
 
import org.aeonbits.owner.Config;
import org.aeonbits.owner.Config.LoadPolicy;
import org.aeonbits.owner.Config.LoadType;
import org.aeonbits.owner.Config.Sources;
@LoadPolicy(LoadType.MERGE)
@Sources({ "file:~/.myapp.config",
	"file:/etc/myapp.config", 
	"classpath:ServerConfig.properties" })
public interface ServerConfig extends Config {
	String name();
	@DefaultValue("18")
	int age();
	String address();
}

具体过程如下:

首先,它会从 ~/.myapp.config 中加载指定的属性,如果找到就将关联的属性值返回;
然后,从 /etc/myapp.config 中加载指定的属性,如果找到就将关联的属性值返回;
最后它会从 classpath 路径下的 foo/bar/baz.properties 中加载指定的属性,如果找到就将关联的属性值返回;
假如指定的属性未在上述任何文件中找到的话,它就会返回用 @DefaultValue 标注的值,否则返回null。
因此基本上我们在多个 properties 文件中执行合并,且第一个properties 文件会重写后面的文件的相同属性值。

3、人工指定properties文件:
1)在调用 ConfigFactory.create() 时人工指定一个属性对象:

import java.util.Properties;
import org.aeonbits.owner.ConfigFactory;
public class OwnerTest {
 
	public static void main(String[] args) {
		Properties props1 = new Properties();
		props1.setProperty("name", "pineapple");
		props1.setProperty("age", "12");
		
		Properties props2 = new Properties();
		props2.setProperty("name", "haha");
		props2.setProperty("age", "0");
		
		ServerConfig sconfig = ConfigFactory.create(ServerConfig.class,props1,props2);
		System.out.println(sconfig.name());
		System.out.println(sconfig.age());
		System.out.println(sconfig.address());
	}
}

可以指定多个properties对象,假如 props1 和 props2 同时指定了同一个属性的值,那么首先指定的值将会被采用。

2)人工指定properties对象优先级高于@Sources注解:

通过代码引入的属性在优先级上要高于通过 @Sources 注释的方式,同时指定时:

首先从人工指定properties对象中加载指定的属性;
然后从@Sources指定properties文件中加载指定属性;
假设有这样一个场景,你已通过 @Sources 定义你的配置文件,但是你又想让用户在代码中自己指定配置文件,这个时候怎么办呢?

@Sources(...)
interface MyConfig extends Config { 
  ...
}
public static void main(String[] args) {
  MyConfig cfg;
  if (args.lenght() > 0) {
    Properties props = new Properties();
    props.load(new FileInputStream(new File(args[0])));
    cfg = ConfigFactory.create(MyConfig.class, userProps);
  } else {
    cfg = ConfigFactory.create(MyConfig.class);
  }
}

在上例中,用户指定的 properties 文件将会重写通过 @Sources 注解加载的 properties 文件中的同名属性。很多命令行工具会使用这种方式,它允许用户在命令行中重写默认配置。

3)除了获取properties文件中的属性外,还可以获取系统属性、环境变量:

public interface SystemEnvProperties extends Config{
	@Key("file.separator")
	String fileSeparator();
	@Key("java.home")
	String javaHome();
	@Key("HOME")
	String home();
	@Key("USER")
	String user();
	@Key("PATH")
	String path();
}

然后通过如下方式获取:

SystemEnvProperties cfg = ConfigFactory.create(SystemEnvProperties.class, System.getProperties(),System.getenv());
System.out.println(cfg.fileSeparator());//File.separator
System.out.println(cfg.javaHome());//System.getProperty("java.home")
System.out.println(cfg.home());//System.getenv().get("HOME")
System.out.println(cfg.user());//System.getenv().get("USER")
System.out.println(cfg.path());//System.getenv().get("PATH")

高级特性
1、参数化属性(parameterized properties):
它允许在映射接口的方法上提供参数,属性值应当遵循 java.util.Formatter 类指定的位置计数规则。

public interface MyConfig extends Config    {
 
	@DefaultValue("hello %s")
	String hello(String str);
}

使用:

MyConfig mconfig = ConfigFactory.create(MyConfig.class);
System.out.println(mconfig.hello("owner"));

说明:最终输出hello owner。但是如果在Myconfig.properties文件中有hello属性,则上面代码直接输出hello属性值。

可以通过@DisableFeature(DisableableFeature.PARAMETER_FORMATTING) 注解取消参数化属性这一功能。例如:

public interface MyConfig extends Config {
	@DisableFeature(DisableableFeature.PARAMETER_FORMATTING)
	@DefaultValue("hello %s")
	String hello(String str);
}

说明:如果在对应properties文件中有hello属性则输出属性值,如果没有输出:“hello %s”

2、变量扩展(Variables expansion):
可以使用${}引用其他变量:

public interface MyConfig extends Config {
	String name();
	
	@DisableFeature(DisableableFeature.PARAMETER_FORMATTING)
	@DefaultValue("hello %s")
	String hello(String str);
	
	@DefaultValue("hello ${name}")
	String myName();
}

说明:假设name属性值为abc,则myName输出hello abc。但是如果在Myconfig.properties文件中有myName属性,则上面代码直接输出myName属性值。

可以通过@DisableFeature(DisableableFeature.VARIABLE_EXPANSION)注解禁用变量扩展功能:

public interface MyConfig extends Reloadable,Mutable,Accessible    {
	String name();
	
	@DisableFeature(DisableableFeature.PARAMETER_FORMATTING)
	@DefaultValue("hello %s")
	String hello(String str);
 
	@DisableFeature(DisableableFeature.VARIABLE_EXPANSION)
	@DefaultValue("hello ${name}")
	String myName();
}

说明:如果在对应properties文件中有myName属性则输出属性值,否则输出:hello ${name}

3、热加载:
owner 支持程序重加载,也支持自动热加载。实现热加载有两种方式:同步和异步。

1)手动热加载:

映射接口需要继承Reloadable接口,在代码中调用reload()方法会像对象初始化时一样重新加载所有属性,假如配置文件被修改过,重加载后的将会是最新的属性值。

在这里插入图片描述

@Sources{...}
interface MyConfig extends Reloadable {
  String someProperties();
}
MyConfig cfg = ConfigFactory.create(MyConfig.class);
cfg.reload();

2)自动热加载:

使用@HotReload注解实现,例如:

@HotReload
@Sources("file:foo/bar/baz.properties")
interface MyConfig extends Config {
  @DefaultValue("localhost")
  String serverName();
}

@HotReload 可接受三个可选参数,其定义如下:

value:指定热加载检查的时间间隔,默认是5s
unit:单位,默认是秒;
type:同步、异步两种加载方式,默认是同步;
例如:

@HotReload(value=2, unit = TimeUnit.SECONDS,type=HotReloadType.SYNC)

@HotReload(value=500, unit = TimeUnit.MILLISECONDS,type=HotReloadType.ASYNC)

同步加载原理:每次调用通过 ConfigFactory.create() 构造的 Config 对象的方法时,会检查配置文件修改时间,并重加载文件。假设在很长一段时间内都没有使用 Config 对象,在对应文件系统上就不会去检查,重载也不会发生。鉴于此,我们也可以称这种方法为“懒加载”,因为只有我们使用时才会发生。
异步加载原理:在指定的时间间隔上执行一个单独的线程上的周期性任务来检查更新并重载。也就是说即使你不调用也会有重载发生。
3)监听:

MyConfig sconfig = ConfigFactory.create(MyConfig.class);
sconfig.addReloadListener(new ReloadListener() {
    public void reloadPerformed(ReloadEvent event) {
        System.out.print( "\rReload intercepted at "+ new Date() + " \n");
        Properties oldProperties = event.getOldProperties();
        System.out.println(oldProperties);
        Properties newProperties = event.getNewProperties();
        System.out.println(newProperties);
    }
});

补充:热加载只能作用在文件系统 URL 上,就是说你可以在这几种 URL 上让它工作:

file:path/to/your.properties: 基于文件系统的URL
jar:file:path/to/some.jar!/path/to/your.properties: 本地文件系统中包含properties文件的jar文件
classpath:path/to/your.properties: 从 classpath 中加载的资源,通常情况下,应用程序会从基于文件系统的classpath下加载类和资源,因此这种方式几乎任何时候都有效
假如你不使用 @Sources 注解,owner 会尝试在同一个包下面寻找与类名相同的 properties 文件。
4、Accessible and Mutable:(可访问性、可变性)
默认情况下,owner 创建的对象是不可改变的且重视信息隐藏。即:配置对象被创建它的属性就不能被改变了,并且不能被属性映射方法外的其他方式所访问。

这些方式是设计 owner 时做的限制,但有时候用户会觉得这并非很好,因此这里提供了 Mutable 接口和 Accessible 接口。以下是Mutable 接口和 Accessible 接口的层次结构:
在这里插入图片描述

1)Mutable接口允许开发者在程序运行期间改变Config对象属性:

public interface MyConfig extends Mutable{
	String name();
}

使用:

MyConfig sconfig = ConfigFactory.create(MyConfig.class);
 
sconfig.setProperty("name", "123");//设置属性
System.out.println(sconfig.name());
 
sconfig.setProperty("h_1","h1");//新增
 
String removeProperty = sconfig.removeProperty("h_1");//移除属性(不是从文件移除,只是内存中删除)
System.out.println(removeProperty);//h1

说明:上面例子中我们看到 setProperty 和 removeProperty 操作,Mutable 接口甚至提供了 clear() 、load(InputStream) 和 load(Reader) 方法,它实现了对 Config 对象内属性的完整写访问。

注意:这里的新增、修改、删除都不是真实的对properties文件中的属性做修改,而是在内存中做的处理。

2)Accessible 接口提供了Config对象的读取:

public interface MyConfig extends Accessible{
	String name();
}

使用

MyConfig sconfig = ConfigFactory.create(MyConfig.class);
System.out.println(sconfig.name());
 
Set<String> propertyNames = sconfig.propertyNames();
System.out.println(propertyNames);//打印properties文件所有key
 
 
sconfig.list(System.out);//打印properties文件所有内容
 
String property = sconfig.getProperty("test","test");
System.out.println(property);

说明:Accessible 不局限于 getProperty() ,它还提供了 list()、store()等操作。

补充:java中允许接口可以多重继承,所以映射接口可以同时继承Mutable、Accessible、Reloadable…

5、事件监听:
上面介绍热加载时(映射接口需要继承Reloadable接口),当properties文件发生重载时,通过addReloadListener方法监听热加载事件。

同样,映射接口集成Mutable接口后,可以通过addPropertyChangeListener方法监听属性的变动:

public interface Mutable extends Config {
  void addPropertyChangeListener(String propertyName, PropertyChangeListener listener);
  void addPropertyChangeListener(PropertyChangeListener listener);
  void removePropertyChangeListener(PropertyChangeListener listener);
  // ...the rest of the interface is cut...
}

6、单例:
上面,我们都是通过这种方式来创建Config对象的:

MyConfig cfg = ConfigFactory.create(MyConfig.class);

在复杂的项目中,这样做存在一个问题,需要不断传递这个对象,或者创建多份。

owner 提供了一种单例模式,可以通过ConfigCache.getOrCreate()创建Config对象,它们的区别在于,每次使用 ConfigFactory 时都会创建一个新的 MyConfig 实例,但使用 ConfigCache 时,实例是从内部缓存中返回的。

MyConfig firstFromCache = ConfigCache.getOrCreate(MyConfig.class);

参考:
owner:轻松管理java项目配置
owner-doc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值