jface databinding/PojoBindable实现对POJO对象的支持

时间:2022-08-05 22:23:46

POJO对象无法被监控

在jface databinding中,将普通的java bean(有get/set方法但没有通过PropertyChangeSupport实现属性监控)定义为POJO对象。
我们可以对POJO对象通过PojoProperties.value(String propertyName)方法提供IObservableValue实例,但返回的PojoValueProperty实例并没有真正实现对POJO对象的监控(参见PojoValueProperty源码)。
所以UI组件与POJO对象之间建立的数据绑定是单向的,UI组件的数据变化可以同步到POJO对象,但反过来不行。
下面这个示例可以演示这个区别,
运行程序,程序启动时,Text组件的内容被更新成POJO对象属性相同的值。
但按”测试”按钮,修改了POJO对象的属性,但Text控件的值并没有同步变化。

package testwb;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Text;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.widgets.Display;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.jface.databinding.swt.DisplayRealm;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.core.databinding.beans.PojoProperties;

public class TestPojoBinding extends Dialog {

    /** * 数据对象定义 * @author guyadong * */
    public class Configuration {
        private String name;
        public Configuration(String name) {
            super();
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
            System.out.printf("updated %s\n",this.name);
        }
    }
    private DataBindingContext m_bindingContext;
    /** * 成员变量:数据对象 */
    protected Configuration editorConfig=new Configuration("hello!");
    private Text myNametext;

    /** * Create the dialog. * @param parentShell */
    public TestPojoBinding(Shell parentShell) {
        super(parentShell);
    }

