Vaadin 开发教程:从入门到精通

Vaadin 开发教程:从入门到精通

Vaadin 开发教程 从入门到精通

掌握 Java 全栈 Web 开发的艺术,构建现代化、响应式企业级应用

纯 Java 开发

无需编写 HTML/CSS/JavaScript,专注业务逻辑

丰富组件库

开箱即用的企业级 UI 组件

Java代码界面
服务器端驱动架构
快速开发
类型安全

Vaadin 基础入门

掌握 Vaadin 的核心概念和开发环境搭建

Vaadin 简介与核心概念

什么是 Vaadin:服务器端驱动的 UI 框架

Vaadin 是一个开源的 Java Web 应用开发框架,其核心理念在于提供一种服务器端驱动的编程模型,使得开发者能够完全使用 Java 语言来构建现代化的、响应式的用户界面,而无需编写任何 HTML、CSS 或 JavaScript 代码。

服务器端架构优势

  • 强大的类型安全保证:编译时捕获大量错误
  • 开发效率极大提升:统一的技术栈和工具链
  • 内置安全防护:有效防止 XSS 等常见攻击
工作流程
1 用户交互触发事件
2 事件发送到服务器
3 服务器处理业务逻辑
4 UI 变更推送到客户端

Vaadin Flow 与 Hilla 的区别与选择

Vaadin 发展出两条主要技术路线,各有侧重:

Vaadin Flow
  • • 纯服务器端驱动 (Java)
  • • 100% Java UI 构建
  • • 无需前端技术知识
  • • 适合企业级后台应用
Hilla
  • • 混合模型 (Java + TypeScript)
  • • TypeScript + Lit 前端
  • • 需要现代前端知识
  • • 适合高度交互的 SPA

Vaadin 的核心优势

Java 全栈开发

单一语言构建完整应用,共享领域模型

强大组件化架构

丰富的 UI 组件,遵循 Web Components 标准

响应式设计

内置响应式布局支持,跨设备兼容

环境搭建与项目创建

准备工作

JDK

Java 17+

配置 JAVA_HOME

IDE

IntelliJ IDEA

或 Eclipse

构建工具

Maven

或 Gradle

使用 Vaadin Start 创建新项目

访问 start.vaadin.com 在线生成项目

1 选择技术栈:Vaadin Flow + Java
2 配置项目元数据:Group ID, Artifact ID
3 选择依赖:Spring Boot, JPA, Security 等
4 下载并导入 IDE 开始开发

项目结构解析

文件/类 位置 核心作用
pom.xml 项目根目录 项目构建与依赖管理
Application.java src/main/java/… Spring Boot 应用入口
MainView.java src/main/java/…/views/ 应用主视图(首页)

第一个 Vaadin 应用:Hello World

创建主视图 (Main View)

在 Vaadin 中,视图是一个使用 @Route 注解的 Java 类:

@Route("")
public class MainView extends VerticalLayout {
    public MainView() {
        // UI 初始化代码
    }
}

添加 UI 组件:按钮与文本

组件创建
// 创建组件
Button sayHelloButton = new Button("Say Hello");
Text greetingText = new Text("");

// 添加到布局
add(sayHelloButton, greetingText);
事件处理
// 添加点击监听器
sayHelloButton.addClickListener(event -> 
    greetingText.setText("Hello, Vaadin!")
);

工作原理

用户点击按钮
浏览器捕获事件
服务器处理
执行事件监听器
UI 更新
自动同步到浏览器

Vaadin UI 组件与布局

深入了解 Vaadin 的组件库和布局系统

核心 UI 组件详解

类别 组件 描述 常用 API
输入组件 TextField 单行文本输入框 setValue(), getValue(), setPlaceholder()
NumberField 数字输入框 setMin(), setMax(), setStep()
ComboBox<T> 下拉组合框 setItems(), setValue(), setAllowCustomValue()
DatePicker 日期选择器 setValue(), setMin(), setMax()
选择组件 Checkbox 复选框 setValue(), addValueChangeListener()
RadioButtonGroup<T> 单选按钮组 setItems(), setValue()
Select<T> 下拉选择框 setItems(), setValue()

输入组件详解

TextField

单行文本输入,支持占位符、必填标识、前缀后缀等

NumberField

数字专用输入,可设置最小值、最大值和步进

ComboBox

支持过滤和自定义项的下拉选择

DatePicker

用户友好的日历界面,支持日期范围限制

选择组件应用

适用于各种选择场景,从简单的布尔选择到复杂的多选一配置。

按钮与链接

Button – 可点击按钮,支持主题变体和事件监听

Anchor – 超链接组件,支持内部导航和外部链接

显示组件

Text – 轻量级文本显示

Label – 表单标签或静态文本

