//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() {
@Override
public void changed(ObservableValue extends Number> observable, Number oldValue, Number newValue) {
if(newValue < 333)
System.out.println("当前小于333");
}
});
三、机制概述
从表现上来看有两个特性
3.1 属性绑定(property binding)
允许同步两个属性的值,其中一个修改时,另一个属性的获取值会同步更新。
有两个绑定方法 bind
与 binBidirectional
分别对应两种绑定方式:
- 单向绑定(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 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 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/