古巴7:有什么新变化?


三年前,我们宣布发布CUBA 6 。 该版本成为革命性的:我们不再使用封闭的专有许可证,而是开始根据Apache 2.0许可证自由分发框架。 那时,我们甚至还无法从长远角度了解这将如何影响框架的开发。 CUBA社区开始呈指数增长,我们面临着使用该框架的所有可能(有时是不可能)的方式。 现在,我们介​​绍CUBA 7 。 我们希望此版本将使社区的所有成员更加轻松愉快地进行开发:从刚熟悉CUBA和Java的初学者到在大型公司级别拥有多个完成项目的经验丰富的开发人员。


开发工具


CUBA Studio是CUBA成功的重要部分。 这种开发环境大大简化了每个Java项目中完成的典型任务的实现,从而将它们简化为在可视设计器中创建简单的配置。 您不需要了解所有Persistence API注释属性,Gradle语法或Spring配置的精妙之处,就可以开发出功能齐全的完整CRUD应用程序-CUBA Studio负责创建典型的代码。



Studio是一个单独的Web应用程序,它引起了许多重大限制:


  • 首先,Studio并不是完整的IDE,因此开发人员必须在Studio和IntelliJ IDEA或Eclipse之间切换才能开发业务逻辑,同时使用便捷的导航,代码完成和其他必要的操作,这有些令人讨厌。
  • 其次,所有这些神奇的简单性都建立在我们花费大量工作来编写用于解析和生成源代码的算法上。 实现更高级的功能将意味着转向成熟的IDE的开发-这对我们来说是一项雄心勃勃的任务。

我们决定依靠另一个巨头来克服这些限制,并基于IntelliJ IDEA构建了Studio。 现在,您既可以将Studio安装为独立应用程序(IntelliJ IDEA捆绑包),也可以安装为IDEA的插件。



这给我们带来了新的机会:


  • 支持其他JVM语言(尤其是Kotlin)
  • 出色的热部署
  • 直观的项目范围导航
  • 更智能的提示和代码生成器

当前,我们正在积极开发Studio的新版本-我们从旧版本转移功能,并使用IntelliJ平台的功能添加新功能。 在不久的将来-将CUBA特定的编辑器转换为IntelliJ组件,并改善在项目代码中的导航。


技术堆栈更新


按照传统,CUBA核心技术栈已更新为新版本:Java 8/11,Vaadin 8,Spring 5。


默认情况下,新项目使用Java 8,但是您可以通过将以下代码添加到build.gradle文件中来指定Java版本:


subprojects { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } 

一个特别大的问题是对Vaadin 8的升级,其中数据绑定API发生了很大变化。 幸运的是,CUBA使开发人员免受Vaadin的内部组件的影响,并将其包装在自己的API中。 CUBA团队在不更改CUBA API的情况下完成了内部组件的更新工作。 这意味着完全保留了兼容性,并且可以在将项目迁移到CUBA 7之后立即利用Vaadin 8的所有新功能。


更改列表中提供了更新的依赖关系的完整列表


新屏幕API


由于CUBA从未在Web客户端模块中正式发布过屏幕API,所以本节可能称为“第一屏幕API”。 这是历史上发生的,包括由于在初始阶段出现了一些假设:


  • 面向声明的方法-可以声明方式描述的所有内容都必须在屏幕描述符中声明,而不是在控制器代码中声明。
  • 标准屏幕(浏览器和编辑器)提供了某些常规功能,因此无需更改它。


当第一千名成员加入我们的社区时,我们意识到“标准” CRUD屏幕上有多少不同的要求。 所有这些要求都远远超出了最初的功能。 但是,很长一段时间以来,由于在初始阶段提出了另一种架构原则:开放继承,我们就可以在不更改API的情况下满足实现非典型屏幕行为的要求。 实际上,Open Inheritance意味着您可以重写主类的任何公共或受保护的方法,以根据需要自定义其行为。 这看似神奇的灵丹妙药,但实际上,即使在短期内,您也不能依靠它。 如果重写的方法被重命名,删除或在框架的未来版本中根本不使用怎么办?