Image – 图片展示,支持动态资源

Icon – 内置图标库,丰富视觉效果

布局管理器 (Layouts)

布局管理器是 Vaadin 中用于组织和排列 UI 组件的容器,决定了组件在屏幕上的位置和大小,并提供强大的响应式布局能力。

布局管理器 描述 核心特性
VerticalLayout 垂直堆叠子组件 setAlignItems(), setJustifyContentMode()
HorizontalLayout 水平排列子组件 setVerticalAlignment(), setFlexGrow()
FlexLayout 基于 CSS Flexbox 的通用布局 setFlexDirection(), setFlexWrap()
FormLayout 专为表单设计的两列布局 setResponsiveSteps()
SplitLayout 可拖动的分割布局 setOrientation(), addToPrimary()

垂直与水平布局

最基础的布局方式,简单易用,支持对齐和间距设置。

// 垂直布局
VerticalLayout layout = new VerticalLayout();
layout.add(component1, component2);

灵活布局

基于 CSS Flexbox,提供最大灵活性,适合复杂布局需求。

// Flexbox 布局
FlexLayout flex = new FlexLayout();
flex.setFlexDirection(FlexDirection.ROW);

表单布局

专为表单设计,自动两列排列,内置响应式支持。

// 表单布局
FormLayout form = new FormLayout();
form.add(nameField, emailField);

分割布局

可拖动的分割条,用户可自定义区域大小。

// 分割布局
SplitLayout split = new SplitLayout();
split.addToPrimary(left);

组件样式与主题

使用 Lumo 主题进行样式定制

Vaadin 默认使用 Lumo 主题,基于 CSS 自定义属性,轻松调整应用外观。

自定义主题颜色
/* styles.css */
html {
  --lumo-primary-color: #007bff;
  --lumo-primary-text-color: #0056b3;
}
可自定义的属性
  • • 主色调 (–lumo-primary-color)
  • • 背景色 (–lumo-base-color)
  • • 字体 (–lumo-font-family)
  • • 圆角 (–lumo-border-radius)

组件变体 (Variants)

通过 Java API 轻松启用预定义样式:

// 应用主题变体
button.addThemeVariants(
    ButtonVariant.LUMO_PRIMARY,
    ButtonVariant.LUMO_SUCCESS
);
LUMO_PRIMARY
LUMO_TERTIARY

自定义 CSS 与样式类

针对特定组件的精细样式调整:

// Java 代码
component.addClassName("my-custom-style");

/* CSS 文件 */
.my-custom-style {
  background: #f0f0f0;
  border: 1px solid #ccc;
}
自定义样式示例

数据绑定与表单处理

学习如何高效处理表单数据验证

数据绑定基础

Binder 类的核心作用

Binder 类是 Vaadin 中实现数据绑定的核心,充当 UI 组件和 Java Bean 属性之间的桥梁,负责自动同步数据。

Java Bean
Binder
UI 字段

绑定 Java Bean 属性

// 创建 Binder 实例
Binder<Person> binder = new Binder<>(Person.class);

// 绑定字段
binder.forField(nameField)
      .bind(Person::getName, Person::setName);

// 或者自动绑定
binder.bindInstanceFields(this);

通过方法引用指定属性的读写方式。

双向数据同步

// 从 Bean 读取数据到 UI
Person person = getPerson();
binder.readBean(person);

// 从 UI 写入数据到 Bean
try {
    binder.writeBean(person);
    save(person);
} catch (ValidationException e) {
    // 处理验证错误
}

读写分离模式,完全控制数据流。

表单验证

内置验证器

非空验证
Validator.notEmpty()
邮箱验证
new EmailValidator("错误消息")
范围验证
Validator.from(value -> value >= 0, "消息")

自定义验证规则

binder.forField(ageField)
      .withValidator(age -> age >= 18, 
          "必须年满18岁")
      .bind(Person::getAge, 
          Person::setAge);

使用 lambda 表达式创建简单验证规则。

验证错误处理

默认显示

错误信息默认显示在对应字段下方

请输入有效的邮箱地址
自定义处理
.withValidationStatusHandler(
    status -> {
        // 自定义错误显示
    }
)

处理复杂数据类型

绑定嵌套 Bean 属性

Java Bean 结构
class Person {
    private String name;
    private Address address;
}
class Address {
    private String street;
}
绑定方式
binder.forField(streetField)
      .bind("address.street");

使用点号语法绑定嵌套属性。

绑定集合与 Grid

// 创建 Grid
Grid<Person> grid = new Grid<>(Person.class);

// 设置数据
grid.setItems(peopleList);

// 自定义列
grid.addColumn(Person::getName)
    .setHeader("姓名");

