序言

CUBA 平台是一个开源框架,目的是为了将业务应用系统的开发过程流程化。框架集成了可靠的架构、开箱即用的企业级组件和高效的工具,因此您能比以往更快的交付现代 Web 应用程序。

在这个快速开始教程,我们将介绍一些 CUBA 的入门知识,开发一个非常简单,但是功能完备的会议计划应用系统。这个例子会展示创建任何 Web 应用程序都需要的三个功能:如何设计数据模型、如何操作数据、如何创建业务逻辑,此外,还将演示如何用 CUBA 创建用户界面。实际上通过观看这个入门教程,足以使您能开始开发您自己的 CUBA 应用程序。准备开始,这个入门教程中,我们将会使用 CUBA Studio,所以请在开始前先安装并接受试用许可协议,以便能使用可视化设计器。

示例代码仓库: https://github.com/cuba-platform/sample-session-planner.

创建一个空项目

我们使用 Intellij IDEA 菜单创建一个空的 CUBA 项目,并命名为 SessionPlanner。
我们将使用 Java 8 作为默认的 JDK。

text

创建数据模型

第一个任务 - 创建实体。业务领域模型只包含两个类:Speaker 和 Session。其关系为一对多。一位发言人可以主持多个会议。

text

我们先创建 Speaker 实体。可以用 IDEA 中项目欢迎页的一个链接来创建:

text

或者右键点击 Studio 界面左侧 CUBA 项目树的 “Data Model” 节点,然后选择 “New -> Entity”

text

输入实体名 - Speaker 并按照需求创建其属性:

名称类型必须属性其他约束
firstNameString (255)
lastNameString (255)
emailString (1024)“Email” 验证器

在 CUBA 中我们使用标准的 JPA 实体,您可以用代码编辑器或者可视化设计器创建实体。只要点击 “+” 图标为实体添加属性,CUBA Studio 会为您生成类成员变量。

text

在 CUBA 中,您可以指定一个格式将实体以字符串的形势展示在 UI 中,这就是 “实体名称(Instance Name)”。对于 speaker,我们选择姓和名。

text

如果我们看一看实体设计器底部的 “Text” 标签页,可以看到这就是一个使用了 JPA 注解的 Java 类。如果需要,也可以手动修改生成的代码,修改后切换到 “Designer” 标签页,设计器也会反映出代码的改动。

我们进行下一步,创建 Session 实体并将其关联至我们的 Speaker 类。字段需求表如下。会议结束时间是一个计算值,为开始时间加一个小时。

名称类型是否必须
topicString (255)
startDateDateTime
endDateDateTime
speaker关联至 “Speaker” 实体,多对一关系
descriptionString (2000)

原理基本上一样,但是 “speaker” 属性将会通过 UI 进行选取,所以我们需要在编辑器的 “Lookup Action” 部分指定 “lookup” 的方式并且使用 “Dropdown” 作为查找类型。最后,字段定义看上去是这样:

text

创建计算属性

在 CUBA 里,可以使用实体的生命周期回调函数来添加字段的计算逻辑。您需要创建一个方法并使用恰当的 Java 注解进行标注。该方法会被自动调用。我们添加一个会在实体 “持久化之前(PrePersist)” 阶段调用的方法。点击实体设计器窗口顶部的 “Lifecycle Callbacks” 按钮,并选择所需的声明周期阶段。

text

将该方法命名为 “updateEndDate”,除了 @PrePersist 之外,我们还使用了 @PreUpdate 注解。我们为计算会议结束时间创建一个单独的方法,以便能在应用程序其他地方重用。

public static Date calculateEndDate(Date startDate) {
  return Date.from(startDate.toInstant().plus(1, ChronoUnit.HOURS));
}

然后我们会在生命周期处理函数中调用该方法:

@PrePersist
@PreUpdate
public void updateEndDate() {
endDate = calculateEndDate(startDate);
}

这就行了。领域模型创建好了。

创建数据库

默认情况下,CUBA Studio 会在开发阶段使用 HSQL 数据库并会为这个关系型数据库创建特定的 SQL 脚本。选择 “CUBA -> Generate Database Scripts” 菜单来生成创建数据库的 SQL 脚本。在将脚本保存为项目文件之前,可以在弹窗中评审生成的脚本。请注意,这些脚本也是项目的一部分,可以在项目树的 “Main Data Store” 节点找到这些脚本。

