owner:java属性文件配置

一.OWNER入门

1.什么是OWNER?

OWNER是一个java库,其目标是通过Java属性文件来最小化处理应用程序配置所需的代码。

2.OWNER的特点

(1)简单:不需要代码来加载、转换和管理属性文件,仅仅只需要用最简单的方式使用属性

(2)强大:基于注解、自动类型转换、变量扩张、参数化属性、热重载等

(3)灵活:选择你需要的功能,屏蔽你不需要的,容易上手,具有丰富的文档

3.OWNER的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>

4.OWNER的使用

(1)基本使用

1)ServerConfig.java:

package com.lsy.owner.demo1;

import org.aeonbits.owner.Config;

/*
 * 1.因为属性文件与java类具有相同的名字,并且位于同一个包中,所以OWNER会自动将它们关联起来
 * 2.属性文件中定义的属性名称,将与相同名字的java接口中的方法对应
 */
public interface ServerConfig extends Config{

	String name();
	int age();
	String address();
}
2)ServerConfig.properties:
name=demo1
age=23
address=china
3)分析:

创建属性文件ServerConfig.properties;

如果想要访问改属性文件,需要在同一个包下面定义一个相同名字的java接口并继承Config接口;

由于属性文件与java接口具有相同的名称,并且位于同一个包中,所以OWNER API能自动将它们进行关联

例如:调用com.lsy.owner.demo1.serverConfig接口时,会自动关联com.lsy.owner.demo1.serverConfig.properties文件;

属性文件中定义的属性名称将与具有相同名称的java接口中的方法关联

例如:name与String name()对应,age与int age()对应,address与String address()对应。

(2)@Default注解:

1)ServerConfig.java:

package com.lsy.owner.demo2;

import org.aeonbits.owner.Config;

/*
 * 属性文件缺少某个属性名称的情况
 */
public interface ServerConfig extends Config{

	String name();
	//当配置文件没有该属性名称时,使用默认属性值
	@DefaultValue("22")
	int age();
	String address();
}

2)ServerConfig.properties:

name=demo2
address=china
3)分析:

@DefaultValue的值代表的是该接口方法对应属性的默认值;

当接口方法中不能在属性文件中找到对应的属性时,通过设置默认值,防止程序报错;

如果没设置默认值,String类型就会返回null,int类型就会报NullPointerException;

如果不想出现NullPointerException,我们就需要设置默认值;

例如:int返回类型设为@DefaultValue(“0”),布尔返回类型设为@DefaultValue(“fasle”)

4)好处:

在开发中,可以只是用@DefaultValue来提供一个默认的配置,而暂时不需要添加真正的属性文件,可以以后再添加属性文件,或者将属性文件交给最用用户来添加

(3)@Key注解:

1)ServerConfig.java:

package com.lsy.owner.demo3;

import org.aeonbits.owner.Config;

/*
 * 自定义属性文件中属性名称的情况
 */
public interface ServerConfig extends Config{

	//使用@key注解将自定义属性映射到关联的方法
	@Key("man.name")
	String name();
	//当配置文件没有该属性名称时,使用默认属性值
	@Key("man.age")
	@DefaultValue("22")
	int age();
	@Key("man.address")
	String address();
}

2)ServerConfig.properties:

#自定义属性键,java类中使用@key注解
man.name=demo3
man.age=23
man.address=china

3)分析:

当属性文件是自己自定义的名字(属性名称由点分隔),不能与java接口的方法对应的时候,使用@Key注解
将需要映射的属性写在对应的方法名上就能实现映射


PS:所有的测试代码见最后的Test类。

    二.OWNER其他几个常用功能

1.加载策略

(1)默认加载路径:


分析:

从代码中可以看到,OWNER将从3个路径加载属性文件;

首先,从用户的主目录~/.mapper.config中加载属性文件,如果找到该文件,该文件将被单独使用;

如果用户主目录没有找到该文件,将尝试在/etc/myapp.config中加载属性文件,如果找到该文件,使用该文件;

如果还没找到,最后就会在classpass路径的foo/bar/baz.properties中加载属性;

如果以上都没找到属性文件,那么java接口将不会关联任何文件,只有在使用@DefaultValue才有属性,如果没有属性值,将会返回null或NullPointerException;

当@Sources加注解被多个URL指定的时候,这种加载方式是OWNER默认的加载方式,我们也可以在@Sources上面使用@LoadPolicy(LoadType.FIRST)来明确指明该加载方式:从URL的顺序依次加载属性文件,找到一个文件,其他文件就被忽视;