因此,为了响应社区不断增长的需求,我们决定引入新的屏幕API。 该API提供了清晰的(没有任何隐藏的魔术),灵活且易于使用的扩展点,这些扩展点保证不会长时间更改。


屏幕声明


在CUBA 7中,声明屏幕非常简单:


 @UiController("new-screen") // screen id public class NewScreen extends Screen { } 

上面的示例显示屏幕标识符直接在控制器类声明的上方明确定义。 换句话说,屏幕ID和控制器类现在以独特的方式相互匹配。 因此,我们有一个好消息:现在您可以直接通过控制器类型安全地访问屏幕:


 @Inject private ScreenBuilders screenBuilders; @Subscribe private void onBeforeClose(BeforeCloseEvent event) { screenBuilders.screen(this) .withScreenClass(SomeConfirmationScreen.class) .build() .show(); } 

屏幕描述符成为屏幕的可选部分。 可以通过编程方式创建UI或将其声明为xml屏幕描述符,该描述符由控制器类上的@UiDescriptor批注定义。 这使控制器和标记更易于阅读和理解-这种方法与Android开发中使用的方法非常相似。


以前,还需要在web-screens.xml文件中注册屏幕句柄并为其分配标识符。 在CUBA 7中,出于兼容性目的而保存了该文件,但是创建屏幕的新方法不需要进行此类注册。


屏幕生命周期


新的API引入了简单且不言自明的屏幕生命周期事件:


  • 初始化
  • 后初始化
  • 放映前
  • 放映后
  • 收盘前
  • 收盘后

您可以按以下方式订阅CUBA 7中的所有事件:


 @UiController("new-screen") public class NewScreen extends Screen { @Subscribe private void onInit(InitEvent event) { } @Subscribe private void onBeforeShow(BeforeShowEvent event) { } } 

与旧方法相比,新API显示出我们不会重叠在初始化时隐式调用的钩子方法,而是显式定义用于处理特定的特定屏幕生命周期事件的逻辑。


事件处理和功能代表


在上一节中,我们学习了如何订阅生命周期事件,但是其他组件又如何呢? 像在6.x版本中一样,使用init()方法初始化屏幕时,是否仍需要将所有必需的侦听器倒入同一堆中? 新的API非常一致,因此您可以按照与屏幕生命事件相同的方式订阅其他事件。


考虑一个具有两个UI元素的简单示例:一个按钮和一个用于显示某种货币金额的字段; XML描述符将如下所示:


 <?xml version="1.0" encoding="UTF-8" standalone="no"?> <window xmlns="http://schemas.haulmont.com/cuba/screen/window.xsd" caption="msg://caption" messagesPack="com.company.demo.web"> <layout> <hbox spacing="true"> <currencyField id="currencyField" currency="$" currencyLabelPosition="LEFT"/> <button id="calcPriceBtn" caption="Calculate Price"/> </hbox> </layout> </window> 

当您单击按钮时,我们从后端调用服务,该服务返回一个数字,我们将其写入金额字段。 该字段应根据价格值更改样式。


 @UiController("demo_MyFirstScreen") @UiDescriptor("my-first-screen.xml") public class MyFirstScreen extends Screen { @Inject private PricingService pricingService; @Inject private CurrencyField<BigDecimal> currencyField; @Subscribe("calcPriceBtn") private void onCalcPriceBtnClick(Button.ClickEvent event) { currencyField.setValue(pricingService.calculatePrice()); } @Subscribe("currencyField") private void onPriceChange (HasValue.ValueChangeEvent<BigDecimal> event) { currencyField.setStyleName(getStyleNameByPrice(event.getValue())); } private String getStyleNameByPrice(BigDecimal price) { ... } } 

在上面的示例中,我们看到两个事件处理程序:一个事件处理程序在按下按钮时调用,另一个事件处理程序在货币字段更改其值时启动-一切都很简单。


现在假设我们需要检查价格并确保其价值为正。 这可以“先行”完成-在屏幕初始化期间添加验证器:


 @UiController("demo_MyFirstScreen") @UiDescriptor("my-first-screen.xml") public class MyFirstScreen extends Screen { @Inject private CurrencyField<BigDecimal> currencyField; @Subscribe private void onInit(InitEvent event) { currencyField.addValidator(value -> { if (value.compareTo(BigDecimal.ZERO) <= 0) throw new ValidationException("Price should be greater than zero"); }); } } 