Grid 组件提供对集合数据的高级绑定支持。

最佳实践

  • 对于复杂对象,使用嵌套 Bean 绑定简化代码
  • 对于集合数据,Grid 是最佳选择,支持排序和选择
  • 保持 Bean 结构清晰,避免过度嵌套

高级 UI 组件与功能

掌握 Vaadin 的高级组件和导航功能

数据展示组件:Grid

创建与配置 Grid

Grid 是 Vaadin 中最强大的数据展示组件,用于以表格形式显示大量数据。

// 自动创建列
Grid<Person> grid = new Grid<>(Person.class);

// 手动定义列
grid.addColumn(Person::getName)
    .setHeader("姓名")
    .setSortable(true);

// 设置数据源
grid.setItems(peopleList);
核心配置选项
  • • setSelectionMode() – 选择模式
  • • setMultiSort() – 多列排序
  • • setColumnReorderingAllowed() – 列拖拽
  • • setSizeFull() – 占满容器

定义列与渲染器

渲染器系统允许自定义单元格内容和外观。

// 组件渲染器
grid.addColumn(new ComponentRenderer<>(
    person -> {
        Button button = new Button("编辑");
        button.addClickListener(e -> 
            editPerson(person));
        return button;
    }))
.setHeader("操作");
可用渲染器:
ComponentRenderer LocalDateRenderer NumberRenderer IconRenderer

处理排序、过滤与分页

排序
grid.addColumn(Person::getName)
    .setHeader("姓名")
    .setSortable(true);
过滤

结合 TextField 和 DataProvider 实现

分页

使用 CallbackDataProvider 实现懒加载

网格内编辑 (In-cell Editing)

允许用户直接在表格中修改数据:

// 添加可编辑列
grid.addEditColumn(Person::getName)
    .text((item, newValue) -> {
        item.setName(newValue);
        savePerson(item);
    })
    .setHeader("姓名");
编辑模式触发
  • • 双击进入编辑
  • • 回车确认修改
  • • ESC 取消编辑
编辑器类型
文本编辑器 选择框编辑器 自定义编辑器

导航与路由

@Route 注解与视图导航

Vaadin 的导航系统基于 @Route 注解构建。

// 定义路由
@Route("users")
public class UserView extends VerticalLayout {
    // 视图内容
}

// 导航到视图
UI.getCurrent().navigate(UserView.class);
// 或
UI.getCurrent().navigate("users");
路由配置选项
  • • 空路由 @Route("") – 首页
  • • 子路由 @Route("admin/users")
  • • 路由别名 @RouteAlias
  • • 布局模板 @ParentLayout
导航方式
编程式导航
RouterLink 组件
浏览器 URL 直接访问

路由参数与通配符

路径参数
@Route("user/:userId")
public class UserDetailView 
    extends VerticalLayout {
    
    public UserDetailView(
        @RouteParam("userId") String userId) {
        // 使用参数加载数据
    }
}

URL: /user/123

通配符路由
@Route("docs/*")
public class DocumentView 
    extends VerticalLayout {
    // 匹配任意子路径
}

路由守卫与访问控制

实现路由守卫
@Route("admin")
public class AdminView 
    extends VerticalLayout 
    implements BeforeEnterObserver {
    
    @Override
    public void beforeEnter(
        BeforeEnterEvent event) {
        if (!isAdmin()) {
            event.rerouteTo(
                AccessDeniedView.class);
        }
    }
}
常用检查项
  • • 用户登录状态
  • • 用户权限角色
  • • 数据访问权限
  • • 业务规则验证

对话框与通知

使用 Dialog 创建模态窗口

// 创建对话框
Dialog dialog = new Dialog();
dialog.setHeaderTitle("编辑用户");

// 添加内容
FormLayout form = new FormLayout();
form.add(nameField, emailField);
dialog.add(form);

// 添加按钮
dialog.getFooter().add(
    new Button("保存", e -> {
        saveUser();
        dialog.close();
    }),
    new Button("取消", e -> 
        dialog.close())
);

// 显示对话框
dialog.open();

特点:

  • • 模态窗口,阻止背景交互
  • • 可自定义标题、内容和底部
  • • 支持拖拽和调整大小

显示通知与确认对话框

简单通知
// 显示成功通知
Notification.show("数据保存成功!")
    .setPosition(Notification.Position.TOP_END);

// 带样式通知
Notification notification = Notification.show(
    "操作完成", 3000, Position.BOTTOM_START);
notification.addThemeVariants(
    NotificationVariant.LUMO_SUCCESS);
确认对话框
// 创建确认对话框
ConfirmDialog dialog = new ConfirmDialog();
dialog.setHeader("确认删除");
dialog.setText("确定要删除此记录吗?");
dialog.setConfirmButton("删除", e -> {
    deleteRecord();
});
dialog.open();