text

如果需要添加一些特殊的内容,您可以手动修改脚本,比如额外的索引或者初始化数据的插入语句。

text

点击 “Save and close” 保存脚本。要使用这些脚本并创建数据库,只需要选择 “CUBA -> Create Database” 菜单。除了我们创建的应用程序表之外,CUBA 会创建一些系统表,用来存储用户、角色、任务等信息。

好了,数据库已经创建。现在我们可以创建一个简单的 UI,用来对数据的数据进行 CRUD 操作。

生成 CRUD 界面

CUBA Studio 带有 UI 界面生成向导,可以帮我们创建基本,但是很有用的 UI 界面:

  • 浏览界面 - 在数据网格中展示实体列表
  • 编辑界面 - 使用类似表单的界面编辑一个实体实例

首先,我们创建处理 Speaker 的界面。由于实体结构非常简单,在创建界面时我们可以直接使用默认参数。点击实体设计器顶部 “Screens” 菜单下的 “Create Screen” 菜单项即可打开界面创建向导。

text

也可以在 CUBA 项目树中右键点击一个实体,然后选择 “New -> Screen” 菜单项打开界面创建向导。

text

对于 “Speaker” 实体,我们为其创建默认的浏览界面和编辑界面。在向导的 “Screen Templates” 标签页选择 “Browser and Editor”,然后点击 “Next”

text

下一个界面使用默认参数,然后点击 “Next”。

text

下一步中,您可以修改界面标题以及应用程序菜单项名称。然后点击 “Finish”,即可生成 CRUD 界面。

text

可以看到,每个界面包含了两个部分:一个控制器类,由 Java 编写,用来处理界面内部逻辑和事件响应,以及一个 XML 布局,定义了界面的展示形式。在我们的例子中,浏览界面由 “speaker-browse.xml” 和 “SpeakerBrowse.java” 文件组成,而编辑界面由 “speaker-edit.xml” 和 “SpeakerEdit.java” 组成。源文件都在 CUBA 项目树的 “Generic UI -> Screens” 中。

text

请注意界面的 XML 描述中的 “DATA” 部分 - 定义了数据如何从数据库获取。

<data readOnly="true">
   <collection id="speakersDc"
               class="com.company.sessionplanner.entity.Speaker"
               view="_local">
       <loader id="speakersDl">
           <query>
               <![CDATA[select e from sessionplanner_Speaker e]]>
           </query>
       </loader>
   </collection>
</data>

通过 CUBA Studio 窗口顶部的按钮可以很容易在界面控制器、界面描述文件以及关联实体之间切换:

text

text

text

为 session 创建浏览和编辑界面

运行界面生成器向导,选择 “Entity browser and editor screens” 然后点击 “Next”。
在下一个界面我们会为界面创建 “浏览视图(Browse View)” 和 “编辑视图(Edit View)”。在 CUBA 框架中,实体视图(Entity View)指定了需要从数据库获取哪些字段。这个信息除了用在界面创建之外,还能用在特定的 API 设计中。

我们会为 “Session” 实体创建一个包含 speaker 引用的单独视图为浏览界面服务。在 “Browse View” 下拉框选择 “Create View…”。

text

在弹出框的 “Name” 字段输入 “session-browse-view” 并在属性选择树中选择 “speaker” 属性。最终的配置如下:

text

通过选择 “speaker” 属性,我们引导 CUBA 从 “Speaker” 表中获取数据,从而在会议浏览界面显示发言人的名和姓。

按下 “OK” 保存新的实体视图。

对于 Session 实体,我们不会开放其结束时间用来编辑,因为我们会自动生成该字段。创建一个编辑视图 “session-edit-view”,但是在 “Extends” 下拉框我们选择 “_minimal” 视图,然后除了 “endDate” 之外,我们选中其他所有字段。这样会告诉 CUBA 不要从数据库获取这个字段的值,但是字段会在持久化之前(pre-persist)或者更新前(pre-update)生命周期中保存到数据库。参阅 “创建计算属性” 章节。另外,界面上也不会为该属性创建界面组件。您的视图编辑器应该是这样:

text

按下 “OK” 保存新的实体视图。

