第三阶段:项目实战①小型项目开发(中篇)
📋 中篇概述
欢迎来到项目实战的中篇!在上篇中,我们完成了学生管理系统的控制台版本,掌握了Spring Boot + JPA的基础开发技能。
在中篇中,我们将开发一个更加复杂和实用的项目——图书管理系统(Swing GUI版)。这个项目将带您进入桌面应用程序开发的世界,学习如何创建用户友好的图形界面。
🎯 中篇学习目标
⏱️ 预计学习时间:8-12小时
- 📖 理论学习:3-4小时
- 💻 代码实践:4-6小时
- 🧪 测试调试:1-2小时
完成中篇学习后,您将能够:
- GUI编程技能:掌握Java Swing组件的使用和布局管理
- 事件驱动编程:理解GUI应用的事件处理机制
- MVC架构:在GUI应用中实现Model-View-Controller模式
- 用户体验设计:创建直观、易用的用户界面
- 数据绑定:实现GUI组件与数据模型的双向绑定
- 异常处理:在GUI环境中进行优雅的错误处理
📈 学习路径图
第一阶段:环境准备 (30分钟)
↓
第二阶段:GUI理论基础 (3-4小时)
├── GUI编程概念
├── Swing框架特点
├── MVC架构理解
├── 组件使用方法
├── 布局管理器
├── 事件处理机制
└── 最佳实践
↓
第三阶段:项目实战 (4-6小时)
├── 实体类设计
├── 数据访问层
├── 业务逻辑层
├── GUI界面实现
├── 事件处理
└── 对话框设计
↓
第四阶段:测试验证 (1-2小时)
├── 单元测试
├── 集成测试
├── 功能验证
└── 性能优化
↓
第五阶段:总结提升
├── 学习检查
├── 进阶建议
└── 项目扩展
🚀 项目二:图书管理系统(Swing GUI版)
项目概述
开发一个基于Swing GUI的图书管理系统,提供完整的图书信息管理功能。相比控制台版本,GUI版本提供了更加直观和用户友好的操作界面。
📸 效果预览
以下是图书管理系统GUI版的运行效果图:
1. 主界面 - 现代化的Swing GUI

现代化的Swing界面,包含菜单栏、工具栏、数据表格和状态栏,采用FlatLaf主题提供优雅的视觉效果
2. 编辑图书对话框 - 用户友好的数据录入

模态对话框设计,提供完整的图书信息编辑功能,包含数据验证和用户提示
3. 统计信息功能 - 数据可视化展示

