Docs

Documentation versions (currently viewingVaadin 24)
Documentation translations (currently viewingChinese)

此页面是从官方文档 http://vaadin.com/docs 机器翻译而来。可能会有错误、不准确之处或误述。Vaadin不保证翻译的准确性、可靠性或及时性,也不对此负责。

会话复制

实现会话复制的最佳实践。

为了使Vaadin Flow应用程序达到高可用性以及可缩减性,正确地在多个服务器之间分布HTTP会话至关重要。如果处理用户请求的服务器不可用,则应当透明地被另一台服务器替代,以使用户在不中断的情况下继续使用应用程序。

可能会出现短暂的离线提示或者加载指示符。然而,浏览器页面并不会被重新加载。因此,用户的工作可以继续进行,而不会丢失任何数据。这要求会话数据存储在共享或分布式存储中,以便新创建的HTTP会话能够填充最新用户数据,从而实现会话复制。

Kubernetes Kit 会话复制

Kubernetes Kit通过后端会话存储(例如Hazelcast或Redis)帮助实现会话复制,在每个请求中传递HTTP会话数据。当某个服务器不可用时,替代它的服务器从会话存储加载用户数据,并填充新的服务端HTTP会话对象。

为了将信息传输到会话存储中,HTTP会话属性使用 Java序列化 编码为二进制格式。

为了实现会话复制,应用程序代码应确保所有存储在HTTP会话中的对象能够进行Java序列化。这些对象必须实现一个特殊的标记接口:java.io.Serializable。无法序列化的对象(例如第三方类)必须声明为`transient`。这样,它们会在序列化过程中被跳过。在反序列化后,transient字段的值为null。

对于Spring应用程序,Kubernetes Kit还能够克服这个限制。序列化期间会识别引用Spring bean的transient字段,并将元数据与会话属性一并存储。在反序列化时,匹配到元数据的transient字段会重新注入相应的Spring bean。

transient字段的检查和注入操作是通过Java反射机制实现的。出于性能考虑并避免反射访问检查所带来的问题,将这种检查限制为仅对应用程序自身的类进行是比较理想的。

通过配置`vaadin.serialization.transients.include-packages`和`vaadin.serialization.transients.exclude-packages`属性,可以限制仅检查符合包路径规则的类。建议排除不涉及会话的包。

Source code
限制Transient字段检查范围的配置示例
vaadin.serialization.transients.include-packages=com.example.app.ui,com.example.app.data
vaadin.serialization.transients.exclude-packages=com.example.app.ui.utils

Vaadin 与 HTTP 会话

一个Vaadin应用程序由UI对象组成,这些UI对象是表示与浏览器交互的网页的组件树的根节点。UI集合到[classname]`VaadinSession`中,存储于HTTP会话,以便跨请求保留服务器端状态。因此,必须谨慎编写应用程序代码,以确保其可序列化,从而达到会话复制目的。

有关序列化流程的更多信息,请参考Java语言文档的 序列化部分。

会话复制的相关技巧

以下部分包含实现完整序列化Vaadin应用程序的最佳实践。

Collaboration Kit

如果应用程序中使用了Collaboration Kit,可能需要遵循一些特定的序列化指引

UI组件成员的可序列化对象

在编写组件时,应确保所有非transient成员实现[classname]Serializable

所有Vaadin UI组件和监听器已经被标记为可序列化。因此,如果一个类继承或实现了它们中的一个,就无需显式地将该类声明实现`Serializable`接口。但在引用到UI视图图中的非UI类时,需要将其显式标记为可序列化。

例如,如果一个视图持有数据传输对象或是配置对象的引用,那么该对象必须被标记为可序列化。此对象的所有成员同样需要可序列化:

Source code
Java
class ContactForm extends VerticalLayout { 1

    private Product product; 2

}

class Product implements Serializable { 3

    private Category category; 4
}

class Category implements Serializable { 5

}
  1. [classname]`ContactForm`类继承自Vaadin组件,该组件本身已作为可序列化标记。

  2. [classname]`ContactForm`类含有[classname]`Product`类型的成员。因此[classname]`Product`也必须实现可序列化。

  3. [classname]`Product`类通过扩展[interfacename]`Serializable`接口变为可序列化。

  4. [classname]`Product`类含有[classname]`Category`类型的成员。因此[classname]`Category`类也必须实现可序列化。

  5. [classname]`Category`类通过扩展[interfacename]`Serializable`接口变为可序列化。

非可序列化对象使用Transient字段

如果一个UI组件需要存储非可序列化对象实例的引用,则必须将其声明为transient。在Spring项目中,Kubernetes Kit能够检测这些字段并在反序列化后自动注入。

非Spring项目中,transient字段应该手动处理,可以使用Java序列化hooks(比如http://docs.oracle.com/en/java/javase/17/docs/specs/serialization/output.html#the-writeobject-method[writeObject]、[writeReplace]、[readObject]、[readResolve]等方法)。

(此处跳过Java源码示例的再次翻译,下同。)

函数接口的可序列化变体

组件可能使用Java函数接口,允许客户端代码提供可执行代码块,例如回调函数。Vaadin在[packagename]`com.vaadin.flow.function`包中提供了常用Java函数接口的可序列化版本。

Lambda表达式中使用不可序列化对象

在使用lambda表达式且目标接口是[classname]`Serializable`的情况下,所捕获的外部对象也必须可序列化。若存在不可序列化对象,可能导致问题。

在Spring工程中,使用Kubernetes Kit,则可利用transient字段自动注入来解决此问题。

序列化与反序列化回调

Kubernetes Kit提供`SessionSerializationCallback`接口来定义自定义的序列化与反序列化错误处理以及成功后的回调方法。

非可序列化组件包装器

包装组件允许一个本身不可序列化的组件使用提供的序列化和反序列化函数进行序列化与恢复。仅当没有其他替代方案时使用这种方式。

会话复制常见问题

即使应用了上述提示,序列化或反序列化过程中仍可能出现问题导致会话复制失败。

Caution
使用扩展调试信息时序列化可能产生的副作用。
系统属性 sun.io.serialization.extendedDebugInfo 在序列化过程中对产生更详细的异常信息很有帮助。这时会使用 toString() 方法表示序列化的对象,但在极少数情况下,这可能导致与序列化本身无关的问题。例如,在使用 Hibernate 时,PersistentList.toString() 将强制初始化延迟加载的集合。如果此时不存在活动的 Hibernate 会话,将会抛出异常。

以下章节介绍了序列化和反序列化的常见问题。

SerializedLambda ClassCastException

Vaadin应用程序通常广泛使用lambda表达式进行监听器、binder等组件。当序列化lambda时,可能出现[classname]ClassCastException。通常原因是lambda捕获自身引用。推荐使用匿名类替代lambda解决问题。

Kubernetes Kit提供了专门的工具来帮助开发人员在开发过程中识别序列化问题。