使用场景对比

Dialog 对话框
  • • 复杂表单输入
  • • 详细信息展示
  • • 多步骤操作
  • • 需要用户专注的场景
Notification 通知
  • • 操作成功反馈
  • • 短暂信息提示
  • • 状态变更通知
  • • 非阻塞消息展示
ConfirmDialog 确认
  • • 危险操作确认
  • • 删除操作验证
  • • 重要决策确认
  • • 需要明确用户意图

与 Spring Boot 深度集成

学习 Vaadin 与 Spring 生态系统的完美结合

Vaadin 与 Spring Boot 基础整合

依赖管理与自动配置

通过 Vaadin Spring Boot Starter 实现无缝集成。

Maven 依赖
<dependency>
    <groupId>com.vaadin</groupId>
    <artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>
自动配置功能
  • • Vaadin Servlet 配置
  • • 资源处理设置
  • • 组件扫描集成
  • • 生产模式优化
Spring Boot 特性
自动配置
依赖注入
内嵌服务器
Actuator 监控

使用 @SpringComponent 和 @UIScope

@SpringComponent

将 Vaadin 组件声明为 Spring Bean

@SpringComponent
@UIScope
@Route("users")
public class UserView extends VerticalLayout {
    
    @Autowired
    private UserService userService;
    
    // ...
}
@UIScope

每个浏览器标签页创建独立实例

✓ 用户会话隔离
✓ 避免数据冲突
✓ 管理 UI 生命周期

依赖注入最佳实践

构造函数注入
@SpringComponent
@UIScope
public class UserForm extends FormLayout {
    
    private final UserService service;
    
    @Autowired
    public UserForm(UserService service) {
        this.service = service;
    }
}
字段注入(不推荐)
@Autowired
private UserService userService;

测试困难,依赖容器

数据访问与持久化

集成 Spring Data JPA

添加 Spring Data JPA Starter 实现数据持久化。

Maven 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>
配置示例
# application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.jpa.hibernate.ddl-auto=update
Repository 接口
@Repository
public interface UserRepository 
    extends JpaRepository<User, Long> {
    
    List<User> findByEmail(String email);
    
    @Query("select u from User u where u.name like %?1%")
    List<User> searchByName(String name);
}
JPA 实体类
@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    private String name;
    private String email;
    
    // getters and setters
}

创建 Repository 与 Service 层

Repository 层
@Repository
public interface UserRepository 
    extends JpaRepository<User, Long> {
    
    // 内置方法
    // save(), findById(), findAll()
    // deleteById(), count()
}
Service 层
@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository repository;
    
    public List<User> findAllUsers() {
        return repository.findAll();
    }
    
    public User saveUser(User user) {
        return repository.save(user);
    }
}

在 Vaadin UI 中调用后端服务

视图层注入
@SpringComponent
@UIScope
@Route("users")
public class UserView extends VerticalLayout {
    
    @Autowired
    private UserService userService;
    
    @Autowired
    public UserView(UserService service) {
        this.userService = service;
    }
}
事件处理示例
saveButton.addClickListener(e -> {
    User user = binder.getBean();
    userService.saveUser(user);
    Notification.show("保存成功");
    grid.setItems(userService.findAllUsers());
});

事务管理与性能优化

事务管理
  • • 在 Service 层使用 @Transactional
  • • 只读事务优化查询
  • • 避免长事务
  • • 正确处理事务边界
性能优化
  • • 使用懒加载策略
  • • 批量操作优化
  • • 缓存策略应用
  • • 分页查询实现

安全认证与授权

集成 Spring Security

添加 Spring Security Starter 实现认证授权。

Maven 依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
Vaadin 安全配置
@EnableWebSecurity
@Configuration
public class SecurityConfig 
    extends VaadinWebSecurity {
        
    @Override
    protected void configure(
        HttpSecurity http) throws Exception {
        // Vaadin 默认配置
        super.configure(http);
    }
}
安全特性
  • • 表单登录认证
  • • HTTP 基础认证
  • • CSRF 保护
  • • 会话管理
  • • 密码加密
  • • 方法级安全
Vaadin 安全适配
  • • 自动处理注销
  • • 导航状态同步
  • • 跨站请求保护
  • • 视图访问控制

配置登录视图与安全规则

自定义登录视图
@Route("login")
@PageTitle("Login")
public class CustomLoginView 
    extends VerticalLayout {
    
    public CustomLoginView() {
        // 自定义登录表单
    }
}
安全配置类
@Override
protected void configure(
    HttpSecurity http) throws Exception {
    
    super.configure(http);
    
    setLoginView(http, "/login");
    
    http.authorizeRequests()
        .antMatchers("/admin/**")
        .hasRole("ADMIN");
}

