//TODO:ExpressionHelper 、bindBidirectional双向绑定、以及IntegerExpression的一系列算术方法和返回的IntegerBinding暂未详细解析(比如,通过 sip.divide(2) 返回的IntegerBinding对象,是如何实现当sip修改时,其get方法的值也能做到除2【随便猜测可能就类似于单向绑定一样,维护observable并记录算术操作,在get时,调用observable.get并加上算术操作】)

//注:关于观察者模式和事件监听模式(具体有没有这个定义都还待定),虽然表现不太一样但实现逻辑都一样的,观察者模式说一对多的依赖关系,当改变时其他相关依赖对象都对得到通知并更新,其实就等于调用监听器的监听方法

一、背景

使用过 SimpXXXProperty 系列的类都知道,这些类是支持属性绑定以及改变监听的,在实际开发中这种机制非常有用。

但包括Observable接口在内的这一系列类,均是由javafx所引入,在javafx包下。为了避免包引入看起来不论不类、也加深自己的理解,以SimpleIntegerProperty为例学习下实现原理。

二、使用示例

2.1 属性绑定示例

例1:javafx窗口界面中有一个圆,若想实现无论怎么拉伸,使圆均处于窗口中心位置的话,就可以使用绑定机制

    circle.centerXProperty().bind(stage.widthProperty().divide(2));

例2:小demo,让一个属性始终为另一个的一半

    SimpleIntegerProperty half = new SimpleIntegerProperty();
SimpleIntegerProperty target = new SimpleIntegerProperty(8);
half.bind(target .divide(2));
System.out.println(half.get());

2.2 修改监听示例

需求:比如做响应式页面,当窗口宽度小于某个阈值时,执行某些操作。

    stage.widthProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(newValue < 333)
System.out.println("当前小于333");
}
});

三、机制概述

从表现上来看有两个特性

3.1 属性绑定(property binding)

允许同步两个属性的值,其中一个修改时,另一个属性的获取值会同步更新。

有两个绑定方法 bindbinBidirectional 分别对应两种绑定方式:

  • 单向绑定(Unidirectional binding):比如属性A绑定B,当B属性改变时,A的获取值会同步更新。且A将无法手动修改,只能修改B,否则会报异常RuntimeException: A bound value cannot be set
  • 双向绑定(Bidirectional binding):只要A、B其中一个修改,另一个的获取值将同步更新。

3.2 修改监听(ChangeListener)

为属性设置修改事件监听器,当属性值修改时,自动回调传入监听器方法。

四、实现原理解析

与我们熟知的观察者模式不同,通过源码我们可以看到在Observable接口中定义的是InvalidationListener类型监听器添加方法,而在ObservableValue接口中才定义了ChangeListener

由此引出疑问:什么是失效监听器(Invalidation Listener)?这涉及到JavaFx属性绑定的 延迟计算(lazy evaluation) 机制。

4.1 属性绑定原理

如 A.bind(B),当绑定目标对象B更新时,并不是通过修改A自身的值来实现同步的。而是在使用bind()进行绑定时,通过传入的绑定目标对象(无论是直接的 SimpleIntegerProeprty 或是通过 add、divide等方法返回的IntegerBinding对象)来构建维护 observable 字段。当调用get()尝试获取A的值时,则调用 observable.get()来获取。

注:由上述属性绑定逻辑我们可知,当绑定目标改变发生时并不直接重新计算,而是只有当此值被get()请求时,才调用 observable.get() 来返回最新值。因此在刚发生绑定操作或绑定目标修改后,还未get()使用前,则存在“失效”状态【具体逻辑参考后面源码解析】

4.2 监听机制原理

调用 addListener 时,通过自身字段 ExpressionHelper helper 来附加存储监听器,当属性值修改或是解绑时,则通过 markInvalid() 方法调用 ExpressionHelper.fireValueChangedEvent(helper),来回调所有附加的监听器方法。

五、源码解析

