Hilla框架簡介
1. 概述
Hilla 是 Java 的全端 Web 框架。 Hilla 讓我們可以透過在 Spring Boot 應用程式新增 React 視圖並透過類型安全性 RPC 從 TypeScript 呼叫後端 Java 服務來建立全端應用程式。
它使用Vaadin UI 元件集並與 Vaadin Flow 相容。兩者都是 Vaadin 平台的一部分。在本教程中,我們將討論 Hilla 開發的基礎知識。
2. 創建 Hilla 項目
我們可以透過在 Spring Initializr 上新增 Vaadin 依賴項或在 Vaadin Start 上下載自訂啟動器來建立 Hilla 專案。
或者,我們可以透過在專案的 Maven pom.xml中新增以下物料清單 (BOM),將 Hilla 新增至現有的 Spring Boot 專案。我們使用最新版本的vaadin-bom初始化vaadin.version屬性:
<properties>
<vaadin.version>24.4.10</vaadin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-bom</artifactId>
<version>${vaadin.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
然後,我們為 Vaadin 平台(其中包括 Hilla)添加以下依賴項:
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
為了完成設置,讓我們創建一個主題。主題設定檔確保所有視圖的外觀一致,並包含CSS 實用程式類別。我們新增一個src/main/frontend/themes/hilla/theme.json文件,其中包含以下內容:
{
"lumoImports" : [ "typography", "color", "sizing", "spacing", "utility" ]
}
然後,我們透過更新 Spring Boot 應用程式以擴展AppShellConfigurator並新增@Theme(“hilla”)註解來載入主題:
@Theme("hilla") // needs to match theme folder name
@SpringBootApplication
public class DemoApplication implements AppShellConfigurator {
// application code omitted
}
3. 啟動應用程式
Hilla 在同一專案中包含 React 視圖和後端 Java 原始程式碼,作為一個統一的全端專案。我們可以透過在src/main/frontend/views資料夾中建立 React 元件來定義視圖。
首先,我們透過建立一個新檔案src/main/frontend/views/@index.tsx (根據需要建立資料夾)來新增視圖,其中包含以下內容:
export default function Index() {
return (
<h1>Hello world</h1>
);
}
現在,讓我們透過執行mvn spring-boot:run或在 IDE 中執行 Spring Boot 應用程式類別來啟動應用程式。
第一次啟動需要一些時間,因為 Hilla 下載 Maven 和 npm 依賴項並啟動 Vite 開發伺服器。隨後的啟動速度會更快。
建置運行後,我們可以開啟瀏覽器存取localhost:8080以查看「 Hello world 」問候語:
4. 使用@BrowserCallable呼叫伺服器方法
Hilla 應用程式的一個獨特之處是我們如何從客戶端呼叫伺服器。與具有 React 前端的傳統 Spring Boot 應用程式不同,我們不會建立兩個透過通用 REST API 進行通訊的單獨應用程式。相反,我們有一個全端應用程序,它使用 RPC 呼叫用 Java 編寫的服務類上的方法。它遵循後端到前端 (BFF) 架構。
讓我們看看如何從瀏覽器存取後端服務。我們將在整個範例中使用Contact類別:
@Entity
public class Contact {
@Id
@GeneratedValue
private Long id;
@Size(min = 2)
private String name;
@Email
private String email;
private String phone;
// Constructor, getters, setters
}
我們還將使用 Spring Data JPA 儲存庫來存取和保存資料:
public interface ContactRepository extends
JpaRepository<Contact, Long>,
JpaSpecificationExecutor<Contact> {
}
我們可以透過使用@BrowserCallable. Hilla 服務受 Spring Security 保護。預設情況下,拒絕所有服務的存取。我們可以加入@AnonymousAllowed註解來允許任何使用者呼叫該服務:
@BrowserCallable
@AnonymousAllowed
public class ContactService {
private final ContactRepository contactRepository;
public ContactService(ContactRepository contactRepository) {
this.contactRepository = contactRepository;
}
public List<Contact> findAll() {
return contactRepository.findAll();
}
public Contact findById(Long id) {
return contactRepository.findById(id).orElseThrow();
}
public Contact save(Contact contact) {
return contactRepository.save(contact);
}
}
Java 和 TypeScript 處理可空性的方式不同。在 Java 中,所有非基本型別都可以為 null,而 TypeScript 要求我們明確地將變數或欄位定義為可為 null。 Hilla 的 TypeScript 產生器預設為嚴格模式,確保 Java 和 TypeScript 類型完全匹配。
這種嚴格性的缺點是 TypeScript 程式碼可能會變得笨拙,因為我們需要在多個地方引入 null 檢查。如果我們遵循良好的 API 設計實踐,避免集合返回 null 值,我們可以在服務包中添加帶有@NonnullApi註釋的package-info.java檔案來簡化 TypeScript 類型:
@NonNullApi
package com.example.application;
import org.springframework.lang.NonNullApi;
現在,我們可以使用 React 中的相同簽章來呼叫該服務。讓我們更新@index.tsx以查找所有聯絡人並將它們顯示在Grid中:
export default function Contacts() {
const contacts = useSignal<Contact[]>([]);
async function fetchContacts() {
contacts.value = await ContactService.findAll();
}
useEffect(() => {
fetchContacts();
}, []);
return (
<div className="pm flex flex-col gap-m">
<h2>Contacts</h2>
<Grid items={contacts.value}>
<GridSortColumn path="name">
{({item}) => <NavLink to={`contacts/${item.id}/edit`}>{item.name}</NavLink>}
</GridSortColumn>
<GridSortColumn path="email"/>
<GridSortColumn path="phone"/>
</Grid>
</div>
);
}
讓我們定義一個非同步函數fetchContacts() ,它等待ContactService.findAll() ,然後將聯絡人訊號值設定為接收到的聯絡人。我們從Frontend/generated資料夾匯入Contact類型和ContactService ,Hilla 就是在該資料夾中產生客戶端程式碼的。 Hilla 使用基於Preact 訊號的訊號:
5. 配置視圖和佈局
Hilla 使用基於檔案的路由,這表示視圖根據檔案名稱和資料夾結構對應到路由。所有視圖的根資料夾是src/main/frontend/views 。在以下部分中,我們將逐步介紹如何配置視圖和佈局。
5.1.查看命名約定
視圖透過名稱和以下約定對應到路徑:
-
@index.tsx— 目錄的索引 -
@layout.tsx —目錄的佈局 -
view-name.tsx— 對應到view-name -
{parameter} —捕獲參數的資料夾 -
{parameter}.tsx— 擷取參數的視圖 -
{{parameter}}.tsx— 擷取選用參數的視圖 -
{…wildcard}.tsx— 符合任何字元
5.2.查看配置
我們可以透過匯出ViewConfig類型的名為 config 的常數來設定視圖。在這裡,我們可以定義視圖的標題、圖示和存取控制等內容:
export const config: ViewConfig = {
title: "Contact List",
menu: {
order: 1,
}
}
export default function Index() {
// Code omitted
}
5.3.定義佈局
我們可以為任何目錄定義父佈局。 views資料夾根目錄中的@layout.tsx包含應用程式中的所有內容,而contacts資料夾中的@layout.tsx僅包含該目錄或其子目錄中的所有檢視。
讓我們在src/main/frontend/views目錄中建立一個新的@layout.tsx檔案:
export default function MainLayout() {
return (
<div className="pm h-full flex flex-col box-border">
<header className="flex gap-m pb-m">
<h1 className="text-l m-0">
My Hilla App
</h1>
{createMenuItems().map(({to, title}) => (
<NavLink to={to} key={to}>
{title}
</NavLink>
))}
</header>
<Suspense>
<Outlet/>
</Suspense>
</div>
);
}
createMenuItems()幫助器傳回已發現路由的數組,並為每個路由建立連結。
讓我們再次打開瀏覽器並驗證我們是否可以在視圖上方看到新選單:
6. 建立表單並驗證輸入
接下來,我們來實作編輯視圖來編輯Contact 。我們將使用 Hilla useForm()掛鉤將輸入字段綁定到Contact物件上的字段,並驗證其上定義的所有驗證約束是否都通過。
首先,我們建立一個新檔案views/contacts/{id}/edit.tsx其中包含以下內容:
export default function ContactEditor() {
const {id} = useParams();
const navigate = useNavigate();
const {field, model, submit, read} = useForm(ContactModel, {
onSubmit: async contact => {
await ContactService.save(contact);
navigate('/');
}
})
async function loadUser(id: number) {
read(await ContactService.findById(id))
}
useEffect(() => {
if (id) {
loadUser(parseInt(id))
}
}, [id]);
return (
<div className="flex flex-col items-start gap-m">
<TextField label="Name" {...field(model.name)}/>
<TextField label="Email" {...field(model.email)}/>
<TextField label="Phone" {...field(model.phone)}/>
<div className="flex gap-s">
<Button onClick={submit} theme="primary">Save</Button>
<Button onClick={() => navigate('/')} theme="tertiary">Cancel</Button>
</div>
</div>
);
}
然後,讓我們使用useParams()掛鉤從 URL 存取id參數。
接下來,我們將ContactModel傳遞給useForm()並將其配置為提交到我們的 Java 服務。我們也將解構field 、 model 、 submit並將屬性從回傳值read到變數中。最後,我們使用useEffect將目前選定的Contact讀入表單,該 useEffect 透過id從後端取得Contact 。
我們為Contact上的每個屬性建立輸入字段,並使用field方法將它們綁定到Contact物件上的相應字段。這會將 Java 物件上定義的值和驗證規則與 UI 同步。
ave按鈕呼叫表單的submit()方法:
7. 使用 AutoCrud 進行自動 CRUD 操作
由於 Hilla 是一個全端框架,因此它可以幫助我們自動化一些常見任務,例如建立清單和編輯實體。讓我們更新我們的服務以利用這些功能:
@BrowserCallable
@AnonymousAllowed
public class ContactService extends CrudRepositoryService<Contact, Long, ContactRepository> {
public List<Contact> findAll() {
return getRepository().findAll();
}
public Contact findById(Long id) {
return getRepository().findById(id).orElseThrow();
}
}
擴充CrudRepositoryService建立一個服務,該服務提供 Hilla 基於給定實體產生資料網格、表單或 CRUD 視圖所需的所有基本 CRUD 操作。在本例中,我們也加入了先前在服務中使用的相同findAll()和findById()方法,以避免破壞現有視圖。
我們現在可以建立一個新視圖,顯示基於新服務的 CRUD 編輯器。讓我們定義一個名為frontend/views/auto-crud.tsx的新文件,其中包含以下內容:
export default function AutoCrudView() {
return <AutoCrud service={ContactService} model={ContactModel} className="h-full"/>
}
我們只需要傳回一個元件AutoCrud ,並傳入服務和模型即可取得列出、編輯和刪除聯絡人的檢視:
如果我們只需要列出項目而不對其進行編輯,我們可以使用AutoGrid元件。同樣,如果我們需要編輯一個項目但不想顯示列表,我們可以使用AutoForm元件。兩者的工作方式與上面的AutoCrud相同。
8. 生產建設
為了建置用於生產的 Hilla,我們使用production Maven 設定檔。生產版本建立一個優化的前端 JavaScript 套件並關閉開發時偵錯。此設定檔自動包含在 Spring Initializr 和 Vaadin Start 上建立的專案中。如果我們有自訂項目,我們也可以手動新增它:
<profile>
<!-- Production mode is activated using -Pproduction -->
<id>production</id>
<dependencies>
<!-- Exclude development dependencies from production -->
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-core</artifactId>
<exclusions>
<exclusion>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-dev</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-maven-plugin</artifactId>
<version>${vaadin.version}</version>
<executions>
<execution>
<goals>
<goal>build-frontend</goal>
</goals>
<phase>compile</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
我們可以使用生產設定檔建置專案:
./mvnw package -Pproduction
9. 結論
在本文中,我們學習了 Hilla 開發的基礎知識,用於建立將 Spring Boot 後端與具有類型安全通訊的 React 前端相結合的全端 Web 應用程式。
Hilla 讓我們將 React 視圖新增到 Spring Boot 專案中。視圖根據名稱和資料夾結構映射到路由。我們可以從 TypeScript 呼叫 Spring Service類,透過使用 @BrowserCallable 註解Service類別來保留完整的類型資訊@BrowserCallable. Hilla 使用 Java Bean Validation 註解在用戶端進行輸入驗證,並再次驗證 Java 服務方法中接收到的資料的正確性。
像往常一樣,可以在 GitHub 上找到程式碼。