(2)指定加载方式:


分析:

如果你想属性之间有点优先权,我们把默认的FIRST它改为MERGE就可以了;

这种情况下,所有的URL都将被查询,并且指定了第一文件下的属性更具有优先权,第一个文件读到了属性,后面文件中有相同属性就不会再读取;

首先,从用户的主目录~/.mapper.config中加载给定的属性,如果找到该属性,属性值将被返回;

然后会尝试在/etc/myapp.config中加载给定属性,如果找到该属性,属性值将被返回;

最后,会在classpass路径下的foo/bar/baz.properties中加载给定属性,如果找到该属性,属性值将被返回;

如果都找不到给定的属性,则返回@DefaultValue指定的值,如果没有则返回null或NullPointerException;

这就是使我们的属性文件之间产生了一种合并的关系,其中第一个属性文件会覆盖后者。

(3)常用方式:

1)ServerConfig.java:

package com.lsy.owner.demo10;


import org.aeonbits.owner.Config;
import org.aeonbits.owner.Config.Sources;

@Sources({"classpath:ServerConfig.properties"})
public interface ServerConfig extends Config{

	String name();
	int age();
	String address();
}
2)ServerConfig.properties:

name=demo10
age=23
address=china

3)分析:

使用了@Sources注解,就会自动去该注解中的路径寻找合适的配置文件

2.导入属性

1)ServerConfig.java:

package com.lsy.owner.demo5;

import org.aeonbits.owner.Config;

/*
 * 导入属性:
 * 该机制是在调用时通过编程的方式指定一个Properties对象
 * 以编程方式导入的属性对于从属性加载的@Sources属性具有更高的优先级
 * 如果属性名称之间有重叠,那么用户指定的属性文件将覆盖加载的属性
 */
public interface ImportConfig extends Config{

	@DefaultValue("demo5")
	String name();
	@DefaultValue("25")
	int age();
	@DefaultValue("china")
	String address();
}

2)分析:

使用编程的方式,不需要属性文件,通过创建一个Properties对象来加载属性;

创建一个Properties对象,通过setProperty设置需要导入的属性值,如果没有设置,则使用默认值;

通过ConfigFactory.create()传入Properties对象完成设置;

也可以指定多个Properties对象在同一行上导入,当这种情况发生的时候,先指定的属性键的值将被读取,如果后面更改了属性键的值,后面的将被忽略;

3.参数化属性

1)ServerConfig.java:

package com.lsy.owner.demo7;

import org.aeonbits.owner.Config;

/*
 * 参数化属性
 */
public interface Sample extends Config{


	@DefaultValue("Hello %s!")
	String sayHelloToName(String name);

}

2)分析:

这个特点可以使我们在接口的方法上提供参数;

但是属性值应准守由java.util.Formatter类指定的位置符号 ;

4.类型转换

1)Myconfig:
package com.lsy.owner.demo8;

import java.util.List;

import org.aeonbits.owner.Config;

/*
 * 数组和集合的类型转换:
 * @Separator与@TokenizerClass可以用在方法上,只影响该方法;也可以用在类上,影响整个类
 * @Separator和@TokenizerClass不能在同一级别上使用,否者会抛UnsupportedOperationException
 */
public interface MyConfig extends Config {

	@DefaultValue("3.1415")
	double pi();
	
	// 默认情况下,OWNER使用逗号","字符为数组和集合标记值
	@DefaultValue("apple, pear, orange")
	public String[] fruit();

	// 用@Separator注释指定不同的字符(和regexp)
	@Separator(";")
	@DefaultValue("0; 1; 1; 2; 3; 5; 8; 13; 21; 34; 55")
	public int[] number();

	@DefaultValue("1, 2, 3, 4")
	List<Integer> ints();

	// 复杂的拆分逻辑可以自定义,使用@TokenizerClass注解
	@TokenizerClass(MyTokenizer.class)
	@DefaultValue("foo-bar-baz")
	public String[] customize();
}
2)MyTokenizer:

package com.lsy.owner.demo8;

import org.aeonbits.owner.Tokenizer;
/*
 * String[] split(String regex, int limit):limit 参数控制模式应用的次数,因此影响所得数组的长度。
 * 如果该限制n大于 0,则模式将被最多应用 n-1次,数组的长度将不会大于n,而且数组的最后一项将包含所有超出最后匹配的定界符的输入。
 * 如果n为负,那么模式将被应用尽可能多的次数,而且数组可以是任何长度。
 * 如果 n为 0,那么模式将被应用尽可能多的次数,数组可以是任何长度,并且结尾空字符串将被丢弃。
 */