获取当前用户信息

通过 SecurityContext
// 获取认证对象
Authentication auth = SecurityContextHolder
    .getContext()
    .getAuthentication();

// 获取用户名
String username = auth.getName();

// 获取权限
Collection<? extends GrantedAuthority> 
    authorities = auth.getAuthorities();
在 Vaadin 视图中
// 获取当前用户
Object principal = VaadinSession
    .getCurrent()
    .getAttribute(
        VaadinSession.USER Principal_ATTRIBUTE);

// 或自定义属性
User currentUser = (User) VaadinSession
    .getCurrent()
    .getAttribute("currentUser");

方法级安全控制

@PreAuthorize 注解
@Service
public class AdminService {
    
    @PreAuthorize("hasRole('ADMIN')")
    public void deleteUser(Long userId) {
        // 只有管理员可以执行
    }
    
    @PreAuthorize("#userId == authentication.principal.id")
    public void updateUserProfile(
        Long userId, UserProfile profile) {
        // 用户只能更新自己的资料
    }
}
在 Vaadin 视图中的使用
@SpringComponent
@UIScope
@Route("admin")
@PermitAll
public class AdminView extends VerticalLayout {
    
    @Autowired
    private AdminService adminService;
    
    @RolesAllowed("ADMIN")
    public void performAdminAction() {
        // 只有管理员可以访问
    }
}
需要在配置类添加 @EnableGlobalMethodSecurity(prePostEnabled = true)

响应式设计与高级特性

掌握现代 Web 开发的高级技巧

响应式布局设计

使用 Flexbox 和 CSS Grid

Flexbox 布局

适合一维布局,处理元素对齐和分布。

FlexLayout flexLayout = new FlexLayout();
flexLayout.setFlexDirection(FlexDirection.ROW);
flexLayout.setFlexWrap(FlexWrap.WRAP);
flexLayout.setJustifyContentMode(
    JustifyContentMode.CENTER);

// JavaScript 方式
UI.getCurrent().getPage().executeJs(
    "document.querySelector('.my-layout')" +
    ".style.display = 'flex';");
CSS Grid 布局

适合二维布局,精确划分网格。

/* 在 styles.css 中 */
.my-grid-layout {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    grid-gap: 1rem;
}

/* 响应式网格 */
@media (max-width: 768px) {
    .my-grid-layout {
        grid-template-columns: 1fr;
    }
}

组件的响应式变体

Button 变体
LUMO_SMALL – 小尺寸
LUMO_PRIMARY – 主要操作
LUMO_ERROR – 危险操作
FormLayout 响应式
FormLayout formLayout = new FormLayout();
formLayout.setResponsiveSteps(
    new FormLayout.ResponsiveStep(
        "0px", 1),
    new FormLayout.ResponsiveStep(
        "600px", 2),
    new FormLayout.ResponsiveStep(
        "1024px", 3)
);

针对不同屏幕尺寸调整布局

媒体查询
/* 移动端优先 */
.my-layout {
    display: flex;
    flex-direction: column;
}

/* 平板横屏 */
@media (min-width: 768px) {
    .my-layout {
        flex-direction: row;
    }
}

/* 桌面端 */
@media (min-width: 1024px) {
    .my-layout {
        justify-content: space-between;
    }
}
Java 代码中的响应式
// 监听窗口大小变化
UI.getCurrent().getPage().addBrowserWindowResizeListener(
    event -> {
        int width = event.getWidth();
        if (width < 768) {
            // 移动端布局
            layout.setFlexDirection(FlexDirection.COLUMN);
        } else {
            // 桌面端布局
            layout.setFlexDirection(FlexDirection.ROW);
        }
    });

响应式设计最佳实践

移动优先
  • • 从小屏幕开始设计
  • • 渐进增强
  • • 触摸友好交互
  • • 简化导航
灵活布局
  • • 使用相对单位
  • • 弹性容器
  • • 智能断点
  • • 内容优先
性能优化
  • • 图片响应式加载
  • • 懒加载策略
  • • 减少重绘重排
  • • 压缩资源

自定义组件开发

扩展现有组件

public class EmailField 
    extends TextField {
    
    public EmailField() {
        super();
        setPlaceholder("email@example.com");
        addThemeVariants(
            TextFieldVariant.LUMO_SMALL);
        
        // 添加邮箱验证
        addValidator(new EmailValidator(
            "请输入有效的邮箱地址"));
    }
    
    @Override
    public void setValue(String value) {
        super.setValue(value != null ? 
            value.toLowerCase() : null);
    }
}

通过继承现有组件,添加自定义功能和行为。

组合多个组件

