Vaadin 开发教程 从入门到精通
掌握 Java 全栈 Web 开发的艺术,构建现代化、响应式企业级应用
纯 Java 开发
无需编写 HTML/CSS/JavaScript,专注业务逻辑
丰富组件库
开箱即用的企业级 UI 组件

Vaadin 基础入门
掌握 Vaadin 的核心概念和开发环境搭建
Vaadin 简介与核心概念
什么是 Vaadin:服务器端驱动的 UI 框架
Vaadin 是一个开源的 Java Web 应用开发框架,其核心理念在于提供一种服务器端驱动的编程模型,使得开发者能够完全使用 Java 语言来构建现代化的、响应式的用户界面,而无需编写任何 HTML、CSS 或 JavaScript 代码。
服务器端架构优势
- 强大的类型安全保证:编译时捕获大量错误
- 开发效率极大提升:统一的技术栈和工具链
- 内置安全防护:有效防止 XSS 等常见攻击
工作流程
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 在线生成项目
项目结构解析
文件/类 | 位置 | 核心作用 |
---|---|---|
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!")
);
工作原理
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() |
输入组件详解
单行文本输入,支持占位符、必填标识、前缀后缀等
数字专用输入,可设置最小值、最大值和步进
支持过滤和自定义项的下拉选择
用户友好的日历界面,支持日期范围限制
选择组件应用
适用于各种选择场景,从简单的布尔选择到复杂的多选一配置。
按钮与链接
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() |
垂直与水平布局
最基础的布局方式,简单易用,支持对齐和间距设置。
灵活布局
基于 CSS Flexbox,提供最大灵活性,适合复杂布局需求。
表单布局
专为表单设计,自动两列排列,内置响应式支持。
分割布局
可拖动的分割条,用户可自定义区域大小。
组件样式与主题
使用 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
);
自定义 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 实例
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 属性
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("操作");
处理排序、过滤与分页
排序
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
导航方式
路由参数与通配符
路径参数
@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 特性
使用 @SpringComponent 和 @UIScope
@SpringComponent
将 Vaadin 组件声明为 Spring Bean
@SpringComponent
@UIScope
@Route("users")
public class UserView extends VerticalLayout {
@Autowired
private UserService userService;
// ...
}
@UIScope
每个浏览器标签页创建独立实例
依赖注入最佳实践
构造函数注入
@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() {
// 只有管理员可以访问
}
}
响应式设计与高级特性
掌握现代 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 变体
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 {
// 在刷新时保留状态
}
监控工具
性能监控与调优
常见性能问题
避免一次性创建过多组件
批量处理 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. 创建 Heroku 应用
- 2. 添加 Java buildpack
- 3. 配置 Procfile
- 4. 推送代码
- 5. 自动构建部署
web: java -jar target/task-manager.jar
AWS 部署
- 1. 创建 EC2 实例
- 2. 安装 Java 环境
- 3. 上传 JAR 文件
- 4. 配置安全组
- 5. 启动应用
nohup java -jar task-manager.jar &
Docker 部署
- 1. 创建 Dockerfile
- 2. 构建镜像
- 3. 推送镜像
- 4. 运行容器
- 5. 配置端口映射
FROM openjdk:17-jdk-alpine
项目总结
通过这个实践项目,我们综合运用了 Vaadin 的核心功能,包括:
这个项目为您提供了完整的 Vaadin 应用开发经验,可以作为您构建更复杂企业级应用的基础。