在实际的应用程序中,一段时间后,初始化方法将是一堆混乱的初始化器,验证器,侦听器等。 为了解决此问题,CUBA具有有用的@Install注释。 让我们看看它对我们的案例有何帮助:


 @UiController("demo_MyFirstScreen") @UiDescriptor("my-first-screen.xml") public class MyFirstScreen extends Screen { @Inject private CurrencyField<BigDecimal> currencyField; @Install(to = "currencyField", subject = "validator") private void currencyFieldValidator(BigDecimal value) { if (value.compareTo(BigDecimal.ZERO) <= 0) throw new ValidationException("Price should be greater than zero"); } } 

实际上,我们将货币字段验证逻辑委托给屏幕中的currencyFieldValidator方法。 乍一看似乎有些复杂,但是开发人员出乎意料的是很快就习惯了这种添加功能的方法,并立即开始使用它。


屏幕构建器,通知,对话框



CUBA 7具有一组有用的组件以及一个便捷的API:


  • ScreenBuilders结合了流畅的工厂,以创建用于查看和编辑实体的标准屏幕以及自定义屏幕。 以下示例显示了如何从另一个屏幕打开一个屏幕。 请注意,build()方法无需返回强制转换即可立即返回所需类型的屏幕:
     CurrencyConversions currencyConversions = screenBuilders.screen(this) .withScreenClass(CurrencyConversions.class) .withLaunchMode(OpenMode.DIALOG) .build(); currencyConversions.setBaseCurrency(Currency.EUR); currencyConversions.show(); 
  • ScreenBuilders API相比Screens组件为创建和显示屏幕提供了较低的抽象层次。 如果您突然需要在一个周期中浏览所有这些屏幕,它还提供对CUBA应用程序中所有打开的屏幕( Screens#getOpenedScreens )的信息的访问。
  • NotificationsDialogs组件提供了一个方便且字面意义上的自我文档化API。 以下是创建和显示对话框和通知的示例:
     dialogs.createOptionDialog() .withCaption("My first dialog") .withMessage("Would you like to thank CUBA team?") .withActions( new DialogAction(DialogAction.Type.YES).withHandler(e -> notifications.create() .withCaption("Thank you!") .withDescription("We appreciate all community members") .withPosition(Notifications.Position.MIDDLE_CENTER) .withHideDelayMs(3000) .show()), new DialogAction(DialogAction.Type.CANCEL) ) .show(); 

资料绑定


CUBA不仅使用先进的可视化开发工具和强大的代码生成系统,还为后台部门提供了极其快速的用户界面开发,而且还提供了一系列现成的丰富组件。 这些组件只需要知道它们正在使用什么数据,其余的将自动完成。 例如,下拉列表,日历,具有内置CRUD操作的表等。


在版本7之前,数据绑定是通过所谓的数据源进行的-数据对象包装了一个或多个实体,以便它们对组件进行反应性绑定。 这种方法效果很好,但是就实现而言,这是一个整体。 整体架构通常在配置时会遇到问题,因此在CUBA 7中,这个巨大的鹅卵石被分为三个部分来处理数据:


  • 数据加载器是数据容器的数据提供者。 加载器不存储数据,它只是将所有必要的查询参数传递给数据仓库,并将结果数据放入数据容器中。
  • 数据容器保存加载的数据(一个或几个实体)并将其提供给数据组件:这些实体中的所有更改都将转移到相应的组件中,反之亦然,组件中的所有更改都将导致数据容器中实体的相应更改。
  • Datacontext(数据上下文)是一个跟踪更改并存储所有更改的实体的类。 只要受监视的实体的属性发生变化,它们就会被标记为脏实体,并且当调用DataContext的commit()方法时,DataContext会将脏实例保存在中间件上。

因此,在处理数据方面具有灵活性。 一个人工的例子:加载器可以从RDBMS中选择UI中的数据,上下文可以将更改保存到REST服务。


在CUBA 6.x中,您将需要为此编写自己的数据源,该数据源可以与RDBMS和REST一起使用。 在CUBA 7中,您可以使用可以与数据库一起使用的标准加载器,并仅编写其上下文实现以用于REST。


可以在屏幕描述符中声明用于处理数据的组件,也可以使用专门的工厂DataComponents以编程方式创建它们。


其他


Uff ...描述了新的屏幕API的最重要部分,因此让我们简要列出Web客户端级别的其他重要功能:


  • URL历史记录和导航 。 此功能解决了一个非常常见的SPA问题-Web浏览器中后退按钮的行为并不总是正确的。 现在,它提供了一种将路由分配给应用程序屏幕的简便方法,并允许API在URL中显示屏幕的当前状态。
  • 用Form代替FieldGroupFieldGroup是用于显示和更改一个实体的字段的组件。 它在运行时显示字段的UI。 换句话说,如果实体具有Date字段,它将显示为DateField 。 但是,如果要以编程方式使用此字段,则需要将其输入到屏幕控制器中,然后手动将其强制转换为正确的类型( 在我们的示例中为DateField )。 如果稍后我们将字段的类型更改为其他类型,则我们的应用程序将在运行时崩溃。 表单通过显式声明字段类型来解决此问题。 有关该组件的更多信息,请参见此处
  • 第三方JavaScript组件的集成已大大简化;请阅读有关在CUBA应用程序中嵌入自定义JavaScript组件的文档
  • HTML / CSS属性现在可以直接从xml屏幕描述符中轻松定义,也可以通过编程设置。 在此处可以找到更多信息。

后端模块的新功能


上一节关于新的屏幕API的内容比我预期的要多,因此在本节中,我将做简短介绍。


实体变更事件


Entity Changed事件是Spring应用程序中的一个事件,当实体进入数据存储,被物理放置且距提交仅一步之遥时就会触发该事件。 处理此事件时,您可以配置其他检查(例如,在确认订单之前检查仓库中的货物可用性)并更改数据(例如,重新计算结果),使其在其他事务(当然,如果您具有级别)可见读取已提交的隔离)。 此事件也可能是通过引发异常来中止事务的最后机会,这在某些棘手的情况下可能很有用。


