[享学Netflix] 五、Apache Commons Configuration2.x别样的Builder模式:ConfigurationBuilder

中国缺少的不是土地,而是位置

–> 返回专栏总目录 <–
代码下载地址: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());
        }
    }
}

另外两个实现MultiFileConfigurationBuilderCombinedConfigurationBuilder就不用再介绍了,另外关于具有重载功能的子类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来服务,但是过程还是比较麻烦的,那么下文就介绍它给我们提供的“工具类”:ParametersConfigurations,那才是生产上真正的使用方式。

分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了309 篇原创文章 · 获赞 467 · 访问量 40万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/104394040