public class MyTokenizer implements Tokenizer{

	// 需要复杂的的逻辑可以自定义
	public String[] tokens(String values) {
		return values.split("-", -1);
	}

}
3)分析:

OWNER API支持对数据类型的转换,我们在接口方法上可使用String类型,会自动转换成方法返回的类型;也可以自定义一个类,封装数据返回类型;对数组和集合也有很好的支持;

默认情况下,OWNER使用逗号","字符为数组和集合标记值;

如果不是逗号字符,可以用@Separator注释指定不同的字符(或正则表达式);

复杂的拆分逻辑可以自定义,使用@TokenizerClass注解;

@Separator和@TokenizerClass可以用在方法上,也可以用在接口上,但是不能在同一级别上使用,否者会抛UnsupportedOperationException

5.变量扩张

1)ExpandConfig:

package com.lsy.owner.demo9;

import org.aeonbits.owner.Config;

/*
 * 变量扩展:
 * 屬性之间的值可以相互使用
 */
public interface ExpandConfig extends Config {

	String story();

	String animal();

	String target();

	@Key("target.attribute")
	String targetAttribute();

	String color();
}

2)ExpandConfig.properties:

story=The ${animal} jumped over the ${target}
animal=quick ${color} fox
target=${target.attribute} dog
target.attribute=lazy
color=brown

3)分析:

有的时候,从属性文件中的A属性获得的属性值对B属性需要获得的属性值有用的时候,可以使用变量的扩张

6.重载与热重载

(1)重载


分析:

继承Reloadable接口,然后使用reload()方法,将在对象最开始创建的时候,加载所有的属性;如果属性文件被修改了,在加载调用后,这些改变的属性将被映射到对象中

(2)热重载



分析:
使用@HotReload注解,指示OWNER去监视这些资源的改变,并在改变后重新加载它;
但是热重载只适用于文件系统的URL,也就是如下URL:
1)文件系统支持的URL


2)包含属性文件的本地文件系统中的jar包


3)加载classpath下的资源


如果从远程URL加载资源,就将不起作用

(3)为什么只有”file:”,”jar:file”和”classpath:”的URL可用?

监视远程URL,如“http”或“ftp”,将涉及到网络通信从远程服务器频繁下载这些文件,只是为了检查它们是否被改变,并且这不方便实施热重载这样频繁的繁重操作。 对于这些情况,可以使用Reloadable接口以编程方式执行重新加载。

(4)@HotReload注解


分析:

1)@HotReload:使用默认值,每5秒检查一次文件更改,使用SYNC热重载;
2)@HotReload(2):每2秒检查一次文件更改,使用SYNC热重载;
3)@HotReload(value = 500,unit = TimeUnit.MILLISECONDS):每500毫秒检查一次文件更改,使用SYNC热重载;
4)@HotReload(type=HotReloadType.ASYNC):将使用ASYNC重载类型(将跨越一个单独的线程检查文件),每5秒更改一次(默认);
5)@HotReload(value=2, type=HotReloadType.ASYNC):将使用ASYNC重载类型并每2秒检查一次。

(5)SYNC和ASYNC热重载的区别?

1)同步热重载:
同步热重载的工作原理是这样的:每次调用ConfigFactory.create()配置文件创建的配置文件的方法都会被检查修改,最终重新加载文件。所以,如果长时间不使用配置对象,文件系统将不会进行检查,因此不会执行重新加载,因为它只在最后一次需要时才会这样做。它是@HotReload注释的默认值,但是也可以显示这种热重载type=SYNC:


2)异步热重新加载
异步热重载的工作方式:它按照指定的时间间隔在单独的线程上执行周期性任务,以检查文件以进行修改并最终重新加载它们。
所以,如果长时间不使用配置对象,那么对文件系统的检查将在后台完成,并最终重新加载。使用该方式,需要指定这种热重载type=ASYNC:



    三.总结

为什么我应该使用OWNER?

1.其他方式处理配置文件:
因为处理配置文件的代码经常是重复的、冗余的,它是由一些静态类、单例、一长串方法组成,但是仅仅将字符串属性转换为命名方法需要返回的对象。

2.OWNER处理配置文件:
1)声明式地映射配置没有任何冗余。
2)以轻松扩展您的加载逻辑,以便拥有多个配置文件,覆盖多个级别(全局配置,用户级别,默认值等)。
3)使用的时候,不需要有一个支持配置的实际属性文件,使用@DefaultValue即可。
4)提供了很多功能,如热重载,变量扩展等。
5)它支持超级强大的类型转换,包括数组,集合,许多标准的Java对象,甚至可以插入自己的转换逻辑。
6)不会给项目带来任何传递依赖,所以没有冲突的jar包,没有依赖性问题。
7)不会像其他处理方式那样用复杂的方式重复的做同样的事。




