Skip to content

Vaadin 适配计划与落地方案 ​

本文定义适配边界、实施阶段与验收条件。具体公开签名以适配模块源码为准,运行形态的支持范围以适配工作区 README(norm-vaadin/README.md)为准。

目标与范围 ​

用 Norm 普通字段保存页面状态,以普通函数和闭包表达派生内容,用结果构建器组合真实 Vaadin 组件。提供两个独立运行入口:独立 Jetty、官方 Spring Boot 集成。两者共享组件 API、响应绑定与前端资源构建。

  • Vaadin、Jetty 与 Spring 适配在编译器仓库之外实现,以普通 Norm Module 交付。
  • 不依赖、复制或改造 JavaFX ui 包;桌面示例仅提供表达风格参考。
  • 不为 Vaadin 新增 std 类型、内建类型、关键字或特殊类名识别。
  • FieldHandle 是现有内建能力,不迁入 std,不再定义同名类型。双向绑定是否直接利用其已有参数上下文转换,由定向编译测试确定。
  • 优先使用已有字段观察、上下文、任务和资源协议;若发现通用缺口,先单独论证其职责与必要性。
  • 不调整 micronaut.web,不实现 Micronaut 集成。
  • 不引入通用 DI 框架、虚拟 DOM 或 JavaFX 组件抽象。
  • 未经明确允许不提交 Git、不发布包、不部署。

事实与入口 ​

能力唯一事实入口
内容块、普通 if 与循环结果构建器
尾随闭包、块调用链函数高级规则
属性访问与对象字段Class 语法
字段读写观察fields.norm、FieldObservations
字段上下文捕获与写回FieldHandleExecutionTest
字段赋值与通知FieldWriteNode
任务调度与取消tasks.norm
资源所有权ownership.norm
模块和 Java 制品Java Library Adapter、包管理器
JVM 与 Native 交付应用构建
应用归档中的本地模块与资源快照ApplicationBundleWriterTest
JAR 文件名与打包后模块解析ResolvedJarClasspathTest、BundledJarGraphsTest
JVM 资源连接与流生命周期JvmJarBindingRuntimeTest
应用与编译器的依赖隔离ApplicationClassLoaderIsolationTest

现有观察机制不等于完整 Vaadin 反应式集成;必须验证 Norm 回调穿过 Java 边界后仍处于正确的依赖追踪与 UI 执行上下文。

Java 类型跨包前置项 ​

跨包签名优先引用已声明依赖提供的公开 Java 类型;没有公开所有者的外部类型保持模块内的不透明声明。页面组件与宿主的强类型传递使用同一通用机制,不能用 Any 或 Vaadin 专属转换规避。

类型所有者解析入口为 ProjectJarBindingLinker。NAR 保存公开导出的派生映射供分析使用;运行时根据固定 JAR 与模块依赖复验映射和生成源码。不得按 Java 类名合并任意 Norm 类型。

生成层入口见 BindingPlanner,声明复用与改名导出的定向测试见 JarBindingImportsTest。生成器测试不代替模块依赖解析和归档往返验收。

本地与归档执行、泛型、改名导出、直接与转导出依赖、菱形依赖、类型所有者歧义诊断,以及生成源码校验见 CrossModuleJarBindingTest。

包边界 ​

下表是逻辑模块边界;具体 Java 根制品仍遵循一模块一个根 JAR。聚合模块只组合依赖,不重复生成公开类型。

模块职责生命周期所有者
vaadin 及组件绑定组件、内容构建器、普通字段绑定、页面契约页面或组件范围
jetty.server独立 Servlet 容器、安装入口、监听、Session、WebSocket、关闭独立服务器对象
vaadin.jettyVaadin 安装、页面工厂、独立应用入口委托 jetty.server
vaadin.spring官方 Spring 集成、页面创建、Bean 与作用域衔接Spring 应用上下文
spring.bootBoot 启动与关闭、配置、应用入口Spring 应用上下文

vaadin.spring 不依赖独立 Jetty 启动器。Spring 选择 Jetty 时通过 Boot 官方机制管理服务器。共享底层制品不意味着共享启动责任。

本文中的 norm-vaadin/... 路径属于独立适配开发工作区,不是本仓库文件或文档站页面。工作区内各发布模块拥有各自的 module.norm;实际发布按模块所有权规则组织仓库。Java 构建配置维护桥接、宿主与应用前端制品,Norm 应用通过固定的模块依赖使用这些产物。

