//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/

从 SimpleIntegerProperty 看 Java属性绑定(property binding) 与 观察者模式(Observable)的更多相关文章

  1. 深度解析qml引擎---(2)绑定(binding)

    强烈的希望是人生中比任何欢乐更大的兴奋剂.--尼采 上一篇文章讲了QML引擎加载qml文件的过程,大体过程是,解析qml文件,然后为文件中的每个元素创建对应的c++对象.例如,qml文件中如果使用了T ...

  2. java之多态(Polymorphic)、动态绑定(Dynamic Binding)、迟绑定(Late Binding)

    今天,我们来说说java面向对象最核心的东西,多态.通过多态可以使我们的程序可复用性达到极致,这就是我们为什么要学多态的原因. “多态”(Polymorphic)也叫“动态绑定”(Dynamic Bi ...

  3. 控制文本和外观------Attr Binding(attr属性绑定)

    Attr Binding(attr属性绑定) 目的 attr 绑定提供了一种方式可以设置DOM元素的任何属性值.你可以设置img的src属性,连接的href属性.使用绑定,当模型属性改变的时候,它会自 ...

  4. 总结学习! xml与java对象转换 --- JDK自带的JAXB(Java Architecture for XML Binding)

    JAXB(Java Architecture for XML Binding) 是一个业界的标准,是一项可以根据XML Schema产生Java类的技术.该过程中,JAXB也提供了将XML实例文档反向 ...

  5. Spring Boot 2.0的属性绑定

    Spring Boot2.0的属性绑定 原文从Spring boot第一个版本以来,我们可以使用@ConfigurationProperties注解将属性绑定到对象.也可以指定属性的各种不同格式.比如 ...

  6. WPF属性绑定实现双向变化

    WPF依赖项属性可以实现属性的绑定,成功绑定之后只要修改后台绑定的属性,即可UI同步自动更新绑定的值,无需手动刷新界面:同样,前台的值变化后,通过获取绑定的属性值也可获取UI变化后的值,实现双向变化的 ...

  7. [XAML]类似WPF绑定的Binding的读取方法

    在WPF的XAML里,依赖属性可以使用基于BindingBase之类的MarkupExtensin 读取XAML时,会自动的把该BindingBase转换为BindingExpressionBase ...

  8. html标签属性(attribute)和dom元素的属性(property)

    简介 attribute和property都有属性之意,但对于attribute和property的区分其实并不难.从对象来说,attribute是html文档上标签属性, 而property则是对应 ...

  9. Android动画主要包含补间动画(Tween)View Animation、帧动画(Frame)Drawable Animation、以及属性动画Property Animation

    程序运行效果图: Android动画主要包含补间动画(Tween)View Animation.帧动画(Frame)Drawable Animation.以及属性动画Property Animatio ...

随机推荐

  1. 阿里云服务器安装配置配置MySQL

    1.先更新软件 输入 yum -y update 2.下载MySql安装包 rpm -ivh http://dev.mysql.com/get/mysql57-community-release-el ...

  2. vue+elementui搭建后台管理界面(8 同步/异步获取数据渲染table)

    elementui已经封装好了 el-table 组件,只需要指定 data 数据源即可,因此通常在 vue 实例生命周期的 created 阶段,从数据库获取数据,再将返回的数据绑定到 data 如 ...

  3. 子查询优化 - Hyper

    Unnesting Arbitrary Queries - T Neumann, A KemperThe Complete Story of Joins (in HyPer) - Thomas Neu ...

  4. gitignore文件示例

    /target/ !.mvn/wrapper/maven-wrapper.jar ### STS ### .apt_generated .classpath .factorypath .project ...

  5. 使用expect快速登录线上机器

    背景: 公司登陆线上服务器一般都要经过跳板机才能登陆,过程比较麻烦,如果要频繁登陆某个机器而且机器名不好记很费劲,使用expect自动登陆会方便很多 实现: 使用expect脚本自动登陆: #!/us ...

  6. (转)tomcat 安全配置文档

    原文:https://www.cnblogs.com/heaven-xi/p/9961354.html 1.配置文档中使用$CATALINA_HOME变量声明为tomcat的安装目录并明确写出了tom ...

  7. ubantu使用ssh服务

    Secure Shell(SSH)是一种加密网络协议,用于在不安全的网络上安全地运行网络服务.利用SSH可以实现加密并安全地远程登录计算机系统. Ubuntu安装后默认只有ssh客户端,即只能在Ubu ...

  8. PHP 基于 Jenkins ansible 动态选择版本进行自动化部署与回滚(第二版)

    PHP 基于 Jenkins ansible 动态选择版本进行自动化部署与回滚(第二版) 先看流程图: 大概介绍一下: 版本选择使用jenkins 中的 git parameter 插件实现 回滚方式 ...

  9. LeetCode_412. Fizz Buzz

    412. Fizz Buzz Easy Write a program that outputs the string representation of numbers from 1 to n. B ...

  10. [LeetCode] 685. Redundant Connection II 冗余的连接之 II

    In this problem, a rooted tree is a directed graph such that, there is exactly one node (the root) f ...