写好代码是个人素养,不是老板要求,更不是为了秀给同事看 代码下载地址:https://github.com/f641385712/netflix-learning
上一篇讲述了Commons Configuration2.x
它全新的事件-监听基础,一方面体会到了相较于1.x的改动之大,另一方面也能感受到2.x在可扩展性方面是有所增强的。
2.x作为现行的主流版本,因此本文仍着眼于它。本文将要讲述的是它的文件定位系统,其中最重要的两个API是FileLocator
和FileHandler
,需要引起关注。
第一篇文章就已经介绍了Commons Configuration
它的配置来源可以有多种,虽然并不强要求必须来自文件,但其实平时我们99%情况下使用它,内容均来自文件(文件可在工程内、外、系统里、网络上等等),所以文件定位对它的重要性可见一斑。
2.x在和文件相关方面也同样抽象出了很多新的API。文件定位在1.x中,只需ConfigurationUtils#locate( ... )
一句代码就搞定,它拥有固定的定位策略,不可更改。
但2.x在这方面做足了功夫,虽然使用起来可能会让你觉得稍显麻烦,但给足了灵活性,这在我们需要热加载的时候,提供了强有力的个性化支撑。
基于文件,该接口提供基本的读、写的能力。
public interface FileBased {
void read(Reader in) throws ConfigurationException, IOException;
void write(Writer out) throws ConfigurationException, IOException;
}
public interface FileBasedConfiguration extends FileBased, Configuration {
}
这个接口有何作用,如下截图一看便知:
可以看到所有的实现类均是基于文件的Configuration
。
文件定位之于Commons Configuration
有多重要不用再强调,2.x它针对于此也抽象出了一堆新的API,需要我们必须了解。
说明:它的API有的确实让人头疼,设计得有好些不合理的地方,多多担待~
它是用于描述文件位置的类。此类的实例提供了查找和访问文件的信息。可以定义文件位置作为一个URL;它以一种独特的方式标识一个文件。
public final class FileLocator {
// 文件名
private final String fileName;
private final String basePath;
// URL
private final URL sourceURL;
private final String encoding; // 编码
private final FileSystem fileSystem;
// 它决定了文件的扫描策略
private final FileLocationStrategy locationStrategy;
// 唯一构造器赋值:必须通过自己的内部类Builder
public FileLocator(final FileLocatorBuilder builder) { ... }
... // 省略所有的get方法
// 用于构建FileLocator实例的Builder,是public的
public static final class FileLocatorBuilder {
... // 省略同上一模一样的属性
// 唯一构造器,并且是Defualt,并且入参是FileLocator,有种你中有我,我中有你的感觉
// 2.x版本里有大量的这种设计,我觉得这种设计比较差。。。。
// 此构造器唯一使用地:FileLocatorUtils#FileLocatorUtils()方法
FileLocatorBuilder(final FileLocator src) {
if (src != null) {
// 这个方法就是把src所有属性赋值给builder,没啥好说的
initBuilder(src);
}
}
... // 省略所有的属性赋值方法
// 创建一个FileLocator,构造器传入this
public FileLocator create() {
return new FileLocator(this);
}
}
}
此类是不能改变的,因此可以被安全的共享。
通过这主要看看它Builder模式的实现,我个人觉得是很繁琐的(这种我中有你,你中有我的设计,个人感觉真的非常难用),使用起来很容易迷糊。
另外,Commons Configuration2.x
中的builder模式可能不是你理解中的那种模式,需要适应起来。
提供与定位文件相关的辅助方法的实用工具类。
从FileLocator
和FileLocatorBuilder
的设计上可知:我们若想得到一个FileLocator
实例,一定必须借助此工具类。
public final class FileLocatorUtils {
// 绝大部分情况下,使用默认的文件系统
public static final FileSystem DEFAULT_FILE_SYSTEM = new DefaultFileSystem();
// 它是一个CombinedLocationStrategy,顺序决定了扫描顺序
public static final FileLocationStrategy DEFAULT_LOCATION_STRATEGY = initDefaultLocationStrategy();
// 这个顺序决定了扫描的顺序,比如次数是先本目录找
// 再文件系统,在绝对路径,再家目录,**最后当前工程的classpath找**
private static FileLocationStrategy initDefaultLocationStrategy() {
final FileLocationStrategy[] subStrategies = new FileLocationStrategy[] {
new ProvidedURLLocationStrategy(),
new FileSystemLocationStrategy(),
new AbsoluteNameLocationStrategy(),
new BasePathLocationStrategy(),
new HomeDirectoryLocationStrategy(true),
new HomeDirectoryLocationStrategy(false),
new ClasspathLocationStrategy()
};
return new CombinedLocationStrategy(Arrays.asList(subStrategies));
}
...
// 定义这些常量,是方便你使用Map为`FileLocator`的各个属性传值
// 这写属性为何不public掉?这个库的有些API设计确实渣渣~~~~
private static final String PROP_BASE_PATH = "basePath";
private static final String PROP_ENCODING = "encoding";
private static final String PROP_FILE_NAME = "fileName";
...
private static final String PROP_STRATEGY = "locationStrategy";
// 尝试将指定的URL转换为file对象
// 其实就是对路径进行容错处理,然后new File(fileName)而已
public static File fileFromURL(final URL url) {
return FileUtils.toFile(url);
}
// 可以看到想要一个新的fileLocator,可以没有sec的,大不了所有属性均为null喽
public static FileLocator.FileLocatorBuilder fileLocator() {
return fileLocator(null);
}
public static FileLocator.FileLocatorBuilder fileLocator(final FileLocator src) {
return new FileLocator.FileLocatorBuilder(src);
}
// 使用Map为FileLocator的各个属性赋值,如 map.get(PROP_BASE_PATH)
public static FileLocator fromMap(final Map<String, ?> map) { ... }
// 把这个locator所有属性值放进map里
public static void put(final FileLocator locator, final Map<String, Object> map) { ... }
// fileName或者sourceUrl有值才算locator定义了位置
public static boolean isLocationDefined(final FileLocator locator) { ... }
// fileName basePath souceURL都有值,才返回true
public static boolean isFullyInitialized(final FileLocator locator) { ... }
// 此方法确保指向文件的FileLocator以一致的方式设置。不要有些缺胳膊少腿的
// 让这个locator完整设置 -> 那三个属性都赋上
// 这里会使用FileLocationStrategy#locate()去把url补充完整哟
public static FileLocator fullyInitializedLocator(final FileLocator locator) { ... }
// 请注意:这时候的FileLocator可能是缺胳膊少腿的,哪怕只有一个FileName也能找到你哦
// 所以这个方法非常重要,还是很靠谱的一个方法,会被经常使用
public static URL locate(final FileLocator locator) {
if (locator == null) {
return null;
}
return obtainLocationStrategy(locator).locate(obtainFileSystem(locator), locator);
}
}
这个工具类是平时编码中使用非常多的,需要掌握常用API。
@Test
public void fun2(){
// 1.x
URL file1 = ConfigurationUtils.locate("1.properties");
System.out.println(file1);
// 2.x
URL file2 = FileLocatorUtils.locate(FileLocatorUtils.fileLocator().fileName("1.properties").create());
System.out.println(file2);
}
1.x定位一个文件很easy,但到了2.x很明显麻烦了不少。我也费解,这么常用的功能为何不提取一个更为便捷的API呢???
学过Spring,对Aware
接口就一点不陌生了。
public interface FileLocatorAware {
void initFileLocator(FileLocator locator);
}
initFileLocator
代表把描述该文件的东西传递给你,唯一调用处在FileHandler
里,帮你注入FileLocator
文件,实现了此接口的均为Configuration
的子类:
文件定位策略接口。
1.x是按照固定的顺序去查找、定位文件:
而2.x让这变得更加的灵活:允许应用程序自定义文件定位过程,这就是这个接口的作用。它有如下实现类:
这些顺序你可以自由组合,甚至还可以自定义。比如:
List<FileLocationStrategy> subs = Arrays.asList(
new ProvidedURLLocationStrategy(),
new FileSystemLocationStrategy(),
new ClasspathLocationStrategy());
FileLocationStrategy strategy = new CombinedLocationStrategy(subs);
这样最终定位strategy 策略翻译如下:
管理关联着的FileBased
对象的持久化动作的类。该类的实例可用于以方便的方式从不同位置加载和保存实现FileBased
的接口的任意对象。
在构造时,就应该把FileBased
传入进来。基本上,这个对象被分配关联了一个加载位置和保存位置(当然你也可以在load()/save()的时候临时指定路径位置)。
public class FileHandler {
private final FileBased content;
// 当前文件的FileLocator
private final AtomicReference<FileLocator> fileLocator;
private final List<FileHandlerListener> listeners = new CopyOnWriteArrayList<>();
// 构造器,为属性赋值
public FileHandler() {
this(null);
}
// 这时,FileLocator是空的,所有数据均为null
public FileHandler(final FileBased obj) {
this(obj, emptyFileLocator());
}
// 此处:FileLocator从FileHandler获取出来
public FileHandler(final FileBased obj, final FileHandler c) {
this(obj, checkSourceHandler(c).getFileLocator());
}
... // 相关get方法,以及对监听器的增删改查方法
public String getFileName() {
FileLocator locator = getFileLocator();
if (locator.getFileName() != null)
return locator.getFileName();
if (locator.getSourceURL() != null)
return FileLocatorUtils.getFileName(locator.getSourceURL());
return null;
}
public void setFileName(final String fileName) { ... }
public void setBasePath(final String basePath) { ... }
// 它不为null的前提是:fileName或者sourceUrl不为null
// 依赖的方法是FileLocatorUtils.fileFromURL/FileLocatorUtils.getFile
public File getFile() {
return createFile(getFileLocator());
}
// =============静态方法,用于快速构建一个FileHandler实例==============
// 但是请注意:FileBased为null,所以用于构建一个FileLocator,方便构造器传值
// 不得不再次吐槽:设计的什么垃圾API,构造器直接传FileLocator不香吗?
public static FileHandler fromMap(final Map<String, ?> map) {
return new FileHandler(null, FileLocatorUtils.fromMap(map));
}
// 判断关联的File是否存在,存在返回true
public boolean locate() { ... }
public void clearLocation() { ... .basePath(null).fileName(null).sourceURL(null); ... }
...
// 从FileLocator里把文件加载进来
public void load() throws ConfigurationException { ... }
// 从你指定的这个fileName去加载,然后填充到FileBased里面去
public void load(final String fileName) throws ConfigurationException { ... }
... // 省略其它重载方法
// 把FileBased内容写到关联的File里面去
public void save() throws ConfigurationException { ... }
public void save(final String fileName) throws ConfigurationException { ... }
...
// 在以上load()和save过程中,会触发监听器的各种事件,从而外部便可以监听到这些动作
}
FileHandler
总结起来,起的作用是:对FileBased
(肯定是一个Configaration
实例)和FileLocator
绑定上一个关系,然后方便的做load/save读写操作。
不使用Configurations
,把一个文件的内容读取到PropertiesConfiguration
里来。
@Test
public void fun3() throws ConfigurationException {
// Configurations configs = new Configurations();
// FileLocator fileLocator = FileLocatorUtils.fileLocator().fileName("1.properties").create();
// fileLocator = FileLocatorUtils.fullyInitializedLocator(fileLocator);
// PropertiesConfiguration config = configs.properties(fileLocator.getSourceURL());
PropertiesConfiguration config = new PropertiesConfiguration();
// 把config和文件关联上
Map<String, Object> map = new HashMap<>();
map.put("fileName", "1.properties");
FileHandler fileHandler = new FileHandler(config, FileHandler.fromMap(map));
ConfigurationUtils.dump(config, System.out);
System.out.println("\n==============上为load之前==============");
fileHandler.load(); // 通过fileHandler也可以给Configuration赋值
ConfigurationUtils.dump(config, System.out);
System.out.println("\n==============上为load之后==============");
fileHandler.load();
ConfigurationUtils.dump(config, System.out);
System.out.println("\n==============上为再load一次的结果==============");
config.clear(); // 会发送ConfigurationEvent.CLEAR事件哦
fileHandler.load();
ConfigurationUtils.dump(config, System.out);
System.out.println("\n==============上为clear清空后再load的结果==============");
}
结果打印:
==============上为load之前==============
common.name=YourBatman
common.age=18
common.addr=China
common.count=4
common.fullname=${common.name}-Bryant
java.version=1.8.123
==============上为load之后==============
common.name=[YourBatman, YourBatman]
common.age=[18, 18]
common.addr=[China, China]
common.count=[4, 4]
common.fullname=[${common.name}-Bryant, ${common.name}-Bryant]
java.version=[1.8.123, 1.8.123]
==============上为再load一次的结果==============
common.name=YourBatman
common.age=18
common.addr=China
common.count=4
common.fullname=${common.name}-Bryant
java.version=1.8.123
==============上为clear清空后再load的结果==============
说明:至于为何${}这种占位符打印的时候没有被解析,请参见第二篇文章,有详细解释
从结果中能得出如下几个结论:
Configurations
,也可以向xxxConfiguration
(基于文件的FileBased
实例)里写数据,并且还可以无限写clear()
后再去读一份最新的FileHandlerListener
(它的内置实现仅有AutoSaveListener
) FileHandler
的几个方法:loading、loaded、saving、saved、locationChanged
代表中各种文件状态~关于Apache Commons Configuration2.x
版本的文件定位系统就介绍到这了,相较于1.x它完全弱化了使用者对定位逻辑的认识(屏蔽掉了实现细节),2.x在这方面做了大量的文章,使得使用者甚至都可以定制FileLocationStrategy
这种策略,当然牺牲的就是使用上的便捷性以及提高了理解上的难度,万事没有银弹,权衡才是关键。