页面作者契约 ​

可执行语法以共享页面示例(norm-vaadin/norm/dependencies/example/profile/profile.norm)为准;公开组件签名见组件入口(norm-vaadin/norm/dependencies/vaadin/components.norm)。普通字段双向输入使用现有强类型字段捕获,不能把普通字符串值当作可写地址。页面作者不需要 State<T>、.value 或 computed。

  • 固定属性直接传值,动态属性传闭包。
  • 普通函数在绑定内执行时追踪实际读取;在绑定外调用时仍是普通函数。
  • 依赖标识包括对象身份与字段身份,不改变 String、Boolean 的值语义。
  • 页面对象每个 UI 独立创建;不默认共享用户状态。
  • 普通构建块中的 if、for 保持现有求值语义。
  • 动态结构使用显式区域与按业务键维护的列表;属性变化不重建整页。
  • 输入同步时机与程序写回规则明确,不重复发出业务提交事件。
  • Spring 与独立宿主使用同一页面契约;依赖提供方式位于集成边界。

反应式实现 ​

优先将现有字段观察连接到 Vaadin Signals,利用官方组件绑定的依赖追踪与生命周期,不在适配层另建一套通用响应引擎。

观察页面对象必须发生在执行其绑定之前。绑定执行期间,字段读取进入相应依赖节点;字段修改使节点失效;订阅按每次计算的实际读取重新建立。嵌套业务对象观察范围必须明确,不能宣称观察根对象就自动覆盖任意对象图。

一个 UI 事件可以修改多个普通字段;绑定刷新应看到事件完成后的状态。批处理只规定通知边界,不承诺对 Norm 普通字段赋值进行事务回滚。

派生计算要求只读。若要自动拒绝绑定中的写入,必须在写入前检测,不能先改变字段再抛错并宣称状态未变。现有字段通知发生在写入后,该约束需要单独验证可行实现,不用事后补丁伪装完成。

字段替换、值集合内部变更与 identity 对象内部变更分别验收。已关闭的组件范围解除字段订阅与 Java 回调;动态分支替换与列表删除必须释放旧范围。按键列表需要验证重排、同键数据更新、重复键错误以及编辑状态保持。

执行与资源边界 ​

  • 启动成功以端口已绑定、Vaadin 已初始化为准。
  • 每个应用只有一个启动与关闭所有者。
  • 服务器等待不能持有 Norm 执行锁阻塞事件回调。
  • 后台任务使用现有 TaskScope 与 TaskExecutor 接入 UI 访问;不提供另一套异步语言。
  • 页面关闭后任务取消或结果失效,完成回调不再更新已关闭 UI。
  • 关闭顺序为停止接收、关闭页面及会话、停止容器、释放 Norm 运行域。
  • 启动失败沿同一资源所有权边界清理;不吞掉初始化或关闭错误。
  • 框架线程、Norm 回调和 UI 会话锁必须通过真实并发测试验证。

Spring 的关闭状态以应用上下文为唯一来源;等待需要覆盖通过 Spring 自身入口关闭上下文,并在资源销毁完成后结束。验证入口见 BootApplicationTest(norm-vaadin/spring-boot/src/test/java/dev/normlanguage/spring/BootApplicationTest.java)。

前端与构建 ​

两个宿主复用官方 Vaadin 前端构建。仅使用官方标准组件且符合官方条件时可使用预编译生产 bundle;自定义主题、资源或组件需要实际构建,不能静默遗漏。

生产 bundle 归应用资源模块所有,通用组件桥接和宿主包不携带应用构建配置。共享示例的构建入口为 profile-frontend(norm-vaadin/profile-frontend/build.gradle.kts),其产物通过普通 Norm 模块依赖进入两个宿主。

应用主题使用 Vaadin 25 的 @StyleSheet 入口。应用资源模块通过标准 Java 服务声明注册 AppShellConfigurator;Spring 自动配置据此注册应用包,Shell 的实例化和初始化仍由 Vaadin 负责。示例入口见 ProfileShell(norm-vaadin/profile-frontend/src/main/java/dev/normlanguage/example/profile/ProfileShell.java)与服务声明(norm-vaadin/profile-frontend/src/main/resources/META-INF/services/com.vaadin.flow.component.page.AppShellConfigurator)。

Norm Core 方法体并非普通 Java 方法体,Java 字节码扫描可能看不到实际组件使用。组件使用信息应由唯一的编译/适配输入派生,不让作者额外维护手工组件白名单。产物需要包含启动配置与全部浏览器资源。