现在我们完成了生成界面的过程。按下 “Next”,如果需要,可以修改界面标题。我们需要稍微修改一下编辑界面。如果您打开一个 session 的编辑界面,会看到 “description” 字段是使用一个单行的文本字段组件生成的。

text

我们将它换成多行编辑器。最简单的方式就是打开 XML 文件然后修改 id="descriptionField" 标签的 “textField” 为 “textArea”。

<form id="form" dataContainer="sessionDc">
   <column width="250px">
       <textField id="topicField" property="topic"/>
       <dateField id="startDateField" property="startDate"/>
       <lookupPickerField id="speakerField" optionsContainer="speakersDc" property="speaker">
           <actions>
               <action id="lookup" type="picker_lookup"/>
           </actions>
       </lookupPickerField>
       <textArea id="descriptionField" property="description"/>
   </column>
</form>

还有,需要注意在编辑界面中没有显示 “endDate”,但是您可以在 session 浏览界面看到这个字段。

运行开发模式的应用程序

要运行 CUBA 应用程序,可以使用 IDE 窗口顶部的运行配置

text

或者从主菜单选择 “CUBA -> Start Application Server”。

text

等待一会您就可以使用浏览器访问应用了。URL 会在 IDEA 的运行工具框内显示。默认情况会是 http://localhost:8080/app。在浏览器打开该地址然后使用 “admin” 登录系统。密码默认也是 “admin”。在 “Application” 菜单下有实体操作的界面。
然后我们给数据库添加一些数据:添加两个 speaker,然后添加两个 session,计划时间为本周剩余的时间。您可以尝试为 speaker 输入无效的 email 看看验证器是否能正常工作。

text

text

生成的界面适合只有基本操作的情况,但是现实中的 UI 通常更加复杂。

自定义用户界面

除了表格视图之外,我们再添加一个日历视图。因此,在浏览界面,我们需要添加标签页控制,再将日历放入标签页,提供在日历中编辑和重计划会议的功能。就像这样:

text

在设计器打开 session-browse.xml 文件并在组件工具箱找到 “Tab Sheet” 容器。

text

拖拽 TabSheet 至组件树中 “filter” 组件的下方,然后为其分配一个 id - “sessionTabs”,填写在工具箱界面的 “Properties” 标签页内。

text

推荐为每个控制组件都分配一个 id,这样我们能使用 id 进行引用。组件树应该如下所示:

text

再拖拽两个 “Tab” 组件,放到 “sessionsTabs” 下面,分别指定 id 为 “calendarTab” 和 “tableTab”,标题为 “Session Calendar” 和 “Session Table”。

text

然后收起 “sessionsTable” 组件,并将其拖至 “tableTab”。

text

您会发现由于我们改动,界面布局被破坏了。

text

如果要修复,我们需要使用一个合适的组件,能扩展至所有的界面可用范围。选择 “layout” 组件并设置 “expand” 属性为 “sessionTabs”,而不是无效的 “sessionsTable”,这个设置会将 tabsheet 扩展至整个界面。

text

现在在组件工具箱找到 “Calendar” 组件,拖拽至布局中的 “calendarTab” 组件。分配一个 id - “sessionsCalendar”。

text

为 “tableTab” 容器设置 “expand” 属性为 “sessionTable”,为 “calendarTab” 设置 “expand” 为 “sessionCalendar”。

text

在 CUBA 中,UI 组件能绑定到实体及其属性上。

我们将日历组件绑定到界面中获取的数据集。只需要将其 “dataContainer” 属性设置为 “sessionsDc”。然后绑定:

  • startDateProperty 至会议的开始时间
  • endDateProperty 到会议结束时间
  • captionProperty 到会议的主题
  • descriptionProperty 到会议的描述

text

要与布局可视化编辑器一起练习使用使用XML编辑器,我们可以更新 XML 描述并修改日历仅显示工作时间再添加导航按钮。试一试,XML 编辑器支持自动补全,所以您不会输入错误的属性名。在添加了下面代码中的 firstVisibleHourOfDay、lastVisibleHourOfDay 和 navigationButtonsVisible 三个属性之后,XML 中的 calendar 标签会是这样:

<calendar id="sessionsCalendar" dataContainer="sessionsDc" startDateProperty="startDate"
         endDateProperty="endDate" captionProperty="topic" descriptionProperty="description"
   firstVisibleHourOfDay="8" lastVisibleHourOfDay="18" navigationButtonsVisible="true"/>

