在模型类中使用 javafx.beans 属性

2022-09-01 13:19:12

在模型类中使用 JavaFX bean 属性是正确的做法吗?

我想知道在模型类中使用属性是否是一种很好的做法,以便能够更轻松地将它们与视图组件绑定。我不担心这些库将来的可用性,因为我的程序将在JRE8或更高版本上运行,但是在模型类中使用JavaFX库的性质使我持怀疑态度,我担心当前和未来的不兼容性,特别是因为我想使用Hibernate来持久化这些属性。

注意:我使用纯 JavaFX 环境,在我的应用程序中永远不需要 Swing 兼容性。


答案 1

我将在这里提出一些不同意见。

JavaFX Properties 和 JPA

正如我对jewelsea的答案所评论的那样,只要您使用“属性访问”而不是“字段访问”,就可以将基于JavaFX属性的bean与JPA一起使用。我链接到那里的博客文章对此进行了更详细的介绍,但基本思想是,任何注释都应该在方法上,而不是在字段上。据我所知,这确实阻止了将任何只读JavaFX属性模式与JPA结合使用,但我从来没有真正觉得JPA在只读属性(即获取方法和没有集合方法)方面玩得很好。get...()

序列化

与我对jewelsea答案的评论相反,并且由于几周的时间来处理这个问题(并且我被置于使用JavaFX属性在JavaFX客户端复制多个实体类的位置),我认为可以解决JavaFX属性缺乏序列化的问题。关键的观察结果是,您实际上只需要序列化属性的包装状态(例如,不需要任何侦听器)。您可以通过实现 来执行此操作。 是需要您填写 和 方法的子接口。可以实现这些方法以仅外部化由属性包装的状态,而不是属性本身。这意味着,如果你的实体被序列化,然后反序列化,你最终会得到一个新的属性实例,并且任何侦听器都不会被保留(即侦听器有效地成为),但据我所知,这将是任何合理的用例中想要的。java.io.ExternalizableExternalizableSerializablereadExternal(...)writeExternal(...)transient

我尝试了以这种方式定义的豆子,这一切似乎都很好用。此外,我还运行了一个小实验,在客户端和宁静的 Web 服务之间传输它们,使用 Jackson 映射器在 JSON 表示形式之间转换。由于映射器仅依赖于使用 get 和 set 方法,因此这工作正常。

一些注意事项

有几点需要注意。与任何序列化一样,拥有无参数构造函数非常重要。当然,由 JavaFX 属性包装的所有值本身都必须是可序列化的 - 同样,这与任何可序列化的 Bean 的规则相同。

关于 JavaFX 属性通过副作用工作的观点得到了很好的理解,在将这些属性(在某种程度上,这些属性在设计时考虑了单线程模型)移动到潜在的多线程服务器时需要小心。一个好的经验法则可能是,如果您使用此策略,则侦听器应该只在客户端注册(请记住,这些侦听器在传输回服务器方面是暂时的,无论是通过序列化还是通过JSON表示)。当然,这表明在服务器端使用这些可能是一个糟糕的设计;它成为拥有“所有人的所有事物”的单个实体的便利性(JavaFX客户端的可观察属性,可序列化以进行持久性和/或远程访问,以及JPA的持久映射)与公开功能(例如可观察性)之间的权衡,如果它可能不完全合适(在服务器上)。

最后,如果您确实使用JPA注释,则这些注释具有运行时保留,这意味着(我认为)您的JavaFX客户端将需要类路径上的javax.persistence规范)。

以下是这种“所有季节的男人”实体的示例:

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.time.MonthDay;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/**
 * Entity implementation class for Entity: Person
 *
 */
@Entity

public class Person implements Externalizable {


    private static final long serialVersionUID = 1L;

    public Person() {

    }

    public Person(String name, MonthDay birthday) {
        setName(name);
        setBirthday(birthday);
    }

    private final IntegerProperty id = new SimpleIntegerProperty(this, "id");

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    public int getId() {
        return id.get();
    }

    public void setId(int id) {
        this.id.set(id);
    }

    public IntegerProperty idProperty() {
        return id ;
    }

    private final StringProperty name = new SimpleStringProperty(this, "name");

    // redundant, but here to indicate that annotations must be on the property accessors:
    @Column(name="name")
    public final String getName() {
        return name.get();
    }

    public final void setName(String name) {
        this.name.set(name);
    }

    public StringProperty nameProperty() {
        return name ;
    }

    private final ObjectProperty<MonthDay> birthday = new SimpleObjectProperty<>();

    public final MonthDay getBirthday() {
        return birthday.get();
    }

    public final void setBirthday(MonthDay birthday) {
        this.birthday.set(birthday);
    }

    public ObjectProperty<MonthDay> birthdayProperty() {
        return birthday ;
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeInt(getId());
        out.writeObject(getName());
        out.writeObject(getBirthday());
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        setId(in.readInt());
        setName((String) in.readObject());
        setBirthday((MonthDay) in.readObject());
    }

}

答案 2

JavaFX属性设计

JavaFX 属性的设计使得您无需运行 JavaFX 程序即可使用它们。Oracle Using JavaFX Properties and Binding Tutorial 的章节演示了这种用法(例如,用于对账单属性进行建模的 Bill 类)。本教程中的示例仅使用 JavaFX 应用程序运行标准 Java 程序,而不是 JavaFX 应用程序。因此,您可以一般地使用属性和绑定,而无需对 JavaFX 运行时有额外的要求。这意味着,例如,您可以在服务器端应用程序中使用 JavaFX 属性和绑定。main

“正确”的做法

好吧,所以你可以做到,但它是“正确”的做法吗?

我不认为很多人以这种方式使用JavaFX属性。其中一个原因很简单,因为JavaFX属性是相当新的。我不认为在模型对象中使用JavaFX属性是“错误的”。

警告

JavaFX属性不支持Java序列化(我的意思是直接支持可序列化接口)。许多服务器端Java技术可能需要模型序列化,并且它们无法序列化任何使用JavaFX属性的对象。

JavaFX 属性本身不是容器感知的,并且通过副作用起作用(例如,更改属性可能会触发对另一个绑定值的更新),因此请注意这一点,并确保这种处理在您的环境中是一种可接受的方法。特别是,请注意不要在多线程服务器环境中生成不需要的争用条件(JavaFX 客户机应用程序通常对此不太小心,因为 JavaFX 通常主要作为单线程环境运行)。

JavaFX Properties 和 Hibernate/JPA

我不认为将JavaFX属性混合到Hibernate(或JPA)实体类中是一个好主意。我没有看到任何人这样做。Hibernate本身不知道JavaFX属性,并且通常被设计为针对像Strings和int这样的Java原语,所以我不知道它如何能够自动将JavaFX属性映射到数据库字段。

您可能需要一个设置来定义实体类层次结构,并为基于 JavaFX 属性的模型类定义并行层次结构,最后需要一个在两者之间映射的映射器层。这种体系结构设置本质上是一个 MVVM 模型。模型 (M) 是 Hibernate 实体类,视图模型 (VM) 是基于 JavaFX 属性的模型。


推荐