绝大部分字段(即类的成员变量,为了避免与 '属性' 混淆,用字段一词代替)都定义在抽象类 IntegerPropertyBase 中,而SimpleIntegerProperty则继承自该抽象类。

		public abstract class IntegerPropertyBase extends IntegerProperty {

		    private int value;  //在非绑定情况下,类本身的值

		    //当使用bind()方法时,以传入对象为基础构建的目标绑定实例【具体逻辑参考下面bind方法源码】,在get()等方法中用到,参考下面方法解释。
private ObservableIntegerValue observable = null; //失效监听器,进行bind()时,则自动构建该监听器。
//作用:【参考下面Listener源码】作为InvalidationListener添加到绑定目标observable中,实现当observable改变时,将本实例设置为Invalid(失效)状态的效果。
//创建时机:【参考下面bind代码】当使用bind()方法时,以自身实例(this)作为参数构建 Listener 对象【Listener 为内部类继承自 InvalidationListener,参考下面代码】
private InvalidationListener listener = null; //当前实例是否有效,创建实例时默认为有效
private boolean valid = true; //用于存储添加的失效或改变监听器
private ExpressionHelper<Number> helper = null; // 获取值的get方法,其逻辑为:若进行过绑定,则调用observable来获取绑定值;若未绑定,则返回本身的值
@Override
public int get() {
valid = true;
return observable == null ? value : observable.get();
} // 根据observable是否为空判断当前是否绑定
@Override
public boolean isBound() {
return observable != null;
} // 解绑方法
@Override
public void unbind() {
if (observable != null) {
value = observable.get(); //解绑时将自身值更新到最新状态
observable.removeListener(listener); //移除失效监听器【关于"失效监听器"参考下面源码解释】
observable = null; //将绑定目标observable置null
}
} //绑定方法
@Override
public void bind(final ObservableValue<? extends Number> rawObservable){
ObservableIntegerValue newObservable;
// …省略newObservable的构建代码。逻辑为:若传入对象是ObservableIntegerValue类型实例则为传入对象本身;否则则以传入对象为基础构建IntegerBinding实例) if (!newObservable.equals(observable)) {
unbind(); //先解绑,若本身未绑定则等于没执行
observable = newObservable; //为绑定目标字段赋值
if (listener == null) {
listener = new Listener(this); //以自身实例为参数构建Listener失效监听器
}
observable.addListener(listener);//将失效监听器添加到绑定目标中
markInvalid(); //设置为失效状态(因为延迟计算机制)
}
} //标记为失效的方法
private void markInvalid() {
if (valid) {
valid = false;
invalidated(); //默认为空实现,子类继承时可自行重写实现(SimpleIntegerProperty未重写实现)
fireValueChangedEvent(); //激活hepler
}
} //官方注释:给所有注册的InvalidationListener和ChangeListeners发送通知(即开始回调各个监听器方法)
protected void fireValueChangedEvent() {
ExpressionHelper.fireValueChangedEvent(helper);
} //属性修改方法
//修改后会调用markInvalid()方法标记为失效,并回调所有附加的监听器
@Override
public void set(int newValue) {
if (isBound()) {
throw new java.lang.RuntimeException((getBean() != null && getName() != null ?
getBean().getClass().getSimpleName() + "." + getName() + " : ": "") + "A bound value cannot be set.");
}
if (value != newValue) {
value = newValue;
markInvalid();
}
} //内部类 失效监听器,在bind()方法中被使用
private static class Listener implements InvalidationListener {
private final WeakReference<IntegerPropertyBase> wref;
public Listener(IntegerPropertyBase ref) {
this.wref = new WeakReference<>(ref);
}
//失效监听器逻辑很简单,直接调用传入实例的markInvalid方法
//即实现了:当绑定目标修改时,则回调该监听器来将"绑定发起属性"置为失效。
@Override
public void invalidated(Observable observable) {
IntegerPropertyBase ref = wref.get();
if (ref == null) {
observable.removeListener(this);
} else {
ref.markInvalid();
}
}
} //…
}

参考

https://www.dummies.com/programming/java/javafx-binding-properties/

http://www.javafxchina.net/blog/2015/08/javafx-properties-binding/

最新文章

  1. Appium移动自动化测试之Eclipse
  2. Spring MVC 学习总结(三)——请求处理方法Action详解
  3. EM算法
  4. Oracle Database Cloud Services
  5. iPhone左下角app图标
  6. .NET DataTable转化为json格式
  7. js 获取月份 格式yy-mm-dd
  8. Andorid Async-HttpClient阅览
  9. C#遍历文件名
  10. IntelliJ IDEA 发布13版本——创造java奇迹
  11. Is it possible to run native sql with entity framework?
  12. Andrew Ng机器学习课程笔记--week9(下)(推荐系统&amp;协同过滤)
  13. DataBase MongoDB高级知识
  14. python 二叉树实现
  15. yum与rpm的区别以及详细介绍
  16. js語句
  17. 洛谷 P1090合并果子【贪心】【优先队列】
  18. Spring Boot 2 (六):使用 Docker 部署 Spring Boot 开源软件云收藏
  19. XJad反编译工具
  20. (原创)拨开迷雾见月明-剖析asio中的proactor模式(一)

热门文章

  1. GIT 安装和升级
  2. javascript如何动态修改iframe的src
  3. ACR Code Pacs
  4. JAVA:使用栈实现一个队列
  5. [转]OpenGL图形渲染管线、VBO、VAO、EBO概念及用例
  6. Python3之logging模块浅析
  7. IntelliJ IDEA Check out from git
  8. how-does-mysql-replication-really-work/ what-causes-replication-lag
  9. Spring cloud微服务安全实战-7-1章节概述
  10. Cannot start service WMSvc on computer &#39;.&#39;.