public class SearchBar 
    extends Composite<HorizontalLayout> {
    
    private final TextField searchField;
    private final Button searchButton;
    
    public SearchBar() {
        searchField = new TextField();
        searchButton = new Button("搜索");
        
        getContent().add(
            searchField, searchButton);
        
        // 添加事件监听器
        searchButton.addClickListener(e -> 
            fireEvent(new SearchEvent(this, 
                searchField.getValue())));
    }
    
    public void addSearchListener(
        ComponentEventListener<SearchEvent> 
            listener) {
        addListener(SearchEvent.class, listener);
    }
}

组合多个组件创建功能复杂的复合组件。

完全自定义组件

// 服务器端组件
@Tag("my-component")
@JsModule("./my-component.js")
public class MyComponent 
    extends Component {
    
    public MyComponent() {
        // 初始化组件
    }
    
    public void setValue(String value) {
        getElement().setProperty("value", value);
    }
    
    @ClientCallable
    public void handleClientEvent(
        JsonObject event) {
        // 处理客户端事件
    }
}
// 客户端 JavaScript
import { html, LitElement } from 'lit';

class MyComponent extends LitElement {
    
    static properties = {
        value: { type: String }
    };
    
    render() {
        return html`
            <div>${this.value}</div>
        `;
    }
}

customElements.define('my-component', MyComponent);

创建全新的客户端-服务器端组件,提供无限灵活性。

组件开发最佳实践

设计原则
  • 单一职责原则
    每个组件只负责一个功能
  • 可复用性
    设计通用的组件接口
  • 可组合性
    支持组件嵌套和组合
技术要点
  • 事件驱动
    提供清晰的事件 API
  • 状态管理
    正确管理组件状态
  • 性能优化
    避免不必要的重绘

性能优化

懒加载与数据分页

CallbackDataProvider
DataProvider<Person, Void> dataProvider =
    DataProvider.fromCallbacks(
        query -> {
            int offset = query.getOffset();
            int limit = query.getLimit();
            return service.findAll(
                offset, limit).stream();
        },
        query -> service.count()
    );

grid.setDataProvider(dataProvider);
分页优势
  • • 减少初始加载时间
  • • 降低内存占用
  • • 提升滚动性能
  • • 支持大数据集

@Push 与服务器推送

启用推送
@Route("dashboard")
@Push
public class DashboardView 
    extends VerticalLayout {
    
    // UI 代码
}
推送场景
  • • 实时数据更新
  • • 协作应用
  • • 通知系统
  • • 监控仪表板
推送模式
@Push(PushMode.MANUAL)
// 或
@Push(PushMode.AUTOMATIC)

组件状态管理与内存优化

内存管理策略
  • • 及时释放大对象
  • • 使用弱引用
  • • 避免静态引用
  • • 清理监听器
会话管理
@PreserveOnRefresh
@Route("long-running")
public class LongRunningView 
    extends VerticalLayout {
    
    // 在刷新时保留状态
}
监控工具
• Vaadin 调试工具
• Spring Boot Actuator
• JVM 监控工具

性能监控与调优

常见性能问题
大量组件渲染

避免一次性创建过多组件

频繁 UI 更新

批量处理 UI 状态变更

内存泄漏

及时清理监听器和引用

优化策略
组件复用

使用 ComponentRenderer

延迟加载

按需加载数据和组件

缓存优化

合理使用 Spring Cache

实践项目:任务管理系统

通过完整项目综合运用所学知识

项目概述

通过构建一个完整的任务管理系统,综合运用前面所学的所有 Vaadin 知识。这个项目将涵盖从后端服务到前端 UI 的完整开发流程。

任务管理

创建、编辑、删除任务,状态管理

用户认证

登录系统,权限控制

数据展示

Grid 表格,过滤排序

需求分析与设计

功能列表

用户认证

用户需要登录才能访问系统

任务列表

显示所有任务,支持排序和过滤

创建任务

用户可以添加新的任务

编辑任务

用户可以修改现有任务

删除任务

用户可以删除任务

状态管理

任务状态:待办、进行中、已完成

数据库设计

User 实体类
@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    // getters and setters
}
Task 实体类
@Entity
@Table(name = "tasks")
public class Task {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String description;
    @Enumerated(EnumType.STRING)
    private TaskStatus status;
    @ManyToOne
    private User user;
    // getters and setters
}
TaskStatus 枚举
public enum TaskStatus {
    TODO,
    IN_PROGRESS,
    COMPLETED
}
数据库关系
  • • 用户与任务:一对多
  • • 任务状态:枚举类型
  • • 使用 H2 内存数据库
  • • JPA 自动建表

技术栈

前端

Vaadin Flow

后端

Spring Boot

数据