详细的统计信息展示,包括图书总数、库存统计、价格分析等,帮助用户了解数据概况
技术要求
- 核心框架:Spring Boot 3.2+(禁用Web功能)
- GUI框架:Java Swing
- 数据持久化:Spring Data JPA、Hibernate 6.x
- 数据库:MySQL 8.0+
- Java版本:JDK 17+
- 工具包:Hutool 5.8+(字符串处理、日期操作)
- 构建工具:Maven
- 设计模式:MVC模式、观察者模式
功能需求
核心功能
-
图书信息管理
- 添加图书信息(ISBN、书名、作者、出版社、价格、库存等)
- 查看图书列表(表格显示,支持排序)
- 修改图书信息(双击编辑或编辑按钮)
- 删除图书信息(确认对话框)
- 搜索图书(按书名、作者、ISBN等条件)
-
用户界面特性
- 主窗口:菜单栏、工具栏、表格、状态栏
- 编辑对话框:模态对话框进行图书信息编辑
- 搜索功能:实时搜索或按钮搜索
- 统计功能:分类统计、库存预警等
-
数据持久化
- 使用Spring Data JPA进行数据持久化
- 支持复杂查询和统计功能
- 事务管理和数据一致性
环境准备
Maven依赖配置
在pom.xml中添加以下配置:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>org.chapter09</groupId>
<artifactId>chapter09_02</artifactId>
<version>1.0.0</version>
<name>Chapter09_02 - Book Management GUI</name>
<description>图书管理系统(GUI版)</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot Data JPA Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot Test Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Validation Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- H2数据库用于开发和测试 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
<!-- FlatLaf - 现代化的Swing外观 -->
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.2.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- JaCoCo测试覆盖率插件 -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
应用配置
创建src/main/resources/application.yml:
spring:
datasource:
url: jdbc:mysql://localhost:${MYSQL_PORT}/book_management?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
username: root
password: ${MYSQL_PASSWORD} # 请修改为您的实际密码
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update # 自动创建/更新表结构
show-sql: false # 生产环境关闭SQL显示
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
format_sql: false
# 应用配置
application:
name: book-management-gui
# 禁用Web功能,这是一个桌面应用
main:
web-application-type: none
# 日志配置
logging:
level:
org.chapter09: INFO
org.hibernate.SQL: WARN
数据库准备
-- 创建数据库
CREATE DATABASE book_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
📖 Java Swing GUI编程基础
在开始实现图书管理系统之前,让我们先系统地学习Java Swing GUI编程的基础知识。这将帮助初学者更好地理解后续的代码实现。
🎯 GUI编程学习目标
通过本章节的学习,您将能够:
- 理解GUI编程的基本概念和原理
- 掌握Swing组件的使用方法
- 学会设计用户友好的界面
- 能够独立开发简单的GUI应用程序
🔍 什么是GUI编程?
GUI(Graphical User Interface)编程是创建图形用户界面的编程方式,与命令行界面不同,它提供了:
- 可视化界面:用户通过图形元素(按钮、文本框、菜单等)与程序交互
- 事件驱动:程序响应用户的操作(点击、输入、选择等)
- 直观操作:用户无需记忆命令,通过鼠标和键盘直接操作
💡 效果预览:您可以参考本文开头的效果预览图,了解我们将要创建的现代化GUI应用程序的最终效果。从主界面的专业布局到编辑对话框的用户友好设计,都体现了优秀GUI应用的特点。
🏗️ Swing框架特点
Swing是Java提供的图形用户界面(GUI)工具包,用于创建桌面应用程序。它具有以下特点:
1. 轻量级组件
// 🔍 学习要点:Swing组件完全用Java编写,不依赖本地系统组件
JButton button = new JButton("点击我"); // 轻量级按钮
JLabel label = new JLabel("标签文本"); // 轻量级标签
JTextField field = new JTextField(); // 轻量级文本框
2. 可插拔外观感觉(Look and Feel)
// 🔍 学习要点:可以动态改变应用程序的外观
try {
// 设置系统默认外观
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel());
// 或者设置特定的外观主题
// UIManager.setLookAndFeel("com.formdev.flatlaf.FlatLightLaf");
// 更新所有组件的外观
SwingUtilities.updateComponentTreeUI(frame);
} catch (Exception e) {
e.printStackTrace();
}
3. MVC架构支持
// 🔍 学习要点:Swing组件采用MVC架构设计
// 以JTable为例:
JTable table = new JTable(); // View(视图)
DefaultTableModel model = new DefaultTableModel(); // Model(模型)
// Controller由Swing内部的UI代理处理
table.setModel(model); // 将模型绑定到视图
🧩 Swing组件层次结构
理解Swing的组件层次结构对于GUI编程至关重要:
Container(容器)
├── Window
│ ├── Frame
│ │ └── JFrame ⭐ 主窗口
│ └── Dialog
│ └── JDialog ⭐ 对话框
├── Panel
│ └── JPanel ⭐ 面板容器
└── Applet
└── JApplet
Component(组件)
├── JButton ⭐ 按钮
├── JLabel ⭐ 标签
├── JTextField ⭐ 文本框
├── JTable ⭐ 表格
├── JList ⭐ 列表
└── JTree ⭐ 树形组件
🔄 事件驱动编程模型
GUI应用程序基于事件驱动模型工作:
// 🔍 学习要点:事件处理流程
// 事件源 → 事件对象 → 事件监听器 → 事件处理方法
JButton button = new JButton("点击");
// 传统方式:匿名内部类
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击了!");
}
});
// 现代方式:Lambda表达式
button.addActionListener(e -> System.out.println("按钮被点击了!"));
// 方法引用方式
button.addActionListener(this::handleButtonClick);
📊 MVC架构在GUI应用中的体现
在图书管理系统中,MVC模式的具体应用:
1. Model层(模型)
// 🔍 实体类 - 数据模型
@Entity
public class Book {
private String isbn;
private String title;
private String author;
// ... 其他属性和方法
}
// 🔍 业务逻辑 - 服务层
@Service
public class BookService {
public List<Book> findAll() { /* 业务逻辑 */ }
public void save(Book book) { /* 业务逻辑 */ }
public void delete(String isbn) { /* 业务逻辑 */ }
}
// 🔍 数据访问 - 仓储层
@Repository
public interface BookRepository extends JpaRepository<Book, String> {
List<Book> findByTitleContaining(String title);
}
2. View层(视图)
// 🔍 GUI组件 - 视图层
@Component
public class MainFrame extends JFrame {
private JTable bookTable; // 数据展示组件
private DefaultTableModel tableModel; // 表格数据模型
// 🔍 显示数据的方法
private void displayBooks(List<Book> books) {
tableModel.setRowCount(0); // 清空现有数据
for (Book book : books) {
Object[] row = {
book.getIsbn(), book.getTitle(), book.getAuthor()
};
tableModel.addRow(row); // 添加新行数据
}
}
}
3. Controller层(控制器)
// 🔍 事件处理 - 控制器逻辑
public class MainFrame extends JFrame {
@Autowired
private BookService bookService; // 注入Model层服务
// 🔍 控制器方法 - 处理用户操作
private void addBook() {
// 1. 获取用户输入(View层)
String title = titleField.getText();
String author = authorField.getText();
// 2. 创建数据模型(Model层)
Book book = new Book();
book.setTitle(title);
book.setAuthor(author);
// 3. 调用业务逻辑(Model层)
try {
bookService.save(book);
// 4. 更新视图(View层)
loadBookData();
showMessage("图书添加成功!");
} catch (Exception e) {
showError("添加失败:" + e.getMessage());
}
}
}
🎨 核心组件学习路径
第一阶段:容器组件
1. JFrame - 主窗口
// 🔍 学习要点:JFrame是顶级容器,所有其他组件的根容器
public class MyFrame extends JFrame {
public MyFrame() {
// 基本设置
setTitle("我的应用程序"); // 设置窗口标题
setSize(800, 600); // 设置窗口大小
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置关闭操作
setLocationRelativeTo(null); // 窗口居中显示
// 设置最小尺寸
setMinimumSize(new Dimension(600, 400));
// 设置窗口图标(可选)
// setIconImage(Toolkit.getDefaultToolkit().getImage("icon.png"));
}
}
2. JPanel - 面板容器
// 🔍 学习要点:JPanel是中间容器,用于组织其他组件
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.setBorder(BorderFactory.createTitledBorder("主面板"));
// 创建子面板
JPanel buttonPanel = new JPanel(new FlowLayout());
buttonPanel.add(new JButton("确定"));
buttonPanel.add(new JButton("取消"));
// 将子面板添加到主面板
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
第二阶段:基础组件
1. JLabel - 标签
// 🔍 学习要点:显示只读文本或图标
JLabel titleLabel = new JLabel("图书标题:");
titleLabel.setFont(new Font("微软雅黑", Font.PLAIN, 12));
titleLabel.setHorizontalAlignment(SwingConstants.CENTER);
// HTML格式标签
JLabel htmlLabel = new JLabel("<html><b>粗体</b> <i>斜体</i> <font color='red'>红色</font></html>");
// 带图标的标签
JLabel iconLabel = new JLabel("状态", icon, SwingConstants.LEFT);
2. JTextField - 文本输入框
// 🔍 学习要点:单行文本输入组件
JTextField nameField = new JTextField(20); // 20列宽度
nameField.setToolTipText("请输入图书名称");
// 添加文档监听器(实时监听文本变化)
nameField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) { validateInput(); }
@Override
public void removeUpdate(DocumentEvent e) { validateInput(); }
@Override
public void changedUpdate(DocumentEvent e) { validateInput(); }
});
// 添加动作监听器(回车键)
nameField.addActionListener(e -> searchBooks());
3. JButton - 按钮
// 🔍 学习要点:用户操作触发器
JButton saveButton = new JButton("保存");
// 设置图标
saveButton.setIcon(new ImageIcon("save.png"));
// 设置快捷键
saveButton.setMnemonic('S'); // Alt+S
// 设置工具提示
saveButton.setToolTipText("保存当前图书信息");
// 添加事件监听器
saveButton.addActionListener(e -> saveBook());
// 设置按钮样式
saveButton.setPreferredSize(new Dimension(100, 30));
saveButton.setFont(new Font("微软雅黑", Font.PLAIN, 12));
第三阶段:高级组件
1. JTable - 表格
// 🔍 学习要点:表格用于显示结构化数据
String[] columnNames = {"ID", "书名", "作者", "价格"};
// 创建只读表格模型
DefaultTableModel model = new DefaultTableModel(columnNames, 0) {
@Override
public boolean isCellEditable(int row, int column) {
return false; // 设置为只读
}
};
// 创建表格
JTable table = new JTable(model);
// 设置表格属性
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); // 单选
table.setRowHeight(25); // 行高
table.getTableHeader().setReorderingAllowed(false); // 禁止列重排
// 设置列宽
table.getColumnModel().getColumn(0).setPreferredWidth(50); // ID列
table.getColumnModel().getColumn(1).setPreferredWidth(200); // 书名列
// 添加选择监听器
table.getSelectionModel().addListSelectionListener(e -> {
if (!e.getValueIsAdjusting()) {
int selectedRow = table.getSelectedRow();
if (selectedRow >= 0) {
// 处理行选择事件
handleRowSelection(selectedRow);
}
}
});
🎨 布局管理器选择指南
布局管理器负责自动安排容器中组件的位置和大小,使界面能够适应不同的窗口尺寸。
1. BorderLayout - 边界布局
🔍 适用场景:主窗口布局,需要明确的区域划分
// 🔍 学习要点:BorderLayout将容器分为5个区域
JFrame frame = new JFrame();
frame.setLayout(new BorderLayout());
// 五个区域的使用
frame.add(menuBar, BorderLayout.NORTH); // 菜单栏(顶部)
frame.add(toolBar, BorderLayout.NORTH); // 工具栏(顶部)
frame.add(mainPanel, BorderLayout.CENTER); // 主内容区(中央)
frame.add(statusBar, BorderLayout.SOUTH); // 状态栏(底部)
frame.add(sidePanel, BorderLayout.WEST); // 侧边栏(左侧)
// frame.add(rightPanel, BorderLayout.EAST); // 右侧面板(右侧)
特点:
- 五个区域:NORTH、SOUTH、EAST、WEST、CENTER
- CENTER区域会填充剩余空间
- 边缘区域根据组件首选大小调整
2. FlowLayout - 流式布局
🔍 适用场景:按钮组、工具栏、简单的水平排列
// 🔍 学习要点:FlowLayout从左到右排列组件
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 5));
// 参数:对齐方式、水平间距、垂直间距
buttonPanel.add(new JButton("确定"));
buttonPanel.add(new JButton("取消"));
buttonPanel.add(new JButton("帮助"));
特点:
- 从左到右排列组件
- 空间不足时自动换行
- 支持LEFT、CENTER、RIGHT对齐
3. GridLayout - 网格布局
🔍 适用场景:计算器界面、规整的表单布局
// 🔍 学习要点:GridLayout创建规整的网格
JPanel gridPanel = new JPanel(new GridLayout(3, 2, 5, 5));
// 参数:行数、列数、水平间距、垂直间距
gridPanel.add(new JLabel("姓名:"));
gridPanel.add(new JTextField());
gridPanel.add(new JLabel("年龄:"));
gridPanel.add(new JTextField());
gridPanel.add(new JLabel("邮箱:"));
gridPanel.add(new JTextField());
特点:
- 所有组件大小相同
- 严格的网格排列
- 适合规整的界面
4. GridBagLayout - 网格包布局
🔍 适用场景:复杂的表单布局,需要精确控制
// 🔍 学习要点:GridBagLayout提供最灵活的布局控制
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
// 设置约束条件
gbc.insets = new Insets(5, 5, 5, 5); // 边距
gbc.anchor = GridBagConstraints.WEST; // 对齐方式
// 添加标签
gbc.gridx = 0; gbc.gridy = 0;
panel.add(new JLabel("ISBN:"), gbc);
// 添加文本框
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0;
panel.add(new JTextField(20), gbc);
⚡ 事件处理详细说明
1. 事件处理流程
用户操作 → 事件对象 → 事件监听器 → 事件处理方法 → 业务逻辑
2. 常用事件类型
ActionListener - 动作事件
// 🔍 学习要点:处理按钮点击、菜单选择、回车键等
JButton button = new JButton("保存");
// 方式1:Lambda表达式(推荐)
button.addActionListener(e -> {
System.out.println("按钮被点击了!");
// 获取事件源
JButton source = (JButton) e.getSource();
System.out.println("事件源:" + source.getText());
});
// 方式2:方法引用
button.addActionListener(this::handleSaveAction);
// 方式3:传统匿名内部类
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
handleSaveAction(e);
}
});
MouseListener - 鼠标事件
// 🔍 学习要点:处理鼠标点击、进入、离开等事件
component.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
System.out.println("双击事件");
editSelectedItem();
}
}
@Override
public void mouseEntered(MouseEvent e) {
// 鼠标进入时改变样式
component.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
}
@Override
public void mouseExited(MouseEvent e) {
// 鼠标离开时恢复默认样式
component.setCursor(Cursor.getDefaultCursor());
}
});
DocumentListener - 文档变化事件
// 🔍 学习要点:实时监听文本框内容变化
textField.getDocument().addDocumentListener(new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
textChanged(); // 文本插入时触发
}
@Override
public void removeUpdate(DocumentEvent e) {
textChanged(); // 文本删除时触发
}
@Override
public void changedUpdate(DocumentEvent e) {
textChanged(); // 文本属性变化时触发
}
private void textChanged() {
String text = textField.getText();
// 实时验证输入
validateInput(text);
// 实时搜索
performSearch(text);
}
});
🎯 GUI设计最佳实践
1. 代码组织结构
// 🔍 学习要点:清晰的代码组织结构
public class MainFrame extends JFrame {
// 1. 组件声明
private JTable bookTable;
private JTextField searchField;
private JLabel statusLabel;
// 2. 构造方法
public MainFrame() {
initializeComponents();
}
// 3. 初始化方法
private void initializeComponents() {
setupWindow(); // 窗口设置
createComponents(); // 创建组件
setupLayout(); // 布局设置
setupEventHandlers(); // 事件处理
loadInitialData(); // 加载数据
}
// 4. 组件创建方法
private void createComponents() {
bookTable = new JTable();
searchField = new JTextField(20);
statusLabel = new JLabel("就绪");
}
// 5. 布局设置方法
private void setupLayout() {
setLayout(new BorderLayout());
add(createToolBar(), BorderLayout.NORTH);
add(createMainPanel(), BorderLayout.CENTER);
add(createStatusBar(), BorderLayout.SOUTH);
}
// 6. 事件处理方法
private void setupEventHandlers() {
// 集中设置所有事件处理器
}
}
2. 线程安全编程
// 🔍 学习要点:Swing的单线程模型
// ✅ 正确:在EDT线程中操作GUI
SwingUtilities.invokeLater(() -> {
updateTableData();
statusLabel.setText("数据更新完成");
});
// ❌ 错误:在后台线程中直接操作GUI
new Thread(() -> {
// 这可能导致线程安全问题
table.setModel(newModel);
}).start();
// ✅ 正确:使用SwingWorker处理耗时操作
SwingWorker<List<Book>, Void> worker = new SwingWorker<List<Book>, Void>() {
@Override
protected List<Book> doInBackground() throws Exception {
// 在后台线程中执行耗时操作
return bookService.getAllBooks();
}
@Override
protected void done() {
try {
// 在EDT线程中更新GUI
List<Book> books = get();
updateTable(books);
} catch (Exception e) {
showErrorMessage("加载数据失败:" + e.getMessage());
}
}
};
worker.execute();
3. 异常处理和用户反馈
// 🔍 学习要点:优雅的错误处理
private void saveBook() {
try {
// 输入验证
if (!validateInput()) {
return;
}
// 执行保存操作
Book book = createBookFromInput();
bookService.save(book);
// 成功反馈
showSuccessMessage("图书保存成功!");
refreshTable();
clearForm();
} catch (Exception e) {
// 错误处理
showErrorMessage("保存失败:" + e.getMessage());
logger.error("保存图书失败", e);
}
}
private boolean validateInput() {
if (titleField.getText().trim().isEmpty()) {
showWarningMessage("请输入图书标题");
titleField.requestFocus();
return false;
}
if (authorField.getText().trim().isEmpty()) {
showWarningMessage("请输入作者姓名");
authorField.requestFocus();
return false;
}
return true;
}
private void showSuccessMessage(String message) {
JOptionPane.showMessageDialog(this, message, "成功", JOptionPane.INFORMATION_MESSAGE);
}
private void showErrorMessage(String message) {
JOptionPane.showMessageDialog(this, message, "错误", JOptionPane.ERROR_MESSAGE);
}
private void showWarningMessage(String message) {
JOptionPane.showMessageDialog(this, message, "警告", JOptionPane.WARNING_MESSAGE);
}
🔧 调试GUI应用的技巧
1. 组件状态检查
// 🔍 学习要点:调试GUI组件状态
private void debugComponentState() {
System.out.println("=== 组件状态调试 ===");
System.out.println("表格行数:" + bookTable.getRowCount());
System.out.println("选中行:" + bookTable.getSelectedRow());
System.out.println("搜索框内容:'" + searchField.getText() + "'");
System.out.println("窗口大小:" + getSize());
System.out.println("窗口可见:" + isVisible());
}
2. 事件调试
// 🔍 学习要点:调试事件处理
button.addActionListener(e -> {
System.out.println("=== 按钮点击事件调试 ===");
System.out.println("事件源:" + e.getSource());
System.out.println("动作命令:" + e.getActionCommand());
System.out.println("修饰键:" + e.getModifiers());
System.out.println("时间戳:" + e.getWhen());
// 执行实际逻辑
handleButtonClick();
});
实现步骤
第一步:主启动类
package org.chapter09.bookmanagement;
import com.formdev.flatlaf.FlatLightLaf;
import org.chapter09.bookmanagement.gui.MainFrame;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import javax.swing.*;
/**
* 图书管理系统主启动类(GUI版)
* <p>
* 基于Spring Boot 3.x的GUI桌面应用程序,实现完整的图书信息管理功能。
* 该系统采用现代化的技术栈,结合Swing GUI和Spring Boot框架。
*
* <h3>🔍 学习要点:</h3>
* <ul>
* <li><strong>Spring Boot GUI应用</strong> - 理解桌面应用的启动流程</li>
* <li><strong>FlatLaf外观</strong> - 掌握现代化Swing外观的使用</li>
* <li><strong>异常处理</strong> - 学习GUI应用的错误处理</li>
* <li><strong>线程安全</strong> - 理解EDT线程的重要性</li>
* </ul>
*/
@SpringBootApplication
public class BookManagementGuiApplication {
/**
* 应用程序主入口方法
* <p>
* 启动Spring Boot应用程序并初始化Swing GUI界面。
*
* <h3>🔍 启动流程:</h3>
* <ol>
* <li><strong>设置外观主题</strong> - 使用FlatLaf提升界面美观度</li>
* <li><strong>启动Spring容器</strong> - 初始化Spring应用上下文</li>
* <li><strong>创建GUI界面</strong> - 在EDT线程中创建主窗口</li>
* <li><strong>异常处理</strong> - 优雅处理启动过程中的异常</li>
* </ol>
*/
public static void main(String[] args) {
try {
// 🎨 设置现代化的外观和感觉
setupLookAndFeel();
// 🚀 启动Spring Boot应用
System.out.println("正在启动图书管理系统...");
ConfigurableApplicationContext context = SpringApplication.run(BookManagementGuiApplication.class, args);
// 🖥️ 获取主窗口组件并显示
try {
// 从Spring容器中获取主窗口组件
MainFrame mainFrame = context.getBean(MainFrame.class);
// 在EDT线程中显示GUI
SwingUtilities.invokeLater(() -> {
mainFrame.setVisible(true);
System.out.println("✅ 图书管理系统启动成功!");
});
} catch (Exception e) {
System.err.println("❌ GUI界面创建失败:" + e.getMessage());
e.printStackTrace();
// 关闭Spring容器
context.close();
System.exit(1);
}
} catch (Exception e) {
System.err.println("❌ 应用程序启动失败:" + (e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName()));
e.printStackTrace();
System.exit(1);
}
}
/**
* 设置应用程序的外观和感觉
* <p>
* 使用FlatLaf库提供现代化的Swing外观,提升用户体验。
*
* <h3>🔍 FlatLaf特点:</h3>
* <ul>
* <li><strong>现代化设计</strong>:扁平化设计风格</li>
* <li><strong>高DPI支持</strong>:适配高分辨率显示器</li>
* <li><strong>主题支持</strong>:支持亮色和暗色主题</li>
* <li><strong>更好的字体</strong>:优化的字体渲染</li>
* </ul>
*/
private static void setupLookAndFeel() {
try {
// 设置FlatLaf亮色主题
UIManager.setLookAndFeel(new FlatLightLaf());
// 设置一些UI属性以优化显示效果
UIManager.put("Button.arc", 8); // 按钮圆角
UIManager.put("Component.arc", 8); // 组件圆角
UIManager.put("TextComponent.arc", 8); // 文本组件圆角
System.out.println("✅ FlatLaf外观主题设置成功");
} catch (Exception e) {
System.err.println("⚠️ FlatLaf外观设置失败,使用默认外观:" + e.getMessage());
// 继续使用Java默认外观
}
}
}
第二步:图书实体类
package org.chapter09.bookmanagement.entity;
import cn.hutool.core.date.DateUtil;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import jakarta.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 图书信息实体类
*/
@Entity
@Table(name = "t_books")
public class Book {
@Id
@Column(name = "isbn_", length = 20)
private String isbn; // ISBN号
@Column(name = "title_", nullable = false, length = 200)
private String title; // 书名
@Column(name = "author_", nullable = false, length = 100)
private String author; // 作者
@Column(name = "publisher_", length = 100)
private String publisher; // 出版社
@Column(name = "price_", precision = 10, scale = 2)
private BigDecimal price; // 价格
@Column(name = "quantity_", nullable = false)
private Integer quantity; // 库存数量
@Column(name = "category_", length = 50)
private String category; // 分类
@Column(name = "publish_date_")
private LocalDate publishDate; // 出版日期
@Column(name = "description_", columnDefinition = "TEXT")
private String description; // 描述
@CreationTimestamp
@Column(name = "created_time_", updatable = false)
private LocalDateTime createdTime; // 创建时间
@UpdateTimestamp
@Column(name = "updated_time_")
private LocalDateTime updatedTime; // 更新时间
// 默认构造方法
public Book() {}
// 带参构造方法
public Book(String isbn, String title, String author, String publisher,
BigDecimal price, Integer quantity, String category) {
this.isbn = isbn;
this.title = title;
this.author = author;
this.publisher = publisher;
this.price = price;
this.quantity = quantity;
this.category = category;
}
// getter和setter方法
public String getIsbn() { return isbn; }
public void setIsbn(String isbn) { this.isbn = isbn; }
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; }
public String getPublisher() { return publisher; }
public void setPublisher(String publisher) { this.publisher = publisher; }
public BigDecimal getPrice() { return price; }
public void setPrice(BigDecimal price) { this.price = price; }
public Integer getQuantity() { return quantity; }
public void setQuantity(Integer quantity) { this.quantity = quantity; }
public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public LocalDate getPublishDate() { return publishDate; }
public void setPublishDate(LocalDate publishDate) { this.publishDate = publishDate; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public LocalDateTime getCreatedTime() { return createdTime; }
public void setCreatedTime(LocalDateTime createdTime) { this.createdTime = createdTime; }
public LocalDateTime getUpdatedTime() { return updatedTime; }
public void setUpdatedTime(LocalDateTime updatedTime) { this.updatedTime = updatedTime; }
@Override
public String toString() {
return String.format("ISBN: %s, 书名: %s, 作者: %s, 出版社: %s, 价格: %.2f, 库存: %d",
isbn, title, author, publisher, price, quantity);
}
}
第三步:数据访问层(Repository)
package org.chapter09.bookmanagement.repository;
import org.chapter09.bookmanagement.entity.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.math.BigDecimal;
import java.util.List;
/**
* 图书数据访问接口
*/
@Repository
public interface BookRepository extends JpaRepository<Book, String> {
/**
* 根据书名查找图书
*/
List<Book> findByTitleContaining(String title);
/**
* 根据作者查找图书
*/
List<Book> findByAuthorContaining(String author);
/**
* 根据出版社查找图书
*/
List<Book> findByPublisherContaining(String publisher);
/**
* 根据分类查找图书
*/
List<Book> findByCategory(String category);
/**
* 根据价格范围查找图书
*/
List<Book> findByPriceBetween(BigDecimal minPrice, BigDecimal maxPrice);
/**
* 查找库存不足的图书
*/
List<Book> findByQuantityLessThan(Integer quantity);
/**
* 自定义查询:根据关键词搜索图书(书名、作者、出版社)
*/
@Query("SELECT b FROM Book b WHERE b.title LIKE %:keyword% OR b.author LIKE %:keyword% OR b.publisher LIKE %:keyword%")
List<Book> searchByKeyword(@Param("keyword") String keyword);
/**
* 自定义查询:根据分类统计图书数量
*/
@Query("SELECT b.category, COUNT(b) FROM Book b GROUP BY b.category")
List<Object[]> countBooksByCategory();
/**
* 自定义查询:根据作者统计图书数量
*/
@Query("SELECT b.author, COUNT(b) FROM Book b GROUP BY b.author ORDER BY COUNT(b) DESC")
List<Object[]> countBooksByAuthor();
/**
* 自定义查询:查找热门图书(可根据销量等指标,这里简单按库存倒序)
*/
@Query("SELECT b FROM Book b ORDER BY b.quantity DESC")
List<Book> findPopularBooks();
}
第四步:业务逻辑层(Service)
package org.chapter09.bookmanagement.service;
import cn.hutool.core.util.StrUtil;
import org.chapter09.bookmanagement.entity.Book;
import org.chapter09.bookmanagement.repository.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.util.List;
import java.util.Optional;
/**
* 图书管理业务逻辑类
*/
@Service
@Transactional
public class BookService {
@Autowired
private BookRepository bookRepository;
/**
* 添加图书
*/
public Book addBook(Book book) {
// 检查ISBN是否已存在
if (bookRepository.existsById(book.getIsbn())) {
throw new RuntimeException("ISBN已存在:" + book.getIsbn());
}
// 数据验证
validateBook(book);
return bookRepository.save(book);
}
/**
* 根据ISBN查找图书
*/
public Book findBookByIsbn(String isbn) {
Optional<Book> book = bookRepository.findById(isbn);
return book.orElse(null);
}
/**
* 获取所有图书
*/
public List<Book> getAllBooks() {
return bookRepository.findAll();
}
/**
* 更新图书信息
*/
public Book updateBook(Book book) {
// 检查图书是否存在
if (!bookRepository.existsById(book.getIsbn())) {
throw new RuntimeException("图书不存在:" + book.getIsbn());
}
// 数据验证
validateBook(book);
return bookRepository.save(book);
}
/**
* 删除图书
*/
public void deleteBook(String isbn) {
if (!bookRepository.existsById(isbn)) {
throw new RuntimeException("图书不存在:" + isbn);
}
bookRepository.deleteById(isbn);
}
/**
* 搜索图书
*/
public List<Book> searchBooks(String keyword) {
return bookRepository.searchByKeyword(keyword);
}
/**
* 更新图书库存
*/
public Book updateBookQuantity(String isbn, Integer quantity) {
Book book = findBookByIsbn(isbn);
if (book == null) {
throw new RuntimeException("图书不存在:" + isbn);
}
book.setQuantity(quantity);
return bookRepository.save(book);
}
/**
* 查找库存不足的图书
*/
public List<Book> findLowStockBooks(Integer threshold) {
return bookRepository.findByQuantityLessThan(threshold);
}
/**
* 统计各分类图书数量
*/
public List<Object[]> countBooksByCategory() {
return bookRepository.countBooksByCategory();
}
/**
* 统计各作者图书数量
*/
public List<Object[]> countBooksByAuthor() {
return bookRepository.countBooksByAuthor();
}
/**
* 验证图书信息
*/
private void validateBook(Book book) {
if (StrUtil.isBlank(book.getIsbn())) {
throw new RuntimeException("ISBN不能为空");
}
if (StrUtil.isBlank(book.getTitle())) {
throw new RuntimeException("书名不能为空");
}
if (StrUtil.isBlank(book.getAuthor())) {
throw new RuntimeException("作者不能为空");
}
if (book.getPrice() != null && book.getPrice().compareTo(BigDecimal.ZERO) < 0) {
throw new RuntimeException("价格不能为负数");
}
if (book.getQuantity() != null && book.getQuantity() < 0) {
throw new RuntimeException("库存数量不能为负数");
}
}
}
第五步:GUI主界面
package org.chapter09.bookmanagement.gui;
import org.chapter09.bookmanagement.entity.Book;
import org.chapter09.bookmanagement.service.BookService;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.math.BigDecimal;
import java.util.List;
/**
* 图书管理系统主窗口
* <p>
* 基于Swing的图形用户界面主窗口,展示了现代Java桌面应用程序的开发模式。
* 该类实现了完整的图书管理功能,包括增删改查、搜索、统计等操作。
*
* <h3>🔍 学习要点:</h3>
* <ul>
* <li><strong>Swing GUI开发</strong> - 学会使用Swing组件构建用户界面</li>
* <li><strong>MVC模式</strong> - 在GUI应用中实现Model-View-Controller</li>
* <li><strong>事件处理</strong> - 掌握GUI事件驱动编程</li>
* <li><strong>表格操作</strong> - 学会使用JTable显示和操作数据</li>
* </ul>
*/
@Component
public class MainFrame extends JFrame implements InitializingBean {
/**
* 图书业务服务
* <p>
* 通过Spring依赖注入获取BookService实例,
* 用于执行所有与图书相关的业务操作。
*
* <h3>🔍 依赖注入学习要点:</h3>
* <ul>
* <li><strong>@Autowired注解</strong> - Spring自动装配,无需手动创建对象</li>
* <li><strong>松耦合设计</strong> - GUI层不直接依赖具体实现,便于测试和维护</li>
* <li><strong>生命周期管理</strong> - Spring容器负责对象的创建、初始化和销毁</li>
* </ul>
*/
@Autowired
private BookService bookService;
// ==================== GUI组件声明 ====================
/**
* 主表格,用于显示图书列表
*/
private JTable bookTable;
/**
* 表格数据模型
*/
private DefaultTableModel tableModel;
/**
* 搜索文本框
*/
private JTextField searchField;
/**
* 状态栏标签
*/
private JLabel statusLabel;
// ==================== 表格列定义 ====================
/**
* 表格列名数组
* <p>
* 定义了图书信息表格的列标题,这些列对应Book实体的主要属性。
*/
private final String[] COLUMN_NAMES = {
"ISBN", "书名", "作者", "出版社", "价格", "库存", "分类"
};
/**
* 默认构造方法
* <p>
* 构造方法中不进行任何初始化,等待Spring依赖注入完成后
* 通过InitializingBean接口的afterPropertiesSet方法进行初始化。
*/
public MainFrame() {
// 构造方法中不进行任何初始化,等待@PostConstruct方法
}
/**
* Spring依赖注入完成后的初始化方法
* <p>
* 该方法在Spring容器完成依赖注入后自动调用,确保所有依赖都已准备就绪。
*
* <h3>🔍 Spring生命周期学习要点:</h3>
* <ul>
* <li><strong>InitializingBean接口</strong> - Spring提供的初始化回调接口</li>
* <li><strong>afterPropertiesSet方法</strong> - 在所有属性设置完成后调用</li>
* <li><strong>依赖注入时机</strong> - 确保@Autowired的依赖已经注入完成</li>
* <li><strong>初始化顺序</strong> - 构造方法 → 依赖注入 → afterPropertiesSet</li>
* </ul>
*/
@Override
public void afterPropertiesSet() throws Exception {
// 🔍 关键概念:SwingUtilities.invokeLater
// 这个方法将GUI初始化任务提交到EDT(Event Dispatch Thread)线程中执行
// 确保所有Swing组件的创建和操作都在正确的线程中进行
// 这是Swing线程安全编程的基本要求
SwingUtilities.invokeLater(this::initializeComponents);
}
/**
* 初始化所有GUI组件
* <p>
* 这个方法在Spring依赖注入完成后调用,确保所有依赖都已准备就绪。
*
* <h3>🔍 GUI构建学习要点:</h3>
* <ul>
* <li><strong>构建顺序</strong> - 从容器到组件,从外层到内层</li>
* <li><strong>职责分离</strong> - 每个方法负责一个特定的界面部分</li>
* <li><strong>模块化设计</strong> - 便于维护和修改</li>
* <li><strong>初始化流程</strong> - 窗口→菜单→工具栏→内容→状态栏→数据→显示</li>
* </ul>
*/
private void initializeComponents() {
// 🏠 窗口基本设置
// 🔍 学习要点:JFrame的基本属性设置,这些是每个桌面应用的必需步骤
setupWindow();
// 📋 创建菜单栏
// 🔍 学习要点:菜单栏提供应用程序的主要功能入口,通常包含文件、编辑、查看、帮助等菜单
createMenuBar();
// 🔧 创建工具栏
// 🔍 学习要点:工具栏提供常用功能的快速访问,通常放置在窗口顶部
createToolBar();
// 📊 创建主面板
// 🔍 学习要点:主内容区域是用户主要交互的地方,包含数据展示和操作控件
createMainPanel();
// 📍 创建状态栏
// 🔍 学习要点:状态栏显示应用程序的状态信息,通常放置在窗口底部
createStatusBar();
// 📚 加载初始数据
// 🔍 学习要点:界面创建完成后加载数据,避免空白界面,提供更好的用户体验
loadBookData();
// 🎯 设置窗口居中显示
// 🔍 学习要点:setLocationRelativeTo(null)让窗口在屏幕中央显示
setLocationRelativeTo(null);
}
/**
* 设置窗口基本属性
* <p>
* 配置主窗口的基本属性,包括标题、大小、关闭操作等。
*
* <h3>🔍 JFrame基础属性学习要点:</h3>
* <ul>
* <li><strong>setTitle()</strong> - 设置窗口标题栏显示的文本</li>
* <li><strong>setDefaultCloseOperation()</strong> - 设置点击关闭按钮时的行为</li>
* <li><strong>setSize()</strong> - 设置窗口的初始大小(宽度,高度)</li>
* <li><strong>setMinimumSize()</strong> - 设置窗口可以缩小到的最小尺寸</li>
* </ul>
*/
private void setupWindow() {
// 🔍 设置窗口标题
setTitle("图书管理系统 v1.0.0");
// 🔍 设置关闭操作
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// 🔍 设置窗口初始大小
setSize(1200, 800);
// 🔍 设置最小尺寸
setMinimumSize(new Dimension(800, 600));
}
private void createMenuBar() {
JMenuBar menuBar = new JMenuBar();
// 文件菜单
JMenu fileMenu = new JMenu("文件");
JMenuItem exitItem = new JMenuItem("退出");
exitItem.addActionListener(e -> System.exit(0));
fileMenu.add(exitItem);
// 图书管理菜单
JMenu bookMenu = new JMenu("图书管理");
JMenuItem addItem = new JMenuItem("添加图书");
JMenuItem editItem = new JMenuItem("编辑图书");
JMenuItem deleteItem = new JMenuItem("删除图书");
JMenuItem refreshItem = new JMenuItem("刷新列表");
addItem.addActionListener(e -> showAddBookDialog());
editItem.addActionListener(e -> showEditBookDialog());
deleteItem.addActionListener(e -> deleteSelectedBook());
refreshItem.addActionListener(e -> loadBookData());
bookMenu.add(addItem);
bookMenu.add(editItem);
bookMenu.add(deleteItem);
bookMenu.addSeparator();
bookMenu.add(refreshItem);
// 统计菜单
JMenu statsMenu = new JMenu("统计分析");
JMenuItem categoryStatsItem = new JMenuItem("分类统计");
JMenuItem authorStatsItem = new JMenuItem("作者统计");
JMenuItem lowStockItem = new JMenuItem("库存预警");
categoryStatsItem.addActionListener(e -> showCategoryStatistics());
authorStatsItem.addActionListener(e -> showAuthorStatistics());
lowStockItem.addActionListener(e -> showLowStockBooks());
statsMenu.add(categoryStatsItem);
statsMenu.add(authorStatsItem);
statsMenu.add(lowStockItem);
menuBar.add(fileMenu);
menuBar.add(bookMenu);
menuBar.add(statsMenu);
setJMenuBar(menuBar);
}
private void createToolBar() {
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
// 添加按钮
JButton addButton = new JButton("添加");
addButton.addActionListener(e -> showAddBookDialog());
JButton editButton = new JButton("编辑");
editButton.addActionListener(e -> showEditBookDialog());
JButton deleteButton = new JButton("删除");
deleteButton.addActionListener(e -> deleteSelectedBook());
JButton refreshButton = new JButton("刷新");
refreshButton.addActionListener(e -> loadBookData());
// 搜索组件
JLabel searchLabel = new JLabel("搜索:");
searchField = new JTextField(20);
JButton searchButton = new JButton("搜索");
searchButton.addActionListener(e -> searchBooks());
// 添加组件到工具栏
toolBar.add(addButton);
toolBar.add(editButton);
toolBar.add(deleteButton);
toolBar.addSeparator();
toolBar.add(refreshButton);
toolBar.addSeparator();
toolBar.add(searchLabel);
toolBar.add(searchField);
toolBar.add(searchButton);
add(toolBar, BorderLayout.NORTH);
}
private void createMainPanel() {
// 创建表格模型
tableModel = new DefaultTableModel(columnNames, 0) {
@Override
public boolean isCellEditable(int row, int column) {
return false; // 表格不可直接编辑
}
};
// 创建表格
bookTable = new JTable(tableModel);
bookTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
bookTable.setRowHeight(25);
// 双击编辑
bookTable.addMouseListener(new java.awt.event.MouseAdapter() {
@Override
public void mouseClicked(java.awt.event.MouseEvent e) {
if (e.getClickCount() == 2) {
showEditBookDialog();
}
}
});
// 创建滚动面板
JScrollPane scrollPane = new JScrollPane(bookTable);
add(scrollPane, BorderLayout.CENTER);
}
private void createStatusBar() {
JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
statusLabel = new JLabel("就绪");
statusPanel.add(statusLabel);
add(statusPanel, BorderLayout.SOUTH);
}
// 加载图书数据到表格
public void loadBookData() {
try {
List<Book> books = bookService.getAllBooks();
updateTable(books);
updateStatus("共加载 " + books.size() + " 本图书 - " + DateUtil.now());
} catch (Exception e) {
showErrorMessage("加载数据失败:" + e.getMessage());
}
}
// 更新表格数据
private void updateTable(List<Book> books) {
tableModel.setRowCount(0); // 清空表格
for (Book book : books) {
Object[] row = {
book.getIsbn(),
book.getTitle(),
book.getAuthor(),
book.getPublisher(),
book.getPrice(),
book.getQuantity(),
book.getCategory(),
book.getPublishDate()
};
tableModel.addRow(row);
}
}
// 其他方法实现...
// (为节省篇幅,完整代码请参考原文档)
}
第六步:图书编辑对话框
package org.chapter09.bookmanagement.gui;
import org.chapter09.bookmanagement.entity.Book;
import javax.swing.*;
import java.awt.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 图书信息编辑对话框
* <p>
* 用于添加和编辑图书信息的模态对话框。
* 该对话框展示了Swing对话框的标准设计模式和数据验证技术。
*
* <h3>🔍 学习要点:</h3>
* <ul>
* <li><strong>模态对话框</strong> - 阻塞父窗口,强制用户完成操作</li>
* <li><strong>表单设计</strong> - 使用GridBagLayout创建复杂表单</li>
* <li><strong>数据验证</strong> - 客户端输入验证和错误提示</li>
* <li><strong>对话框模式</strong> - 确定/取消的标准交互模式</li>
* </ul>
*/
public class BookEditDialog extends JDialog {
private Book book;
private boolean confirmed = false;
// 输入组件
private JTextField isbnField;
private JTextField titleField;
private JTextField authorField;
private JTextField publisherField;
private JTextField priceField;
private JTextField quantityField;
private JTextField categoryField;
private JTextField publishDateField;
private JTextArea descriptionArea;
public BookEditDialog(Frame parent, String title, Book book) {
super(parent, title, true);
this.book = book != null ? book : new Book();
initializeDialog();
if (book != null) {
populateFields();
}
}
private void initializeDialog() {
setSize(500, 600);
setLocationRelativeTo(getParent());
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
// 创建主面板
JPanel mainPanel = new JPanel(new BorderLayout());
// 创建表单面板
JPanel formPanel = createFormPanel();
mainPanel.add(formPanel, BorderLayout.CENTER);
// 创建按钮面板
JPanel buttonPanel = createButtonPanel();
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
add(mainPanel);
}
private JPanel createFormPanel() {
JPanel panel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 5, 5, 5);
// ISBN
gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST;
panel.add(new JLabel("ISBN:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0;
isbnField = new JTextField(20);
panel.add(isbnField, gbc);
// 书名
gbc.gridx = 0; gbc.gridy = 1; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0;
panel.add(new JLabel("书名:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0;
titleField = new JTextField(20);
panel.add(titleField, gbc);
// 作者
gbc.gridx = 0; gbc.gridy = 2; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0;
panel.add(new JLabel("作者:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0;
authorField = new JTextField(20);
panel.add(authorField, gbc);
// 出版社
gbc.gridx = 0; gbc.gridy = 3; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0;
panel.add(new JLabel("出版社:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0;
publisherField = new JTextField(20);
panel.add(publisherField, gbc);
// 价格
gbc.gridx = 0; gbc.gridy = 4; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0;
panel.add(new JLabel("价格:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0;
priceField = new JTextField(20);
panel.add(priceField, gbc);
// 库存
gbc.gridx = 0; gbc.gridy = 5; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0;
panel.add(new JLabel("库存:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0;
quantityField = new JTextField(20);
panel.add(quantityField, gbc);
// 分类
gbc.gridx = 0; gbc.gridy = 6; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0;
panel.add(new JLabel("分类:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0;
categoryField = new JTextField(20);
panel.add(categoryField, gbc);
// 出版日期
gbc.gridx = 0; gbc.gridy = 7; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0;
panel.add(new JLabel("出版日期:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0;
publishDateField = new JTextField(20);
publishDateField.setToolTipText("格式: yyyy-MM-dd");
panel.add(publishDateField, gbc);
// 描述
gbc.gridx = 0; gbc.gridy = 8; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 0;
gbc.anchor = GridBagConstraints.NORTHWEST;
panel.add(new JLabel("描述:"), gbc);
gbc.gridx = 1; gbc.fill = GridBagConstraints.BOTH; gbc.weightx = 1.0; gbc.weighty = 1.0;
descriptionArea = new JTextArea(5, 20);
descriptionArea.setLineWrap(true);
descriptionArea.setWrapStyleWord(true);
JScrollPane scrollPane = new JScrollPane(descriptionArea);
panel.add(scrollPane, gbc);
return panel;
}
private JPanel createButtonPanel() {
JPanel panel = new JPanel(new FlowLayout());
JButton confirmButton = new JButton("确定");
confirmButton.addActionListener(e -> confirmAction());
JButton cancelButton = new JButton("取消");
cancelButton.addActionListener(e -> dispose());
panel.add(confirmButton);
panel.add(cancelButton);
return panel;
}
private void populateFields() {
if (book != null) {
isbnField.setText(book.getIsbn());
titleField.setText(book.getTitle());
authorField.setText(book.getAuthor());
publisherField.setText(book.getPublisher());
if (book.getPrice() != null) {
priceField.setText(book.getPrice().toString());
}
if (book.getQuantity() != null) {
quantityField.setText(book.getQuantity().toString());
}
categoryField.setText(book.getCategory());
if (book.getPublishDate() != null) {
publishDateField.setText(book.getPublishDate().toString());
}
descriptionArea.setText(book.getDescription());
}
}
private void confirmAction() {
try {
// 验证并设置数据
if (StrUtil.isBlank(isbnField.getText())) {
showError("ISBN不能为空");
return;
}
book.setIsbn(isbnField.getText().trim());
if (StrUtil.isBlank(titleField.getText())) {
showError("书名不能为空");
return;
}
book.setTitle(titleField.getText().trim());
if (StrUtil.isBlank(authorField.getText())) {
showError("作者不能为空");
return;
}
book.setAuthor(authorField.getText().trim());
book.setPublisher(publisherField.getText().trim());
// 价格
if (StrUtil.isNotBlank(priceField.getText())) {
try {
book.setPrice(new BigDecimal(priceField.getText().trim()));
} catch (NumberFormatException e) {
showError("价格格式错误");
return;
}
}
// 库存
if (StrUtil.isNotBlank(quantityField.getText())) {
try {
book.setQuantity(Integer.parseInt(quantityField.getText().trim()));
} catch (NumberFormatException e) {
showError("库存格式错误");
return;
}
}
book.setCategory(categoryField.getText().trim());
// 出版日期
if (StrUtil.isNotBlank(publishDateField.getText())) {
try {
book.setPublishDate(LocalDate.parse(publishDateField.getText().trim(),
DateTimeFormatter.ofPattern("yyyy-MM-dd")));
} catch (DateTimeParseException e) {
showError("出版日期格式错误,请使用 yyyy-MM-dd 格式");
return;
}
}
book.setDescription(descriptionArea.getText().trim());
confirmed = true;
dispose();
} catch (Exception e) {
showError("数据验证失败:" + e.getMessage());
}
}
private void showError(String message) {
JOptionPane.showMessageDialog(this, message, "错误", JOptionPane.ERROR_MESSAGE);
}
public boolean isConfirmed() {
return confirmed;
}
public Book getBook() {
return book;
}
}
测试与运行实践
GUI组件测试
package org.chapter09.bookmanagement.gui;
import org.chapter09.bookmanagement.entity.Book;
import org.chapter09.bookmanagement.service.BookService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
/**
* GUI组件单元测试
*/
@ExtendWith(MockitoExtension.class)
class MainFrameTest {
@Mock
private BookService bookService;
private MainFrame mainFrame;
private List<Book> testBooks;
@BeforeEach
void setUp() {
// 创建测试数据
Book book1 = new Book();
book1.setIsbn("978-7-111-54742-6");
book1.setTitle("Java核心技术");
book1.setAuthor("Cay S. Horstmann");
book1.setPublisher("机械工业出版社");
book1.setPrice(new BigDecimal("119.00"));
book1.setQuantity(50);
book1.setCategory("编程");
testBooks = Arrays.asList(book1);
// 初始化GUI(在测试环境中)
System.setProperty("java.awt.headless", "true");
mainFrame = new MainFrame();
// 手动注入mock service
// 注意:实际测试中应该使用@InjectMocks或Spring测试框架
}
@Test
void testLoadBookData() {
// Given
when(bookService.getAllBooks()).thenReturn(testBooks);
// When
SwingUtilities.invokeLater(() -> {
mainFrame.loadBookData();
});
// Then
verify(bookService).getAllBooks();
}
}
运行脚本
创建scripts/run-gui.sh:
#!/bin/bash
echo "=== 图书管理系统(GUI版) ==="
echo "请确保MySQL数据库已启动并创建了book_management数据库"
JAR_FILE="target/chapter09_02-1.0.0.jar"
if [ ! -f "$JAR_FILE" ]; then
echo "JAR文件不存在,开始编译..."
mvn clean package -DskipTests
if [ $? -ne 0 ]; then
echo "编译失败,请检查代码"
exit 1
fi
fi
echo "启动图书管理系统GUI版..."
echo "注意:这是一个桌面GUI应用,将打开图形界面"
# 设置JVM参数
JAVA_OPTS="-Xmx512m -Xms256m"
JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8"
JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=false"
java $JAVA_OPTS -jar $JAR_FILE
🎯 运行效果说明
当您成功运行GUI应用程序后,将看到如下效果:
1. 应用程序启动阶段
- Spring Boot在后台完成初始化
- FlatLaf主题自动应用,提供现代化外观
- 数据库连接建立,表结构自动创建
- 主窗口在屏幕中央显示
2. 主界面功能展示
- 菜单栏:文件、编辑、查看、帮助等标准菜单
- 工具栏:新增、编辑、删除、刷新、统计等快捷按钮
- 数据表格:
- 清晰的列标题(ISBN、书名、作者、出版社、价格、库存、分类)
- 支持行选择和双击编辑
- 自动调整列宽,优化显示效果
- 搜索功能:实时搜索,支持模糊匹配
- 状态栏:显示当前操作状态和记录数量
3. 编辑对话框特性
- 模态设计:阻塞主窗口,确保数据完整性
- 表单布局:使用GridBagLayout实现专业的表单设计
- 数据验证:
- 必填字段检查(书名、作者不能为空)
- 数据格式验证(价格必须为正数)
- 重复性检查(ISBN唯一性)
- 用户反馈:清晰的错误提示和成功消息
4. 统计功能展示
- 数据概览:图书总数、总库存、平均价格
- 分类统计:各类别图书数量分布
- 价格分析:价格区间分布统计
- 库存预警:低库存图书提醒
5. 用户体验亮点
- 现代化外观:FlatLaf主题提供扁平化设计风格
- 响应式交互:鼠标悬停效果、按钮状态变化
- 键盘支持:Tab键导航、快捷键操作
- 国际化友好:完整的中文界面支持
💡 运行提示:
- 确保MySQL服务已启动
- 首次运行会自动创建数据库表
- 建议添加一些测试数据以获得最佳演示效果
- 如果界面显示异常,请检查Java版本是否为JDK 17+
项目特点
- 现代化GUI界面:如效果图所示,采用FlatLaf主题的现代化Swing界面,提供专业的桌面应用体验
- 完整的MVC架构:清晰的分层设计,GUI层、业务层、数据层职责分明
- Spring Boot集成:利用Spring的依赖注入和自动配置,简化开发复杂度
- 用户友好交互:
- 直观的菜单栏和工具栏设计
- 模态对话框确保数据完整性
- 实时搜索和数据验证
- 详细的统计信息展示
- 数据管理功能:
- 完整的CRUD操作支持
- 数据持久化存储
- 事务管理和数据一致性
- 技术栈现代化:
- Java 17+ 新特性支持
- Spring Boot 3.x 最新版本
- JPA/Hibernate 数据访问
- Maven 项目管理
🎯 GUI编程学习进度检查表
完成图书管理系统的学习后,请检查您是否掌握了以下技能:
基础概念理解 □
- 理解GUI编程与控制台编程的区别
- 掌握Swing框架的特点和优势
- 理解MVC架构在GUI应用中的应用
- 了解事件驱动编程的基本概念
组件使用技能 □
- 熟练使用JFrame创建主窗口
- 会使用JPanel组织界面布局
- 掌握基础组件(JLabel、JTextField、JButton)的使用
- 理解JTable的数据绑定和显示机制
- 会创建和使用JMenuBar、JToolBar
布局管理能力 □
- 掌握BorderLayout在主窗口中的应用
- 理解FlowLayout适用的场景
- 会使用GridLayout创建规整表单
- 了解GridBagLayout的灵活布局能力
- 能够嵌套使用多种布局管理器
事件处理技能 □
- 理解事件处理的基本流程
- 会使用ActionListener处理按钮和菜单事件
- 掌握MouseListener处理鼠标交互
- 能够使用DocumentListener实现实时输入监听
- 会处理表格选择和双击事件
最佳实践应用 □
- 代码结构清晰,职责分离明确
- 正确处理Swing的线程安全问题
- 添加适当的异常处理和用户反馈
- 提供良好的用户体验和界面响应
- 会使用调试技巧排查GUI问题
项目实战能力 □
- 能够独立创建完整的GUI应用程序
- 会集成Spring Boot与Swing框架
- 掌握数据绑定和界面更新技术
- 能够设计用户友好的交互界面
- 具备GUI应用的测试和调试能力
🚀 进阶学习建议
1. 深入学习方向
- 自定义组件开发:学习如何创建自定义的Swing组件
- 高级布局技术:掌握复杂界面的布局设计
- 外观主题定制:学习Look and Feel的深度定制
- 国际化支持:实现多语言界面切换
2. 实践项目推荐
- 计算器应用:练习基础组件和事件处理
- 文本编辑器:练习菜单、工具栏和文件操作
- 图片查看器:学习图形绘制和文件处理
- 数据分析工具:练习图表组件和数据可视化
3. 现代化技术探索
- JavaFX:学习更现代的Java GUI框架
- Web前端技术:了解基于Web的用户界面开发
- 跨平台解决方案:探索Electron等跨平台桌面应用技术
❓ 常见问题解答
Q1: 为什么选择Swing而不是JavaFX?
A: Swing是Java标准库的一部分,无需额外依赖,学习成本较低。虽然JavaFX更现代,但Swing仍然广泛应用于企业级桌面应用开发,是学习GUI编程的良好起点。
Q2: GUI应用如何处理数据库连接异常?
A: 在GUI应用中,应该:
- 使用try-catch捕获数据库异常
- 通过JOptionPane向用户显示友好的错误信息
- 记录详细的错误日志供开发者调试
- 提供重试机制或降级方案
Q3: 如何让Swing应用看起来更现代?
A: 可以通过以下方式改善:
- 使用FlatLaf等现代化Look and Feel
- 合理使用颜色和字体
- 添加图标和视觉元素
- 优化布局和间距
- 提供良好的用户反馈
Q4: GUI应用的性能如何优化?
A: 主要优化策略:
- 避免在EDT线程中执行耗时操作
- 使用SwingWorker处理后台任务
- 合理使用缓存减少数据库查询
- 优化表格数据的加载和显示
- 及时释放不需要的资源
Q5: 如何调试GUI应用的问题?
A: 调试技巧包括:
- 使用IDE的调试器设置断点
- 添加日志输出跟踪程序执行
- 使用组件检查器查看组件状态
- 检查事件处理的执行流程
- 验证数据绑定是否正确
Q6: 为什么我的GUI界面与效果图不一致?
A: 可能的原因和解决方案:
- Look and Feel问题:确保正确设置了FlatLaf主题,检查
UIManager.setLookAndFeel()调用 - 字体显示异常:确保系统支持中文字体,可以显式设置字体:
new Font("微软雅黑", Font.PLAIN, 12) - 布局错乱:检查布局管理器设置,确保组件的约束条件正确
- 组件大小异常:调用
pack()方法自动调整窗口大小,或使用setPreferredSize()设置组件大小 - 颜色主题不一致:确保FlatLaf依赖正确添加到项目中
Q7: GUI应用启动后没有显示窗口怎么办?
A: 检查以下几点:
- EDT线程问题:确保GUI创建在事件分发线程中:
SwingUtilities.invokeLater() - 窗口可见性:确保调用了
setVisible(true) - 窗口位置:可能窗口显示在屏幕外,使用
setLocationRelativeTo(null)居中显示 - 异常阻塞:检查控制台是否有异常信息,可能初始化过程中出现错误
- 系统兼容性:确保Java版本兼容,建议使用JDK 17+
Q8: 如何获得与效果图相同的数据展示效果?
A: 建议添加以下测试数据:
// 在应用启动时添加测试数据
Book book1 = new Book("978-7-111-54742-6", "Java核心技术", "Cay S. Horstmann",
"机械工业出版社", new BigDecimal("119.00"), 50, "编程");
Book book2 = new Book("978-7-115-48831-2", "Spring Boot实战", "Craig Walls",
"人民邮电出版社", new BigDecimal("89.00"), 30, "编程");
// 添加更多测试数据...
这样可以获得与效果图相同的丰富数据展示效果。
📖 中篇小结
在中篇中,我们完成了:
✅ GUI编程基础:系统学习了Swing框架的核心概念和组件使用
✅ MVC架构应用:深入理解了MVC模式在GUI应用中的具体实现
✅ 布局管理掌握:学会了各种布局管理器的选择和使用技巧
✅ 事件处理机制:掌握了GUI应用的事件驱动编程模式
✅ 最佳实践指导:学习了GUI开发的代码组织和调试技巧
✅ GUI框架搭建:Spring Boot + Swing的完整集成
✅ 实体设计:图书实体类和复杂的业务逻辑
✅ 数据访问:Repository接口和统计查询方法
✅ GUI界面:主窗口、菜单栏、工具栏、表格展示,如效果图所示的专业界面设计
✅ 对话框设计:模态对话框和数据验证,提供用户友好的交互体验
✅ 事件处理:用户交互和数据绑定,实现响应式的GUI应用
✅ 视觉效果:现代化的FlatLaf主题,提供与效果图一致的优雅界面
🔗 相关文章
- 第三阶段:项目实战①小型项目开发(上篇) - 学生管理系统控制台版
- 第三阶段:项目实战①小型项目开发(下篇) - 项目运行与最佳实践
🎉 中篇完成!
你已经掌握了Swing GUI应用开发的核心技能!
接下来让我们进入下篇,学习项目运行和最佳实践。
📖 继续阅读:下篇 - 项目运行与最佳实践
436

被折叠的 条评论
为什么被折叠?



