Hilla сочетает в себе серверную часть Java, созданную на Spring, с внешней частью TypeScript, созданной с помощью Lit, быстрой реактивной среды для JavaScript. Hilla, основанная на Vaadin Fusion, представляет собой уникальное животное в экосистеме Java: что-то вроде Next.js для JavaScript, но с серверной частью Java на основе Spring. В этой статье вы познакомитесь с Hilla, в том числе о том, как создать базовое веб-приложение, создать внешний интерфейс и добавить новые компоненты.
Хилла и Ваадин
В январе этого года разработчики Vaadin объявили, что переименовывают Vaadin Fusion в Hilla. Для разработчиков, уже знакомых с Fusion, изменилось только название. Разработчики, только открывающие для себя Hilla, заметят, что в примерах в этой статье используются пакеты и компоненты, названные в честь Vaadin. В будущем выпуске пакеты Vaadin Fusion будут переименованы в Hilla.
Веб-разработка на Java с реактивным интерфейсом
Hilla объединяет реактивный интерфейс JavaScript и серверную часть Spring Java в одной унифицированной сборке. Примеры в этой статье покажут вам, как эти компоненты работают вместе, образуя Hilla полнофункциональную среду. Чтобы продолжить, вам понадобятся оба Node.js (npm
) и последний JDK, установленный в вашей системе. Убедитесь, что оба node -v
и java –version
работа!
Чтобы начать, откройте командную строку и создайте новый проект с помощью npx
как показано в листинге 1.
Листинг 1. Строительные леса для нового проекта в Хилле
npx @vaadin/cli init --hilla foundry-hilla
Сейчас, cd
в новый каталог и введите ./mvnw
(или mvnw
для Windows). Эта команда инициирует сборку Maven. Вы увидите журнал вывода как для внутренней, так и для создаваемой внешней части. Вскоре приложение будет запущено в режиме разработки.
Рисунок 1. Посетите localhost:8080, и вы увидите, что ваше приложение Hilla работает.
Если вы посмотрите на только что созданную файловую систему, вы увидите, что ее структура разделена на стандартную структуру Maven и внешний каталог:
/project-root
/frontend
html
ts
ts
/stores
/themes
/views
/src
/target
Корень проекта содержит файл сборки Maven (pom.xml
), который создает код Java из /src into /target
и вызывает инструмент сборки JavaScript (vite.js) для создания клиентского приложения, содержащегося в /frontend
.
Создайте переднюю часть
В Hilla интерфейс загружается из /front-end/index.html
, /front-end/index.ts
и routes.ts
файлы. Вместе эти файлы определяют маршрутизацию и устанавливают содержимое страницы для данного маршрута. Самая поучительная из этих страниц — routes.ts
показанный в листинге 2.
Листинг 2. Routes.ts
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[] = [
// place routes below (more info https://vaadin.com/docs/latest/fusion/routing/overview)
{
path: '',
component: 'hello-world-view',
icon: '',
title: '',
},
{
path: 'hello',
component: 'hello-world-view',
icon: 'la la-globe',
title: 'Hello World',
},
{
path: 'about',
component: 'about-view',
icon: 'la la-file',
title: 'About',
action: async (_context, _command) => {
await import('./views/about/about-view');
return;
},
},
];
export const routes: ViewRoute[] = [
{
path: '',
component: 'main-layout',
children: [...views],
},
];
Код в листинге 2 связывает путь с компонентом. Как и многие фреймворки JavaScript, Hilla использует компонент для представления представления. В этом случае, когда пользователь переходит по пустому маршруту, он будет обслуживать hello-world-view
компонент. (Обратите внимание, что другие маршруты предоставляют дополнительную информацию, такую как значок, заголовок и действие.)
Основной макет обрабатывается /frontend/views/main-layout.ts
в то время как содержание hello-world-view
находится в /frontend/views/helloworld/hello-world-view.ts
показанный в листинге 3.
Листинг 3. hello-world-view.ts
import '@vaadin/button';
import '@vaadin/notification';
import { Notification } from '@vaadin/notification';
import '@vaadin/text-field';
import { html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { View } from '../../views/view';
@customElement('hello-world-view')
export class HelloWorldView extends View {
name="";
connectedCallback() {
super.connectedCallback();
this.classList.add('flex', 'p-m', 'gap-m', 'items-end');
}
render() {
return html`
<vaadin-text-field label="Your name" @value-changed=${this.nameChanged}></vaadin-text-field>
<vaadin-button @click=${this.sayHello}>Say hello</vaadin-button>
`;
}
nameChanged(e: CustomEvent) {
this.name = e.detail.value;
}
sayHello() {
Notification.show(`Hello ${this.name}`);
}
}
Код в листинге 3 показывает, как Лит строит представление. Если вы знакомы с идиомами реактивного JavaScript, источник должен быть достаточно ясен. Если нет, см. мое недавнее введение в лит. render()
Метод отвечает за вывод содержимого представления. Мы будем использовать его здесь как место для изучения вещей. В частности, мы хотим увидеть, как соединить этот интерфейс с нашими внутренними конечными точками Java.
Создайте конечные точки Java
Hilla построена на основе Spring Boot, поэтому мы можем создавать конечные точки с помощью Spring Web, как обычно. Hilla предлагает дополнительные функции для автоматического создания TypeScript, который будет использоваться во внешнем интерфейсе Lit.
Начните с создания нового файла в /src/java/main/com/example/application
называется MyEndpoint.java
. Вставьте содержимое листинга 4 в этот файл.
Листинг 4. MyEndpoint.java
package com.example.application;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import dev.hilla.Endpoint;
import dev.hilla.Nonnull;
@Endpoint
@AnonymousAllowed
Public @Nonnull class MyEndpoint {
public String foo() {
return "bar";
}
}
Хилла @Endpoint
аннотация сообщает платформе, что этот класс является REST API. Класс также помечен с помощью @AnonymousAllowed
аннотация, потому что Hilla по умолчанию защищает все конечные точки с помощью безопасности Spring. @Nonnull
аннотация генерирует правильную привязку типа для внешнего интерфейса TypeScript.
После сохранения этого файла класса вы можете заметить, что Hilla сгенерировала новый файл TypeScript в /frontend/generated/MyEndpoint.ts
. Мы будем использовать этот модуль для достижения конечной точки из представления.
Примечание: Не вносите изменения в эти сгенерированные файлы; Hilla перезапишет их в зависимости от изменений в файле Java.
Теперь вернитесь к frontend/views/helloworld/hello-world-view.ts
, где мы заставим работать нашу простую конечную точку. В этом случае мы хотим вывести содержимое вызова foo()
конечная точка (которая представляет собой «bar
»). В листинге 5 показаны дополнения, которые следует внести в hello-world-view.ts
файл. (Обратите внимание, что я удалил большую часть предыдущего кода и оставил только дополнения для этого листинга.)
Листинг 5. Hello-world-view.ts
//...
import { customElement,property } from 'lit/decorators.js';
import { foo } from 'Frontend/generated/MyEndpoint';
@customElement('hello-world-view')
export class HelloWorldView extends View {
//...
@property()
myString: string = "";
constructor() {
super();
this.getString();
}
render() {
return html`
//...
<div>${this.myString}</div>
`;
}
async getString() {
this.myString = await foo();
}
}
Главное здесь — импортировать foo()
функция от MyEndpoint
модуль, а затем используйте его для вызова удаленного внутреннего метода Java, который мы определили ранее.
Для этого мы определяем реактивное свойство класса с помощью аннотации Lit TypeScript. @property
с именем string
. Мы будем использовать это свойство для хранения значения с сервера. Чтобы заполнить его, мы вызываем async getString()
метод, который просто вызывает foo()
функцию и помещает возвращаемое значение в myString
.
Хилла берет на себя большую часть работы, включая удаленную выборку, поэтому нам не нужно много об этом думать.
Использование компонентов Vaadin в Hilla
Как я уже отмечал ранее, Hilla — это Vaadin Fusion, поэтому приложения, созданные с помощью Hilla, могут использовать преимущества всех хорошо продуманных компонентов, которые вы, возможно, знаете из этой среды. В качестве примера давайте воспользуемся компонентом сетки Vaadin для загрузки коллекции романов с их названиями и авторами.
Сначала мы создадим объект модели, который просто содержит два Strings
, как показано в листинге 6. Этот файл представляет собой типичный объект данных Java. Сохраните это как /src/main/java/com/example/application/Novel.java
.
Листинг 6. Объект модели для хранения романов
package com.example.application;
import javax.validation.constraints.NotBlank;
public class Novel {
@NotBlank
private String title;
@NotBlank
private String author;
public Novel(String title, String author){
this.title = title;
this.author = author;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
В листинге 7 мы обслуживаем List
романов из MyEndpoint
.
Листинг 7. MyEndpoint со списком моих любимых романов
package com.example.application;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import dev.hilla.Endpoint;
import dev.hilla.Nonnull;
@Endpoint
@AnonymousAllowed
public class MyEndpoint {
private final List<Novel> novels = new ArrayList<Novel>();
MyEndpoint(){
Novel empireFalls = new Novel("Empire Falls", "Richard Russo");
Novel reservationBlues = new Novel("Reservation Blues", "Sherman Alexie");
Novel theAthenianMurders = new Novel("The Athenian Murders", "José Carlos Somoza");
this.novels.add(empireFalls);
this.novels.add(reservationBlues);
this.novels.add(theAthenianMurders);
}
public @Nonnull List<Novel> getNovels() {
return this.novels;
}
}
В листинге 7 мы подготовили несколько романов с их авторами и вставили их в novels
свойство. Затем мы представили данные в getNovels()
конечная точка.
Теперь давайте отобразим новые данные, как показано в листинге 8. (Обратите внимание, что в листинге 8 показаны только измененные части кода.)
Листинг 8. Использование сетки для отображения романов
//...
import { foo, getNovels } from 'Frontend/generated/MyEndpoint';
import '@vaadin/grid/vaadin-grid';
@customElement('hello-world-view')
export class HelloWorldView extends View {
@property()
novels: object = {};
constructor() {
//...
this.initNovels();
}
render() {
return html`
<vaadin-grid .items="${this.novels}" theme="row-stripes">
<vaadin-grid-column path="title"></vaadin-grid-column>
<vaadin-grid-column path="author"></vaadin-grid-column>
</vaadin-grid>
`;
}
async initNovels(){
this.novels = await getNovels();
}
В этом листинге мы импортируем getNovels
объект из frontend/generated/MyEndpont
, который Хилла сгенерировала для нас. Затем мы используем этот метод в качестве источника содержимого this.novels
.
Далее мы используем this.novels
предоставить .items
собственность ввозимому vaadin-grid
компонент. Конечным результатом является красиво отформатированный компонент сетки с минимальными усилиями.
Заключение
В этой статье представлена Hilla, полнофункциональная платформа, основанная на Vaadin Fusion. Hilla предлагает хорошо интегрированный опыт создания веб-приложений Java с реактивным интерфейсом. Благодаря Vaadin в нем имеется множество готовых к использованию полезных компонентов. Примеры в этой статье должны дать вам представление о работе с Hilla.