Spring Data JPA + H2

安全

Spring Security

后端服务实现

创建实体类与 Repository

UserRepository
@Repository
public interface UserRepository 
    extends JpaRepository<User, Long> {
    
    Optional<User> findByUsername(
        String username);
}
TaskRepository
@Repository
public interface TaskRepository 
    extends JpaRepository<Task, Long> {
    
    List<Task> findByUser(User user);
    
    List<Task> findByStatus(
        TaskStatus status);
}

实现 Task 和 User 的 Service 层

UserService
@Service
@Transactional
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    public User findByUsername(String username) {
        return userRepository.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
    }
    
    public User saveUser(User user) {
        // 密码加密
        user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));
        return userRepository.save(user);
    }
}
TaskService
@Service
@Transactional
public class TaskService {
    
    @Autowired
    private TaskRepository taskRepository;
    
    public List<Task> findAllTasks() {
        return taskRepository.findAll();
    }
    
    public Task saveTask(Task task) {
        return taskRepository.save(task);
    }
    
    public void deleteTask(Long id) {
        taskRepository.deleteById(id);
    }
    
    public List<Task> findTasksByUser(User user) {
        return taskRepository.findByUser(user);
    }
}

配置 Spring Security 进行登录认证

@EnableWebSecurity
@Configuration
public class SecurityConfig extends VaadinWebSecurity {
    
    @Autowired
    private UserDetailsService userDetailsService;
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        setLoginView(http, "/login");
    }
    
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
        web.ignoring().antMatchers(
            "/images/**",
            "/styles/**"
        );
    }
    
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
            .passwordEncoder(passwordEncoder());
    }
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

前端 UI 实现

创建登录视图 (LoginView)

@Route("login")
@PageTitle("Login | Task Manager")
public class LoginView extends VerticalLayout {
    
    public LoginView() {
        setSizeFull();
        setAlignItems(Alignment.CENTER);
        setJustifyContentMode(JustifyContentMode.CENTER);
        
        // 创建登录表单
        LoginForm loginForm = new LoginForm();
        loginForm.addLoginListener(event -> {
            boolean authenticated = authenticate(
                event.getUsername(), 
                event.getPassword());
            
            if (authenticated) {
                UI.getCurrent().navigate("");
            } else {
                loginForm.setError(true);
            }
        });
        
        add(loginForm);
    }
    
    private boolean authenticate(String username, String password) {
        // 调用 UserService 进行认证
        return true;
    }
}

创建主任务列表视图 (TaskListView) 与 Grid

@Route("")
@PageTitle("Tasks | Task Manager")
public class TaskListView extends VerticalLayout {
    
    private final Grid<Task> grid;
    private final TaskService taskService;
    private final UserService userService;
    
    @Autowired
    public TaskListView(TaskService taskService, UserService userService) {
        this.taskService = taskService;
        this.userService = userService;
        
        // 创建 Grid
        grid = new Grid<>(Task.class);
        grid.setColumns("title", "description", "status");
        grid.addColumn(new ComponentRenderer<>(task -> {
            HorizontalLayout actions = new HorizontalLayout();
            Button editBtn = new Button("编辑", e -> editTask(task));
            Button deleteBtn = new Button("删除", e -> deleteTask(task));
            actions.add(editBtn, deleteBtn);
            return actions;
        })).setHeader("操作");
        
        // 加载数据
        User currentUser = getCurrentUser();
        grid.setItems(taskService.findTasksByUser(currentUser));
        
        // 添加按钮
        Button addTaskBtn = new Button("添加任务", e -> addTask());
        
        add(addTaskBtn, grid);
    }
    
    private void editTask(Task task) {
        // 打开编辑对话框
    }
    
    private void deleteTask(Task task) {
        // 显示确认对话框
    }
    
    private void addTask() {
        // 打开添加对话框
    }
}

创建任务编辑表单 (TaskForm) 与 Binder

public class TaskForm extends FormLayout {
    
    private final Binder<Task> binder;
    private final TaskService taskService;
    private final Callback onSave;
    
    private final TextField titleField;
    private final TextArea descriptionField;
    private final ComboBox<TaskStatus> statusField;
    
    public TaskForm(TaskService taskService, Callback onSave) {
        this.taskService = taskService;
        this.onSave = onSave;
        
        // 初始化字段
        titleField = new TextField("标题");
        descriptionField = new TextArea("描述");
        statusField = new ComboBox<>("状态");
        statusField.setItems(TaskStatus.values());
        
        // 创建 Binder
        binder = new Binder<>(Task.class);
        binder.bindInstanceFields(this);
        
        // 添加按钮
        Button saveBtn = new Button("保存", e -> save());
        Button cancelBtn = new Button("取消", e -> close());
        
        add(titleField, descriptionField, statusField, saveBtn, cancelBtn);
    }
    