Test类:

package com.lsy.owner.test;

import java.io.File;
import java.util.Properties;

import org.aeonbits.owner.ConfigFactory;
import org.junit.Assert;
import org.junit.Test;

public class OwnerTest {

	@Test
	public void test1(){
		com.lsy.owner.demo1.ServerConfig config = 
				ConfigFactory.create(com.lsy.owner.demo1.ServerConfig.class);
		System.out.println(config.name()+","+config.age()+","+config.address());
	}
	
	@Test
	public void test2(){
		com.lsy.owner.demo2.ServerConfig config = 
				ConfigFactory.create(com.lsy.owner.demo2.ServerConfig.class);
		System.out.println(config.name()+","+config.age()+","+config.address());
	}
	
	@Test
	public void test3(){
		com.lsy.owner.demo3.ServerConfig config = 
				ConfigFactory.create(com.lsy.owner.demo3.ServerConfig.class);
		System.out.println(config.name()+","+config.age()+","+config.address());
	}
	
	@Test
	public void test4(){
		com.lsy.owner.demo4.ServerConfig config = 
				ConfigFactory.create(com.lsy.owner.demo4.ServerConfig.class);
		System.out.println(config.name());
		System.out.println(config.yesOrNo());
		System.out.println(config.age());
	}
	
	@Test
	public void test5_1(){
		Properties props = new Properties();
		props.setProperty("name", "test5");
		props.setProperty("age", "12");
		com.lsy.owner.demo5.ImportConfig config = 
				ConfigFactory.create(com.lsy.owner.demo5.ImportConfig.class, props);
		Assert.assertEquals("test5", config.name());
		Assert.assertEquals(12, config.age());
		Assert.assertEquals("china", config.address());
	}
	
	@Test
	public void test5_2(){
		Properties p1 = new Properties();
		p1.setProperty("name", "test5");
		p1.setProperty("age", "11");
		Properties p2 = new Properties();
		p2.setProperty("age", "12");
		p2.setProperty("address","chongqing");
		com.lsy.owner.demo5.ImportConfig config = 
				ConfigFactory.create(com.lsy.owner.demo5.ImportConfig.class, p1,p2);
		Assert.assertEquals("test5", config.name());
		Assert.assertEquals(11, config.age());
		Assert.assertEquals("chongqing", config.address());
	}
	
	@Test
	public void test6(){
		com.lsy.owner.demo6.SystemEnvProperties systemEnvProperties = 
				ConfigFactory.create(com.lsy.owner.demo6.SystemEnvProperties.class,
						System.getProperties(),System.getenv());
		Assert.assertEquals(File.separator, systemEnvProperties.fileSeparator());
		Assert.assertEquals(System.getProperty("java.home"), systemEnvProperties.javaHome());
		System.out.println(System.getProperty("java.home"));
		Assert.assertEquals(System.getenv().get("HOME"), systemEnvProperties.home());
		Assert.assertEquals(System.getenv().get("USER"), systemEnvProperties.user());
	}
	
	@Test
	public void test7(){
		com.lsy.owner.demo7.Sample config = 
				ConfigFactory.create(com.lsy.owner.demo7.Sample.class);
		System.out.println(config.sayHelloToName("lsy"));
	}
	
	@Test
	public void test8(){
		com.lsy.owner.demo8.MyConfig config = 
				ConfigFactory.create(com.lsy.owner.demo8.MyConfig.class);
		System.out.println(config.pi()+1);
		for (String s : config.fruit()) {
			System.out.print(s+" ");
		}
		for (Integer i : config.number()) {
			System.out.print(i+" ");
		}
		for (Integer i : config.ints()) {
			System.out.print(i+" ");
		}
		for (String s : config.customize()) {
			System.out.print(s+" ");
		}
	}
	
	@Test
	public void test9(){
		com.lsy.owner.demo9.ExpandConfig config = 
				ConfigFactory.create(com.lsy.owner.demo9.ExpandConfig.class);
		System.out.println(config.story());
	}
	
	@Test
	public void test10(){
		com.lsy.owner.demo10.ServerConfig config = 
				ConfigFactory.create(com.lsy.owner.demo10.ServerConfig.class);
		System.out.println(config.name()+","+config.age()+","+config.address());
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值