关键要点
- Hilla 是一个开源框架,有望显着提高 Web 应用程序的开发效率。
- 它将 Spring Boot Java 后端与响应式 TypeScript 前端集成在一起。
- 用户界面是使用 Lit 或 React 以及 Vaadin 的 40 多个开源 UI Web 组件创建的。
- Hilla 通过类型安全的服务器通信和集成工具帮助更快地构建业务应用程序。
- Hilla 还自动为客户端生成 REST API 和访问代码。
- 默认情况下,后端是安全的并且完全无状态。
作为旨在简化 Web 应用程序开发的框架,Hilla 在开源社区中脱颖而出。它结合了 Spring Boot Java 后端和反应式 TypeScript 前端,以及通过 Lit 或 React 进行的 UI 设计,可以创建动态应用程序。Vaadin 的 40 多个开源 UI Web 组件进一步增强了它,为卓越的用户体验提供了随时可用的元素。
Hilla 非常重视效率和安全性,自动生成 API 和客户端访问代码,并默认确保安全的后端。本文将深入探讨 Hilla 的核心方面:它对 Lit、Spring Bean 端点、前端和后端角色以及路由视图的使用。这些见解将帮助开发人员利用 Hilla 更快地构建强大的业务应用程序。 以下是 Hilla 如何通过 Lit、Spring Bean 端点、前端和后端角色以及路由视图提高开发人员效率的几个示例。
希拉
Hilla框架由芬兰公司 Vaadin 开发,该公司还维护着同名的 Java Web 框架 Vaadin Flow。
与使用纯 Java 方法的 Vaadin Flow 不同,Hilla 是一个经典的单页应用程序 (SPA) 框架,专注于全栈开发。
这意味着客户端是用 TypeScript 开发的。前端可以使用 Lit 框架或 React,目前后端仅使用 Spring Boot,但正在努力支持其他 Java框架。
Hilla 项目是一个纯 Maven 项目。在底层,Hilla Maven 插件使用 npm 和Vite进行前端构建。
然而,与传统的前端开发不同的是,您不必担心配置和运行这些工具,这大大简化了前端开发的开始,尤其是对于 Java 开发人员而言。
点燃
Hilla 在客户端支持 Lit 和 React。我将在本文中重点介绍 Lit,因为它是 Hilla 中使用的第一个客户端框架。Lit 是著名的 Polymer 库 [Polymer] 的继承者,用于快速开发Web Components。使用 Lit,可以开发所谓的自定义组件,即 HTML 语言的扩展。模板以声明方式包含在 TypeScript 代码中,也可以添加仅在 Web 组件上下文中有效的 CSS。Web 组件的属性是反应式的,并在发生更改时自动重新呈现。
代码语言:javascript复制@customElement('simple-greeting')
export class SimpleGreeting extends LitElement {
@property()
name?: string = 'World';
render() {
return html`<p>Hello, ${this.name}!</p>`;
}
}
代码图 1:带有 Lit 的组件
图 1 中需要注意的关键是@customElement
装饰器中的名称,它必须包含一个连字符以将其与标准 HTML 元素区分开来。装饰@property
器使字符串名称成为一个反应性属性,可以从组件外部设置,并导致组件在更改时重新呈现。该render()
方法为 Web 组件生成模板。在生成的 DOM 中,可以找到该组件,如图 2 所示。
<body>
<simple-greeting name="World"></simple-greeting>
</body>
代码图像 2:呈现的 Web 组件
端点
在后端,Hilla 使用所谓的端点。端点是一个用 注释的 Spring Bean @Endpoint
。由此,Hilla 生成一个 REST API,包括 TypeScript 代码,用于在客户端访问它。
@Endpoint
@AnonymousAllowed
public class HelloWorldEndpoint {
@Nonnull
public String sayHello(@Nonnull String name) {
if (name.isEmpty()) {
return "Hello stranger";
} else {
return "Hello " name;
}
}
}
代码图 3:端点
在图 3 中首先要注意的是@AnonymousAllowed
注释。此注释对于无需身份验证即可访问 API 是必需的,因为默认情况下 Hilla 中的所有端点都受到保护。@Nonnull
还应注意注释。由于 TypeScript 对 null 的处理比 Java 更严格,这可以通知 TypeScript 生成器参数和返回值永远不应该是null
.
function _sayHello(name: string): Promise<string> {
return client.call('HelloWorldEndpoint', 'sayHello', {name});
}
export { _sayHello as sayHello };
代码图 4:生成的 TypeScript 代码
图 4 显示了生成的可在前端使用的 TypeScript 代码。如果端点、参数或返回类型发生任何变化,就会重新生成代码,并在客户端报告相应的错误。这有助于检测开发期间 API 使用中的错误。
示例应用程序
该应用程序将显示一个个人数据表,可以使用表单对其进行编辑。个人数据将使用 JPA 存储在数据库中。图 1 显示了结果的样子。示例代码发布在GitHub上。
图 1:带有表格的网格
命令行界面
在创建 Hilla 应用程序之前,开发人员需要安装NodeJS 16.14 或更高版本。之后,Vaadin CLI 可以与 npx 一起使用来创建一个新项目。CLI 生成一个完整的 Hilla 应用程序,带有Hello-World-View
和HelloWorldEndpoint
来自图像 3。
npx @vaadin/cli init --hilla hilla-app
代码图 5:CLI
后端
首先,Person
添加一个名为的实体。该示例使用 JPA 将数据保存在 H2 数据库中。
@Entity
public class Person {
@Id @GeneratedValue
private Long id;
@NotBlank
private String firstName;
@NotBlank
private String lastName;
@Email @NotBlank
private String email;
...
}
代码图 6:人物实体
如图 6 所示,使用了 Jakarta Bean 验证注释。Hilla 生成器也考虑了这些。如果在客户端的表单中使用 Person 实体,则会根据注释验证输入(图 2)。
图 2:验证
下一步,创建端点以读取和保存人员数据。PersonRepository
图 7 中使用的 扩展了Spring Data JPA JpaRepository
接口。
@Endpoint
@AnonymousAllowed
public class PersonEndpoint {
@Autowired
private PersonRepository personRepository;
@Nonnull
public List<@Nonnull Person> findAll() {
return personRepository.findAll();
}
public void save(@Nonnull Person person) {
this.personRepository.save(person);
}
}
代码图 7:人员端点
代码语言:javascript复制public interface PersonRepository extends JpaRepository<Person, Integer> {
}
代码图 8:人员存储库
前端
显示人物
在客户端,需要一个视图来显示人员数据,它使用Vaadin 网格。所有 Vaadin 组件都是 Web 组件,因此可以轻松地与 Lit 一起使用。Vaadin 网格提供了分页、排序等多种功能,使得以表格形式显示数据变得非常容易。
代码语言:javascript复制@customElement('person-view')
export class PersonView extends View {
@state()
people: Person[] = [];
async connectedCallback() {
super.connectedCallback();
this.people = await PersonEndpoint.findAll();
}
render() {
return html`
<vaadin-grid .items=${this.people} style="height: 100%">
<vaadin-grid-column path="firstName"></vaadin-grid-column>
<vaadin-grid-column path="lastName"></vaadin-grid-column>
<vaadin-grid-column path="email"></vaadin-grid-column>
</vaadin-grid>
`;
}
}
代码图 9:人物视图
在connectedCallback
将 Web 组件添加到 DOM 时调用的方法中,从端点读取人员实体(图 9)。人员被添加到 Vaadin 网格的项目属性中,“路径”属性用于定义人员属性的路径。为简单起见,此示例不使用分页。如果表包含大量记录,则应使用分页来加载数据的子集。HillaDataProvider
为此提供了一个,它提供当前显示的页面、页面大小、选择的排序等信息,并在分页时逐页向端点请求数据。可以在GitHub 存储库中找到详细的代码示例。
编辑人员
编辑人员数据需要创建表单。为此,使用了 Vaadin Web 组件,如图 10 所示。
代码语言:javascript复制<vaadin-form-layout>
<vaadin-text-field
label="First name"
${field(this.binder.model.firstName)}
></vaadin-text-field>
<vaadin-text-field
label="Last name"
${field(this.binder.model.lastName)}
></vaadin-text-field>
<vaadin-text-field
label="Email"
${field(this.binder.model.email)}
></vaadin-text-field>
</vaadin-form-layout>
<vaadin-button @click=${this.save}>Save</vaadin-button>
代码图 10:表格
为了将实体绑定Person
到组件,Hilla 提供了一个活页夹(图 11)。活页夹使用生成的PersonModel
类,其中包含有关 Person 实体的附加信息,例如验证或类型。
private binder = new Binder<Person, PersonModel>(this, PersonModel);
代码图 11:活页夹
为了能够保存更改的实体,我们使用方法 savePerson
扩展。PersonEndpoint
这个方法可以直接传给binder。为此,单击事件绑定到按钮(参见图 10),并调用保存方法。保存后,重新加载此人的数据,更新网格(图 12)。
private async save() {
await this.binder.submitTo(PersonEndpoint.save);
this.people = await PersonEndpoint.findAll();
}
代码图 12:保存方法
现在,剩下的就是将选定的人从网格传递到活页夹。为此,可以使用 active-item-changed 事件(见图 13)。此外,需要通知网格选择了哪个人,这是使用属性完成的selectedItems
。
<vaadin-grid
.items=${this.people}
@active-item-changed=${this.itemSelected}
.selectedItems=${[this.selectedPerson]}>
代码图 13:网格选择
现在,在itemSelected
图 14 中的方法中,只需要从事件中读取选定的人并将其传递给活页夹。这将填充表单。
private async itemSelected(event: CustomEvent) {
this.selectedPerson = event.detail.value as Person;
this.binder.read(this.selectedPerson);
}
代码图 14:itemSelected 方法
路由
如果应用程序包含多个视图,那么我们将需要一种在视图之间导航的方法。为此,Hilla 使用 Vaadin 路由器(图 15)。hello-world-view
首先,导入应用程序启动时显示的视图,在本例中为, 。然后它被映射到根路径和路径hello-world
。在主从视图的示例中,另一个视图是延迟加载的,因此仅在用户导航到它时才加载。最后,为视图定义布局,其中包括页眉和页脚等元素以及导航组件。
import {Route} from '@vaadin/router';
import './views/helloworld/hello-world-view';
import './views/main-layout';
export type ViewRoute = Route & {
title?: string;
icon?: string;
children?: ViewRoute[];
};
export const views: ViewRoute[] = [
{
path: '',
component: 'hello-world-view',
icon: '',
title: '',
},
{
path: 'hello-world',
component: 'hello-world-view',
icon: 'la la-globe',
title: 'Hello World',
},
{
path: 'master-detail',
component: 'master-detail-view',
icon: 'la la-columns',
title: 'Master-Detail',
action: async (_context, _command) => {
await import('./views/masterdetail/master-detail-view');
return;
},
},
];
export const routes: ViewRoute[] = [
{
path: '',
component: 'main-layout',
children: [...views],
},
];
代码图像:15 路由器配置
生产部署
默认情况下,Hilla 应用程序配置为在开发模式下运行。这需要稍微更多的内存和 CPU 性能,但允许更容易调试。对于部署,应用程序必须在生产模式下构建。开发模式和生产模式的主要区别在于,在开发模式下,Hilla 使用 Vite 将 JavaScript 文件传递到浏览器,而不是传递到运行应用程序的 Java 服务器。更改 JavaScript 或 CSS 文件时,会考虑并自动部署更改。然而,在生产模式下,在构建期间准备一次 JavaScript 和 CSS 文件并让服务器处理所有请求会更高效。同时,可以进一步优化和最小化客户端资源,以降低网络和浏览器负载。
Hilla 项目中的文件pom.xml
使用带有 Vaadin 插件配置的配置文件在生产模式下创建构建(图 16)。
<profiles>
<profile>
<id>production</id>
<build>
<plugins>
<plugin>
<groupId>dev.hilla</groupId>
<artifactId>hilla-maven-plugin</artifactId>
<version>${hilla.version}</version>
<executions>
<execution>
<goals>
<goal>build-frontend</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
<configuration>
<productionMode>true</productionMode>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
代码图 16:Maven 插件
要创建生产构建,开发人员可以调用 Maven,如图 17 所示。此过程将生成一个 JAR 文件,其中包含所有依赖项和已转换的前端资源,可供部署使用。
代码语言:javascript复制./mvnw package -Pproduction
代码图 17:生产构建
结论
由于 Hilla 自动生成端点和模型类的访问代码,因此与传统的单页应用程序开发相比,它使前端和后端的集成更加容易。包含的 Vaadin Web 组件(例如网格)对于开发数据密集型应用程序也非常有帮助。活页夹,特别是与 Bean 验证结合使用,可以非常轻松地创建表单并将代码减少到最低限度。由于开发人员不必处理前端构建和工具,Hilla 也非常适合 Java 开发人员。总的来说,这些特性使 Hilla 能够为结合了反应式前端和 Java 后端的应用程序提供更高的效率。
这篇文章只涵盖了 Hilla 最关键的方面。Hilla 提供了多种其他功能来创建功能齐全的应用程序,例如样式和主题、安全性、本地化、错误处理或应用程序范围的状态管理。官方文档涵盖了这些和许多其他主题。