    public void setTask(Task task) {
        binder.setBean(task);
    }
    
    private void save() {
        Task task = binder.getBean();
        taskService.saveTask(task);
        onSave.run();
        close();
    }
}

实现任务状态切换与删除功能

状态切换
// 在 Grid 中添加状态切换按钮
grid.addColumn(new ComponentRenderer<>(task -> {
    Button statusBtn = new Button(task.getStatus().name());
    statusBtn.addClickListener(e -> {
        // 切换状态
        TaskStatus nextStatus = getNextStatus(task.getStatus());
        task.setStatus(nextStatus);
        taskService.saveTask(task);
        
        // 刷新 Grid
        grid.getDataProvider().refreshItem(task);
    });
    return statusBtn;
})).setHeader("状态");
删除确认
// 删除功能实现
private void deleteTask(Task task) {
    ConfirmDialog dialog = new ConfirmDialog();
    dialog.setHeader("确认删除");
    dialog.setText("确定要删除任务 '" + task.getTitle() + "' 吗?");
    dialog.setConfirmButton("删除", e -> {
        taskService.deleteTask(task.getId());
        grid.getDataProvider().refreshAll();
        Notification.show("任务已删除", 3000, Position.TOP_END);
    });
    dialog.open();
}

项目完善与部署

添加响应式布局支持

主布局响应式
public class MainLayout extends AppLayout {
    
    public MainLayout() {
        // 创建导航栏
        H1 title = new H1("Task Manager");
        title.getStyle().set("font-size", "var(--lumo-font-size-l)")
            .set("margin", "0");
        
        // 添加响应式行为
        getElement().getStyle().set("flex-direction", "column");
        
        // 移动端菜单按钮
        Button menuButton = new Button("菜单", e -> {
            // 切换侧边栏
        });
        menuButton.setVisible(false);
        
        // 响应式监听
        UI.getCurrent().getPage().addBrowserWindowResizeListener(
            event -> {
                boolean mobile = event.getWidth() < 768;
                menuButton.setVisible(mobile);
            });
        
        addToNavbar(title, menuButton);
    }
}
表单响应式
// TaskForm 响应式配置
public TaskForm() {
    // 使用 FormLayout 的响应式特性
    FormLayout formLayout = new FormLayout();
    formLayout.setResponsiveSteps(
        new FormLayout.ResponsiveStep("0px", 1),
        new FormLayout.ResponsiveStep("600px", 2)
    );
    
    // 添加字段
    formLayout.add(titleField, 2);
    formLayout.add(descriptionField, 2);
    formLayout.add(statusField, 1);
    formLayout.add(priorityField, 1);
    
    add(formLayout);
}

打包应用为可执行的 JAR 文件

Maven 打包命令
mvn clean package -Pproduction
  • • 清理目标目录
  • • 编译源代码
  • • 运行测试
  • • 构建可执行 JAR
  • • 包含所有依赖
运行应用
java -jar task-manager.jar
  • • 内嵌 Tomcat 启动
  • • 自动配置数据库
  • • 默认端口 8080
  • • 生产模式优化
生产模式会优化前端资源,减小包体积

部署到云平台

Heroku 部署
  1. 1. 创建 Heroku 应用
  2. 2. 添加 Java buildpack
  3. 3. 配置 Procfile
  4. 4. 推送代码
  5. 5. 自动构建部署
web: java -jar target/task-manager.jar
AWS 部署
  1. 1. 创建 EC2 实例
  2. 2. 安装 Java 环境
  3. 3. 上传 JAR 文件
  4. 4. 配置安全组
  5. 5. 启动应用
nohup java -jar task-manager.jar &
Docker 部署
  1. 1. 创建 Dockerfile
  2. 2. 构建镜像
  3. 3. 推送镜像
  4. 4. 运行容器
  5. 5. 配置端口映射
FROM openjdk:17-jdk-alpine
云平台通常提供免费层,适合小型应用部署

项目总结

通过这个实践项目,我们综合运用了 Vaadin 的核心功能,包括:

Spring Boot 集成与配置
JPA 数据持久化
Spring Security 安全认证
Grid 数据展示组件
Binder 数据绑定
表单验证处理
响应式布局设计
对话框与通知

这个项目为您提供了完整的 Vaadin 应用开发经验,可以作为您构建更复杂企业级应用的基础。

发表评论

Only people in my network can comment.
人生梦想 - 关注前沿的计算机技术 acejoy.com 🐾 步子哥の博客 🐾 背多分论坛 🐾 知差(chai)网 🐾 DeepracticeX 社区 🐾 老薛主机 🐾 智柴论坛 🐾