中国缺少的不是土地,而是位置
–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning
目录
前言
在文初已经知道:Configuration
是最最最重要的接口,所以构建它的实例自然就非常重要。
在1.x时,绝大多数情况下均使用new
的方式来创建Configuration
实例,而作为“更时髦”的2.x版本,显然得尽量避免出现new
关键字,那么怎么办?这就是本文要介绍的Builder模式。
Builder
模式相信大家已经很熟悉了,虽然ConfigurationBuilder
接口在1.x版本里也存在,但是一般情况下并不使用它。而2.x版本对它进行了全新的设计,并且提供了更多的实用子类,方便我们完成构建。
说明:
Commons Configuration
它的builder模式可能和你熟知的使用上可能并不太一样,它并没有build()方法,而是getXXX,并且使用一个Map来管理各个属性…
题外话:Commons Configuration
的设计里大量的使用到了Map传值,我个人觉得这么做非常不面对对象,所以差评,各位做好心理准备~
正文
1.x和2.x都有名为ConfigurationBuilder
的接口,但二者体现出来的重要程度不可同日而语。在1.x里你几乎可以忽略它,而在2.x里它是我们不能绕过的鸿沟,为了深入理解必须吃透。
BuilderParameters
在了解ConfigurationBuilder
之前,先知道BuilderParameters
是做什么用的。
BuilderParameters
的作用是为构造器提供参数,并且提供getParameters()
供以访问,类似于Java DSL
。
public interface BuilderParameters {
// 常量,用于保留初始化参数键的前缀。
String RESERVED_PARAMETER_PREFIX = "config-";
Map<String, Object> getParameters();
}
它的继承结构如图:
BasicBuilderParameters
顾名思义,它是BuilderParameters
基础实现,也不是抽象类哦。
// 注意它实现了接口BasicBuilderProperties哦,提供了一些列的set方法
// 形如:setLogger、setListDelimiterHandler、setInterpolator。。。
// 这些值都会被保存在Map里,对应的key参见下面常量
public class BasicBuilderParameters implements Cloneable, BuilderParameters, BasicBuilderProperties<BasicBuilderParameters> {
// =======这里定义了一大推属性key,方便从Map里获取=======
private static final String PROP_THROW_EXCEPTION_ON_MISSING = "throwExceptionOnMissing";
...
private static final String PROP_LOGGER = "logger";
// 插值器
private static final String PROP_INTERPOLATOR = "interpolator";
private static final String PROP_PREFIX_LOOKUPS = "prefixLookups";
...
private static final String PROP_CONVERSION_HANDLER = "conversionHandler";
private static final String PROP_CONFIGURATION_DECODER = "configurationDecoder";
// config-BeanHelper
private static final String PROP_BEAN_HELPER = RESERVED_PARAMETER_PREFIX + "BeanHelper";
// 存储这些属性值的Map
// 接口方法getParameters()返回的就是它
private Map<String, Object> properties;
// 唯一构造器
public BasicBuilderParameters() {
properties = new HashMap<>();
}
}
对应其它子类,介绍两个最具代表性的实现。
FileBasedBuilderParametersImpl
FileBased
说明他是基于文件的Builder参数。
// 实现了FileBasedBuilderProperties接口,提供了形如:
// setReloadingRefreshDelay,setFile/setURL/setFileName/setEncoding...一系列方法
// 这些参数大多最终都设置到FileHandler身上
public class FileBasedBuilderParametersImpl extends BasicBuilderParameters implements FileBasedBuilderProperties<FileBasedBuilderParametersImpl> {
// config-fileBased
private static final String PARAM_KEY = RESERVED_PARAMETER_PREFIX + "fileBased";
private static final String PROP_REFRESH_DELAY = "reloadingRefreshDelay";
private static final String PROP_DETECTOR_FACTORY = "reloadingDetectorFactory";
// 存储文件-Configuration关系和处理器
private FileHandler fileHandler;
// reloading detectors
private ReloadingDetectorFactory reloadingDetectorFactory;
private Long reloadingRefreshDelay;
... // 省略构造器
// 特点:每次get的时候,都会把“自己”放进去,注意一下即可
@Override
public Map<String, Object> getParameters() {
final Map<String, Object> params = super.getParameters();
params.put(PARAM_KEY, this);
return params;
}
// 静态工具方法:FileBasedBuilderParametersImpl 实例你可以通过Map装着传进来,高手
// 这种设计很无语
public static FileBasedBuilderParametersImpl fromParameters(final Map<String, ?> params, final boolean createIfMissing)
FileBasedBuilderParametersImpl instance = (FileBasedBuilderParametersImpl) params.get(PARAM_KEY);
...
if (instance == null && createIfMissing) {
instance = new FileBasedBuilderParametersImpl();
}
return instance;
}
... // 省略什么fromMap、inheritFrom、fromParameters等等方法
基于文件的配置的典型代表是XML和Properties文件喽。
PropertiesBuilderParametersImpl
// 实现接口PropertiesBuilderProperties
// setIncludesAllowed:是否允许支持include关键字 setLayout:设置模版
public class PropertiesBuilderParametersImpl extends FileBasedBuilderParametersImpl implements PropertiesBuilderProperties<PropertiesBuilderParametersImpl> {
private static final String PROP_INCLUDE_LISTENER = "includeListener";
private static final String PROP_INCLUDES_ALLOWED = "includesAllowed";
private static final String PROP_LAYOUT = "layout";
...
// 每个set方法,就是往Map里放值
@Override
public PropertiesBuilderParametersImpl setIncludeListener(final ConfigurationConsumer<ConfigurationException> includeListener) {
storeProperty(PROP_INCLUDE_LISTENER, includeListener);
return this;
}
... // 省略其它接口方法
}
使用示例
@Test
public void fun2(){
PropertiesBuilderParametersImpl parameters = new PropertiesBuilderParametersImpl();
parameters.setFileName("1.properties");
FileHandler fileHandler = parameters.getFileHandler();
FileLocator fileLocator = fileHandler.getFileLocator();
fileLocator = FileLocatorUtils.fullyInitializedLocator(fileLocator);
System.out.println(fileLocator.getSourceURL());
System.out.println(fileLocator.getBasePath());
System.out.println(parameters.getParameters());
}
输出:
file:/E:/work/mylessons/netflix-learning/commons-configuration/target/classes/1.properties
file:///E:/work/mylessons/netflix-learning/commons-configuration/target/classes/
{config-fileBased=org.apache.commons.configuration2.builder.PropertiesBuilderParametersImpl@368239c8}
ConfigurationBuilder
接口描述很简单:为创建一个Configuration
实例的接口。
// 请注意它也是一个EventSource ,所以你可以监听它的
// 请对应事件类型:`ConfigurationBuilderEvent`
public interface ConfigurationBuilder<T extends ImmutableConfiguration> extends EventSource {
// 请注意:这里创建方法名用的是get...,并不是builde(),类似于Spring的getBean的设计
T getConfiguration() throws ConfigurationException;
}
它的继承结构截图如下:
如果这幅图表示的层级关系还不够直观,再看下面这个两个截图:
可以看到,加有Reloading
前缀的子类,是在父类的基础上增加实现了ReloadingControllerSupport
接口,从而使得它具有对Configuration
重新加载的能力,这就是热更新、热加载,掌握了这个规律,按此来阅读其子类将如鱼得水。
BasicConfigurationBuilder
它是ConfigurationBuilder
接口的基本实现,并且通过反射创建一个Configuration
实例。
// ImmutableConfiguration接口的子接口是Configuration
public class BasicConfigurationBuilder<T extends ImmutableConfiguration> implements ConfigurationBuilder<T> {
// 最终生成的`Configuration`的Class类型,会用反射生成具体实例
private final Class<? extends T> resultClass;
// Bean声明:可得到getBeanFactoryName/getBeanFactoryParameter(可为null)
// getBeanClassName/getBeanProperties(字段们)/getConstructorArgs(构造器,可以有多个,所以返回集合)
private BeanDeclaration resultDeclaration;
//有了上面的Class和描述声明,便可通过反射生成Result实例喽
private volatile T result;
// 管理注册上来的监听器们
private final EventListenerList eventListeners;
// 初始化的时候是否允许失败,如果是false:那遇到异常就抛出,终止程序
// 如果是true:那就不会终止程序 默认是false
private final boolean allowFailOnInit;
// 初始化结果Configuration时需要的参数们
// Commons Configuration经常使用Map来传值,这是非常糟糕的一种设计
private Map<String, Object> parameters;
... // 省略为属性们赋值的构造器们
... // 省略各个属性的get方法
// set方法,为属性赋值。返回this,符合Builder风格
// 此方法是给params赋值:updateParameters(params);只是让其变为只读的Collection而已
public synchronized BasicConfigurationBuilder<T> setParameters(final Map<String, Object> params) {
updateParameters(params);
return this;
}
// putAll进去
public synchronized BasicConfigurationBuilder<T> addParameters(map) { ... }
// 把BuilderParameters里面的parameter参数,以及事件都添加进来
public BasicConfigurationBuilder<T> configure(final BuilderParameters... params) { ... }
// 注册监听器:installEventListener
// 既会把该监听器注册到当前builder,也会注册进当前result实例(如果它是个EventSource的话)
// 所以:所以:所以:此方法请务必在result有值了后才能调用()
// 也就是在getConfiguration()方法调用之后再使用
@Override
public <E extends Event> void addEventListener(final EventType<E> eventType, final EventListener<? super E> listener)
installEventListener(eventType, listener);
}
... // 省略removeEventListener方法
}
以上是基类针对这些基础属性的一些基本设置方法,比较简单。下面需要关注其最重要的接口实现方法:获取Configuration实例
BasicConfigurationBuilder:
@Override
public T getConfiguration() throws ConfigurationException {
// 发送Builder类型的时间,请求事件:CONFIGURATION_REQUEST
fireBuilderEvent(new ConfigurationBuilderEvent(this, ConfigurationBuilderEvent.CONFIGURATION_REQUEST));
T resObj = result;
boolean created = false; // 局部变量标记是否已创建
// 如果result为null,那就是首次,或者被reset过
// 这是使用的synchronized双重校验锁,来保证线程安全
if (resObj == null) {
synchronized (this) {
resObj = result;
// 再次判断一次,双重校验锁
if (resObj == null) {
// createResult():简而言之,通过参数匹配到合适的构造器,初始化出一个实例
// 并不一定是空构造器哦(但大部分是空构造,由`BeanDeclaration`决定参数)
// 这个实例化特别想Spring的Bean,它自己也抽象了一个`org.apache.commons.configuration2.beanutils.BeanFactory`
result = resObj = createResult();
created = true;
}
}
}
// 如果创建成功了,发送RESULT_CREATED类型的事件,并且把resObj发出去
if (created) {
fireBuilderEvent(...,RESULT_CREATED,resObj)
}
return resObj;
}
该方法类似于你的build()
方法,不同的是在你没有reset()
的情况下,每次调用该方法获取到的是相同实例(实例是通过反射完成:可以是任意构造器,也可以是工厂方法,有点Spring的意思有木有)。
除了这些,它作为一个“合格的”BaseXXX
基类更是提供的一些好用的(工具)方法:
BasicConfigurationBuilder:
// 把当前builder持有的这两个属性:result = null; resultDeclaration = null;
// 并且移除oldResult上的监听器们(这个不要忘了哦)
// 最后发送builder事件:RESET
public void resetResult() { ... }
// 把准备给构造器传的参数parameters也置为null
public void resetParameters() { setParameters(null); }
public synchronized void reset() {
resetParameters();
resetResult();
}
// 这也是个工具方法:给当前Builder实例设置一个controller参数
// 说明:BaseXXX木有Reloading能力,但是上面说过的Reloading打头的几个子类都需要ReloadingController来支持哦
public final void connectToReloadingController(final ReloadingController controller) {
ReloadingBuilderSupportListener.connect(this, controller);
}
说了这么多,来个示例跑一跑一看便知。
使用示例
BasicConfigurationBuilder
它并不是一个抽象类,所以它可以直接使用。
@Test
public void fun1() throws ConfigurationException {
BasicConfigurationBuilder builder = new BasicConfigurationBuilder(PropertiesConfiguration.class);
Map<String, Object> params = new HashMap<>();
// params.put("fileName", "YourBatman"); // 此处不能设置,因为它没有名为`fileName`的这个属性值,所以会抛错
params.put("includesAllowed", false);
builder.addParameters(params);
// PropertiesBuilderParametersImpl parameters = new PropertiesBuilderParametersImpl();
// parameters.setFileName("1.properties");
// // parameters.setIncludesAllowed(true);
// builder.configure(parameters);
PropertiesConfiguration configuration = (PropertiesConfiguration) builder.getConfiguration();
System.out.println(configuration.isIncludesAllowed()); // 设置成功,includesAllowed的值被成功设置为了false
PropertiesConfiguration configuration2 = (PropertiesConfiguration) builder.getConfiguration();
System.out.println(configuration == configuration2); // true。 说明get多次是同一实例
// 重置一下
builder.reset();
configuration2 = (PropertiesConfiguration) builder.getConfiguration();
System.out.println(configuration == configuration2); // false。说明重置后,生成了新的configuration2实例
}
虽然它是允许直接使用,但实际不会直接用它,而是使用功能更强的子类。
FileBasedConfigurationBuilder
同样的,FileBased
的实现。
public class FileBasedConfigurationBuilder<T extends FileBasedConfiguration> extends BasicConfigurationBuilder<T> {
// 存储对PropertiesConfiguration,XMLPropertiesConfiguration的编码们
private static final Map<Class<?>, String> DEFAULT_ENCODINGS = initializeDefaultEncodings();
// 注意这个取名为set,实际为put
// 比如你可以重新为PropertiesConfiguration等设置编码(public的哟)
public static void setDefaultEncoding(final Class<?> configClass, final String encoding) {}
// 临近的找,找不到找父类、父接口的。采用就近原则
public static String getDefaultEncoding(final Class<?> configClass) { ... }
// 当前文件的Handler
// 因为是FileBased,所以肯定与文件有关嘛,所以交给FileHandler来处理
private FileHandler currentFileHandler;
private AutoSaveListener autoSaveListener;
// 标志位:参数是否被reset了
private boolean resetParameters;
... // 省略构造器
// 如果没指定FileHandler,那就从parameters里给构造一个出来
public synchronized FileHandler getFileHandler() {
return currentFileHandler != null ? currentFileHandler : fetchFileHandlerFromParameters();
}
// 是否是自动保存的
public synchronized boolean isAutoSave() {
return autoSaveListener != null;
}
// 开启自动保存。说明:因为AutoSaveListener这个类为非public,所以外部能用true和false来控制
public synchronized void setAutoSave(final boolean enabled) {
if (enabled) { installAutoSaveListener(); }
else { removeAutoSaveListener(); }
}
// 给this此builder注册一个自动保存的监听器,拥有自动保存的能力
// 实现FileHandlerListener,监听loading等等方法
private void installAutoSaveListener() {
if (autoSaveListener == null) {
autoSaveListener = new AutoSaveListener(this);
addEventListener(ConfigurationEvent.ANY, autoSaveListener);
autoSaveListener.updateFileHandler(getFileHandler());
}
}
}
另外两个实现MultiFileConfigurationBuilder
和CombinedConfigurationBuilder
就不用再介绍了,另外关于具有重载功能的子类ReloadingFileBasedConfigurationBuilder
放在下篇重点介绍。
使用示例
@Test
public void fun3() throws ConfigurationException {
FileBasedConfigurationBuilder<PropertiesConfiguration> builder = new FileBasedConfigurationBuilder<>(PropertiesConfiguration.class);
builder.setAutoSave(false);
// 构建参数
PropertiesBuilderParametersImpl parameters = new PropertiesBuilderParametersImpl();
parameters.setFileName("1.properties");
builder.configure(parameters);
// // 直接get,会使用上面的parameter来生成实例
// FileHandler fileHandler = builder.getFileHandler();
// System.out.println(fileHandler);
PropertiesConfiguration configuration = builder.getConfiguration();
ConfigurationUtils.dump(configuration,System.out);
}
控制台打印:
common.name=YourBatman
common.age=18
common.addr=China
common.count=4
common.fullname=${common.name}-Bryant
java.version=1.8.123
本案例成功通过Builder得到了一个PropertiesConfiguration
,并且和文件关联上了。
总结
关于ConfigurationBuilder
就介绍到这了,本文专门花篇幅专门介绍Builder模式,是想让各位接受:一般提供给你Builder构建器使用,那么就不建议你自己去new实例,尽量拥抱Builder。
当然,不管是BuilderParameters
还是ConfigurationBuilder
最终都是为了创建出一个Configuration
来服务,但是过程还是比较麻烦的,那么下文就介绍它给我们提供的“工具类”:Parameters
和Configurations
,那才是生产上真正的使用方式。
声明
原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭
。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。