应用前端使用官方完整扫描模式收集组件注解,避免按 Java 调用可达性遗漏 Norm 动态调用的组件。配置入口见应用前端构建,资源归档与双宿主验收见 FrontendPackagingTest(norm-vaadin/profile-frontend/src/test/java/dev/normlanguage/example/profile/FrontendPackagingTest.java)、Jetty 自定义组件验收(norm-vaadin/standalone/src/test/java/dev/normlanguage/vaadin/jetty/CustomComponentBrowserTest.java)和 Spring 自定义组件验收(norm-vaadin/spring/src/test/java/dev/normlanguage/vaadin/spring/CustomComponentBrowserTest.java)。

JVM、生产资源和 Native 分开验收。Spring Native 使用官方 AOT 路径;独立 Jetty Native 独立验证反射、资源、初始化和推送。某一路径未验证时如实记录,不将 JAR 可加载或服务可启动当作完整交付。

Spring 的 Bean 定义由可分析的工厂方法提供,运行时页面工厂通过应用上下文的可解析依赖传入。官方 AOT 生成、代理字节码参与编译、构建期间不创建页面,以及独立 JVM 中启用生成代码后的浏览器运行与关闭验收见 SpringAotTest(norm-vaadin/spring/src/test/java/dev/normlanguage/vaadin/spring/SpringAotTest.java);该验收不代表 Norm Native 可运行。

AOT 生成在独立 JVM 中执行,避免运行阶段已加载的代理类影响构建输出。构建工具入口为 VaadinSpringAotProcessor(norm-vaadin/spring-aot/src/main/java/dev/normlanguage/vaadin/aot/VaadinSpringAotProcessor.java),应用标识以 NormSpringApplication(norm-vaadin/spring/src/main/java/dev/normlanguage/vaadin/spring/NormSpringApplication.java)为准。生成类、资源与 Native 提示由应用制品(norm-vaadin/profile-spring/build.gradle.kts)携带,再通过 Norm 普通固定依赖进入构建,不向编译器核心添加 Spring 专属判断。归档内容边界见 AotPackagingTest(norm-vaadin/profile-spring/src/test/java/dev/normlanguage/example/spring/AotPackagingTest.java)。

版本选择以适配构建声明为唯一源,锁定具体制品与内容;不使用浮动版本或改写已发布制品。依赖升级需要核对 Vaadin、Servlet 环境、Spring Boot 和 Jetty 的兼容组合。

实施阶段与完成条件 ​

阶段工作完成条件
P0事实核对、包边界、依赖固定、测试入口设计索引有效;无 ui 依赖与无关 std 改动;定向失败测试可运行
P1字段观察到官方 Signals 的桥接分支依赖、对象隔离、解除订阅、事件批量刷新通过真实 Signals 测试
P2Norm 内容构建器、属性闭包、双向输入普通字段示例编译执行;嵌套函数读取、程序写回、输入事件和组件身份通过测试
P3独立 Jetty 包与 Vaadin 安装浏览器可访问生产页面;两个会话隔离;端口冲突与正常关闭释放资源
P4动态区域、按键列表、页面任务分支资源释放、重排保持身份、旧异步结果失效与 UI 调度通过验收
P5Spring 基础与官方 Vaadin Spring 集成同一页面逻辑在 Boot 下运行;真实 Bean/代理、作用域、启动与关闭验收通过
P6共享资源构建和交付标准组件及自定义资源完整;独立环境启动不依赖开发服务器;Native 路径分别实测
P7代码审查与文档收口无重复类型源、生命周期竞争、无意义 helper 或过程注释;明确支持矩阵与剩余限制

各阶段先编写行为测试再实现,不运行全量测试。低层使用真实 Vaadin/Jetty/Spring 对象,Norm 测试涉及真实宿主绑定,浏览器验收通过实际输入与点击完成;HTTP 200 不代替 UI 验收。

测试证据保存在对应适配模块的测试报告与浏览器验收产物中;此文不重复维护测试数量或实现逻辑。实施 Goal 持续跟踪阶段完成状态,所有必需工作完成后才标记完成。

运行形态的实测支持矩阵与验收入口统一维护在适配工作区 README(norm-vaadin/README.md)。Native 构建成功但启动失败的产物不计为运行支持。

官方参考 ​

Norm 0.25