还有一种在提交后立即处理Entity Changed事件的方法。


您可以在文档的本章中查看示例。


交易数据管理器


开发应用程序时,我们通常使用分离的实体-不在任何事务上下文中的实体。 但是,并非总是可以使用分离的实体,尤其是当您需要完全遵守ACID要求时-可以使用事务数据管理器时就是这种情况。 他与常规经理非常相似,但在以下方面有所不同:


  • 他可以加入现有交易(在此交易的上下文中称为)或创建自己的交易。
  • 它没有提交方法,但是有一个保存方法不会立即提交,而是等待当前事务提交。

这是其用法的一个例子


JPA回调


最后,CUBA 7支持JPA回调。 为了不重复使用这些回调的已知材料,我只在此处保留一个链接 。 在此材料中,回调的主题已完全公开。


兼容性如何?



对于任何主要发行版,这都是一个公平的问题,尤其是在存在许多重要更改时! 我们开发了所有这些向后兼容的新功能和API:


  • CUBA 7支持旧的API,并通过幕后的新API实现:)
  • 我们还提供了用于通过旧API进行数据绑定的适配器。 这些适配器将完全适合根据旧方案创建的屏幕。

好消息是,从版本6到版本7的迁移过程应该非常简单。


结论


在结束技术审查时,我要指出,还有其他重要的创新,尤其是在许可领域:


  • Studio的10个实体限制现已删除
  • “报告”,“ BPM”,“图表和地图”以及“全文本搜索”的附件现已免费提供并且是开源的。
  • Studio的商业版本在实体,屏幕,菜单和其他平台元素的可视化设计人员的帮助下增加了便利性,而免费版本则侧重于处理代码。
  • 请注意,对于6.x版和更早版本,Platform和Studio的许可条件保持不变!

最后,让我再次感谢社区的支持和反馈。 希望您喜欢版本7! 传统上,完整信息可在官方变更日志中获得

Source: https://habr.com/ru/post/zh-CN447538/


All Articles