要查看 UI 的改动,您不需要重启应用程序,只需要在应用中重新打开界面。CUBA 框架支持界面的热部署。现在您可以看到会议都显示在日历中了。

使用界面 API

当我们与 UI 进行交互的时候,会产生交互事件,因此,CUBA 提供了 API 可以订阅这些事件并进行处理。我们试试处理日历条目的点击事件,并调用会议编辑器。CUBA 提供了界面构建 API 用来操控界面,我们一会能用到。

在 SessionBrowse 控制器代码编辑器内,点击窗口顶部的 “Subscribe to Event” 按钮

text

然后在弹出的事件选择框中选择 CalendarEventClickEvent,然后点击 OK。

text

会为您生成下面的空方法。

@Subscribe("sessionsCalendar")
private void onSessionsCalendarCalendarEventClick(Calendar.CalendarEventClickEvent event) {

}

要使用操控界面的 API,我们需要在控制器添加 ScreenBuilders 服务。点击窗口顶部的 “Inject” 按钮并在弹窗中的 Screen API 部分选择 screenBuilders 服务。

另一个注入服务(或者订阅事件)的方法是 - 在编辑界面按下 Alt+Insert(Mac: Ctrl+Enter)组合键,然后在出菜单选择 “Inject”:

text

如果要搜索需要注入的 service,只需直接开始输入其名称,然后用上下键选择正确的 service 注入即可。

text

界面构建服务注入之后,调用一个界面就变成了一组链式的方法调用。
我们需要以 this (浏览界面)为父界面调用会议的编辑界面。然后我们希望能编辑从点击事件中获取的会议实体。编辑器通过对话框模式打开。当编辑器关闭时,我们需要为浏览界面重新加载所有的数据。做完这些事情之后,我们应该展示我们构建的这个编辑界面。在事件处理器中,代码如下:

@Subscribe("sessionsCalendar")
private void onSessionsCalendarCalendarEventClick(Calendar.CalendarEventClickEvent event) {
   Screen screen = screenBuilders.editor(Session.class, this)
           .editEntity((Session) event.getEntity())
           .withLaunchMode(OpenMode.DIALOG).build();
   screen.addAfterCloseListener(afterCloseEvent -> {
       getScreenData().loadAll();
   });
   screen.show();
}

就这些。您可以在运行的程序中重新打开会议浏览界面,通过点击日历上的会议调用编辑界面。

text

编辑界面不是很好看,我们需要调整一下其宽高。在 IDE 中,打开界面的 XML 描述,选择 “dialogMode” 属性并设置 “width” 和 “height” 属性为 “auto”。

text

切换到应用程序,先关闭编辑界面再点击日历的会议重新打开编辑界面。现在看上去好多了。

text

添加业务逻辑

现在我们将用 CUBA Studio 来创建一个服务,实现业务逻辑,然后在一个界面中使用这个服务。此服务将用于重新安排会议,检查同一个发言人不会在同一个时段有两场会议。

在 CUBA 项目树中右键点击 Service 节点并选择 “New ->Service”。会自动打开服务创建对话框。输入 “SessionService” 作为接口名称,Studio 会自动生成 SessionServiceBean 作为实现类的名称。

text

在接口中按照下面的代码创建一个 “rescheduleSession” 方法:

public interface SessionService {
   String NAME = "sessionplanner_SessionService";

   boolean rescheduleSession(Session session, Date newStartDate);
}

该方法接收 session 和会议的新 startDate 作为参数。如果我们能重新计划这个会议,我们将使用新的开始时间将其保存到数据库;如果不行,则返回 false 作为服务执行的结果。

要实现该方法,打开 SessionServiceBean 类的代码编辑器,可以在 CUBA 项目树的 “Middleware - Services” 节点找到该类:

text

可以看到类是空的,并且有错误:

在类中任意处按下 Alt+Insert(Mac: Ctrl+Enter)在弹出菜单中选择 “Implement methods”:

text

选择 “rescheduleSession” 方法,会自动生成如下桩代码:

@Service(SessionService.NAME)
public class SessionServiceBean implements SessionService {

   @Override
   public boolean rescheduleSession(Session session, Date newStartDate) {
       return false;
   }
}