    /** * Create contents of the dialog. * @param parent */
    @Override
    protected Control createDialogArea(Composite parent) {
        Composite container = (Composite) super.createDialogArea(parent);
        container.setLayout(null);

        Button btnNewButton = new Button(container, SWT.NONE);
        btnNewButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                editorConfig.setName("word");
            }
        });
        btnNewButton.setBounds(38, 154, 80, 27);
        btnNewButton.setText("测试");

        myNametext = new Text(container, SWT.BORDER);
        myNametext.setBounds(38, 27, 80, 23);

        return container;
    }

    /** * Create contents of the button bar. * @param parent */
    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
        createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
        m_bindingContext = initDataBindings();
    }

    /** * Return the initial size of the dialog. */
    @Override
    protected Point getInitialSize() {
        return new Point(362, 298);
    }
    public static void main(String[] args) {
        Display display = Display.getDefault();
        Realm.runWithDefault(DisplayRealm.getRealm(display), new Runnable() {
            public void run() {
                try {
                    TestPojoBinding setting = new TestPojoBinding(null);
                    setting.open();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
    protected DataBindingContext initDataBindings() {
        DataBindingContext bindingContext = new DataBindingContext();
        IObservableValue observeTextMyNametextObserveWidget = WidgetProperties.text(SWT.Modify).observe(myNametext);
        IObservableValue nameEditorConfigObserveValue = PojoProperties.value("name").observe(editorConfig);
        bindingContext.bindValue(observeTextMyNametextObserveWidget, nameEditorConfigObserveValue, null, null);
        return bindingContext;
    }
}

PropertyChangeSupport

如果想要实现上面例子中数据对象属性与Text组件的内容双向同步绑定。解决方案之一就是改造数据对象Person,通过PropertyChangeSupport实现属性监控。

package testwb;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Text;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.swt.widgets.Display;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.jface.databinding.swt.DisplayRealm;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.core.databinding.beans.BeanProperties;

public class TestPojoBinding2 extends Dialog {
    public class ModelObject {
        private final PropertyChangeSupport changeSupport =
                new PropertyChangeSupport(this);

        public void addPropertyChangeListener(PropertyChangeListener
                listener) {
            changeSupport.addPropertyChangeListener(listener);
        }

        public void removePropertyChangeListener(PropertyChangeListener
                listener) {
            changeSupport.removePropertyChangeListener(listener);
        }

        protected void firePropertyChange(String propertyName, Object oldValue,
                Object newValue) {
            changeSupport.firePropertyChange(propertyName, oldValue, newValue);
        }
    }
    /** * 数据对象定义,继承ModelObject类,获取属性改变时被监控能力 * @author guyadong * */
    public class Person extends ModelObject {
        private String name;
        public Person(String name) {
            super();
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            // 修改set方法,在修改属性的同时,调用firePropertyChange通知所有侦听器属性已经改变
            firePropertyChange("name", this.name, this.name = name);
            System.out.printf("updated %s\n",this.name);
        }
    }
    private DataBindingContext m_bindingContext;
    /** * 成员变量:数据对象 */
    protected Person editorConfig=new Person("hello!");
    private Text myNametext;

    /** * Create the dialog. * @param parentShell */
    public TestPojoBinding2(Shell parentShell) {
        super(parentShell);
    }

    /** * Create contents of the dialog. * @param parent */
    @Override
    protected Control createDialogArea(Composite parent) {
        Composite container = (Composite) super.createDialogArea(parent);
        container.setLayout(null);

        Button btnNewButton = new Button(container, SWT.NONE);
        btnNewButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                editorConfig.setName("word");
            }
        });
        btnNewButton.setBounds(38, 154, 80, 27);
        btnNewButton.setText("测试");

        myNametext = new Text(container, SWT.BORDER);
        myNametext.setBounds(38, 27, 80, 23);

        return container;
    }

    /** * Create contents of the button bar. * @param parent */
    @Override
    protected void createButtonsForButtonBar(Composite parent) {
        createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true);
        createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false);
        m_bindingContext = initDataBindings();
    }

    /** * Return the initial size of the dialog. */
    @Override
    protected Point getInitialSize() {
        return new Point(362, 298);
    }
    public static void main(String[] args) {
        Display display = Display.getDefault();
        Realm.runWithDefault(DisplayRealm.getRealm(display), new Runnable() {
            public void run() {
                try {
                    TestPojoBinding2 setting = new TestPojoBinding2(null);
                    setting.open();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
    protected DataBindingContext initDataBindings() {
        DataBindingContext bindingContext = new DataBindingContext();
        // 为Text组件创建观察对象
        IObservableValue observeTextMyNametextObserveWidget = WidgetProperties.text(SWT.Modify).observe(myNametext);
        // 为数据对象属性创建观察对象
        IObservableValue nameEditorConfigObserveValue = BeanProperties.value("name").observe(editorConfig);
        // 数据绑定
        bindingContext.bindValue(observeTextMyNametextObserveWidget, nameEditorConfigObserveValue, null, null);
        //
        return bindingContext;
    }
}

再运行程序,点击”测试”按钮,Text的值随着数据对象的属性同步改变了。

PojoBindable

上面这个方案已经实现了数据对象和UI组件的双向同步更新,但缺点就是需要对POJO对象进行改造,当项目中有多个POJO对象需要实现与UI组件的双同步更新时,这个工作量也是挺大的。
有没有办法在不改变现有POJO对象的代码的情况下,实现双向同步的目标呢?
有,解决方案就是本文的标题jface databinding/PojoBindable。[注意:这还是个实验项目,使用需谨慎]
PojoBindable利用ASM代码动态修改的技术,通过在运行时为POJO对象添加PropertyChangeSupport 的方法并修改setter方法,提供了一个途径让开发者在不修改自己的POJO类代码的情况下让POJO对象拥有完整的数据绑定能力。
凡事都有代价,使用PojoBindable想不修改POJO对象代码就拥有PropertyChangeSupport能力的话,代价是什么呢?

要修改JVM的运行参数

Pojo Bindable是一个Java Agent,所以为了使用PojoBindable,必须在java程序启动时指定jvm参数,用-javaagent参数指定使用PojoBindable

-javaagent:<your path>/org.eclipse.core.databinding.pojo.bindable_1.0.0.jar

需要-Dbindable.packages指定对哪些pojo对象进行修改java代码

-Dbindable.packages=org.eclipse.core.examples.databinding.pojo.bindable.model

需要 ASM支持

必须将 ObjectWeb ASM加入classpath

关于Pojo Bindable配置的更详细说明参见其官网原文:

https://wiki.eclipse.org/JFace_Data_Binding/PojoBindable#With_Pojo_Bindable

参考资料
《JFace Data Binding/PojoBindable》
《AJFace Data Binding - Tutorial》