在服务中,我们将使用 CUBA API 来访问数据 - DataManager 类。我们创建一个 JPQL 查询语句检查在定义的时间段内是否有为 Speaker 安排的会议。然后我们检查查询结果,依据查询结果,我们用新的开始日期更新会议或者直接从方法退出,返回相应的结果。

在类中任意处按下 Alt+Insert(Mac: Ctrl+Enter)在弹出菜单中选择 “Inject” 将 DataManager 注入:

text

在弹窗中选择 DataManager:

text

方法实现代码如下:

@Override
public boolean rescheduleSession(Session session, Date newStartDate) {

   Date newEndDate = Session.calculateEndDate(newStartDate);

   int sessionsSameTime = dataManager.load(Session.class)
           .query("select s from sessionplanner_Session s where " +
                   "s.startDate < :newEndDate and s.endDate > :newStartDate " +
                   "and s.speaker = :speaker " +
                   "and s.id <> :sessionId")
           .parameter("newStartDate", newStartDate)
           .parameter("newEndDate", newEndDate)
           .parameter("speaker", session.getSpeaker())
           .parameter("sessionId", session.getId())
           .list().size();

   if (sessionsSameTime == 0) {
       session.setStartDate(newStartDate);
       dataManager.commit(session);
       return true;
   }

   return false;
}

注意到我们重用了之前章节 “创建计算属性” 中创建的方法:

Date newEndDate = Session.calculateEndDate(newStartDate);

服务就完成了,现在我们将其添加到会议浏览界面。会在日历中的会议被拖拽时调用。如果会议不能重新安排,我们用通知 API 显示一个屏幕通知。

切换到会议浏览界面的控制器,并注入新创建的服务,使用与注入界面 API 一样的方法。然后同样注入 “Notifications” API。最后,订阅日历组件的 “CalendarEventMove” 事件,方法也与我们之前订阅点击事件类似。

然后我们可以在事件处理器中加入下列代码:

@Subscribe("sessionsCalendar")
private void onSessionsCalendarCalendarEventMove(Calendar.CalendarEventMoveEvent event) {

   Session session = ((EntityCalendarEvent<Session>)event.getCalendarEvent()).getEntity();

   if (!sessionService.rescheduleSession(session, event.getNewStart())) {
       notifications.create(Notifications.NotificationType.WARNING)
       .withCaption("Session "+session.getTopic()+" cannot be rescheduled to "+event.getNewStart()+" due to a conflict")
       .show();
   }

   getScreenData().loadAll();
}

由于需要部署新的服务,我们必须重启应用程序,也可以用 IDEA 的 “Run” 按钮。

text

应用重启之后,我们可以打开会议日历 - 搞定!可以拖拽会议进行重新安排了!测试一下,我们再添加一个会议并尝试重新安排。

添加品牌信息

您可以通过修改标准界面的默认标题对 CUBA 应用程序进行自定义,比如主界面或者登录界面。我们试试按照应用程序的业务领域 - 会议安排(conference planning),修改一下标题。

打开主消息包文件 - “messages.properties”,位于 CUBA 项目树的 “Generic UI - Main Message Pack” 节点中。

text

这是个标准的 Java .properties 文件,所以可以自由编辑 - 添加新的消息键值或者修改已有的键值。我们修改消息键值体现应用程序的用途。示例如下:

application.caption = CUBA Session Planner
application.logoImage = branding/app-icon-menu.png
application.logoLabel = Session Planner

loginWindow.caption = Login
loginWindow.welcomeLabel = Welcome to Session Planner!
loginWindow.logoImage = branding/app-icon-login.png

menu-config.application-sessionplanner = Planner
menu-config.sessionplanner_Speaker.browse=Speakers
menu-config.sessionplanner_Session.browse=Sessions

由于 CUBA 有热部署功能,我们只需登出系统再重新登入就能看到变化。

市场

框架带有 市场 ,市场内有很多组件,这样您可以非常方便的添加有用的功能,比如为已有的应用程序添加 图表地图 支持。可以通过 Studio 的 “CUBA -> Marketplace” 菜单直接安装这些组件。

text


CUBA 框架提供很多有用的 API,能帮您快速的创建业务系统。这个快速开始教程只演示了 CUBA 框架和 CUBA Studio 的最基本功能。访问我们的网站 cuba-platform.cn 您能找到更多的 示例向导

感谢您关注 CUBA 框架,享受使用 CUBA 开发吧!