尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java Swing+JDBC教务系统开发:从分层架构到Swing界面实战

Java Swing+JDBC教务系统开发:从分层架构到Swing界面实战 1. 项目概述与核心价值最近在整理过往的项目经验发现一个挺有意思的现象很多计算机专业的学生或者刚入行的朋友在课程设计或者接一些小型外包时常常会被“带界面的教务管理系统”这类题目难住。难点不在于业务逻辑本身而在于如何将Java、数据库和图形界面这三者顺畅地串联起来形成一个稳定、可用的桌面应用。我自己带过不少实习生也看过很多课程设计的代码发现大家容易在几个地方卡壳数据库连接池的管理、Swing界面的事件响应与数据绑定、以及业务逻辑与界面展示的分离。这个项目看似基础实则是一个检验Java SE综合能力的绝佳试金石它涵盖了面向对象设计、JDBC操作、多线程Swing EDT、模块化分层等核心知识点。一个完整的、带数据库的Java桌面教务管理系统绝不仅仅是“能跑通”就行。它的核心价值在于通过一个具体的业务场景学生、课程、成绩、教师管理将Java桌面开发中那些琐碎但至关重要的技术点串联起来形成一套可复用的工程实践。比如如何设计一个健壮的数据库访问层DAO来应对各种CRUD操作如何在Swing的表格JTable中优雅地展示和编辑从数据库查询出来的分页数据又该如何处理用户登录、权限校验这类通用功能把这些搞明白了以后无论是做进销存、客户关系管理还是其他任何桌面应用你都能快速上手。接下来我就结合自己多次重构这类系统的经验从头到尾拆解一遍重点会放在那些官方文档不会细讲但实际开发中一定会遇到的“坑”和最佳实践上。2. 技术栈选型与架构设计思路2.1 为什么是Swing JDBC MySQL看到这个组合可能有人会问现在不都流行Spring Boot Vue/React做前后端分离吗为什么还要用“老掉牙”的Swing和纯JDBC这里有两个核心考量。第一教学与原型验证目的。对于学习Java SE和数据库编程而言Swing和JDBC是最直接、最纯粹的技术组合能让你清晰地看到从界面点击到SQL执行再到结果渲染的完整链路没有任何框架的“黑魔法”干扰。理解了这个底层过程未来学习MyBatis、Hibernate乃至Spring Data JPA才会知其所以然。第二轻量级桌面应用的特定场景。对于一些内部工具、离线操作软件或者对安装部署有极简要求的场合一个打包成可执行JAR的Swing应用用户双击就能运行无需安装数据库客户端可使用嵌入式数据库如H2或配置Web服务器有其不可替代的便利性。当然纯JDBC操作比较繁琐所以我们通常会引入一些轻量级工具来提升效率。数据库方面MySQL社区版免费、普及率高是首选。为了简化JDBC编码我会使用Apache Commons DbUtils这个小巧的库它封装了结果集处理能大大减少模板代码。连接池则选择HikariCP它是目前性能最好的JDBC连接池之一对于桌面应用来说也足够轻量。界面构建如果不想手写大量布局代码可以用WindowBuilder这样的Eclipse/IDEA插件进行可视化拖拽设计但我会建议关键界面还是手写代码以便更好地控制布局管理和事件监听。2.2 分层架构设计告别“意大利面条”式代码最忌讳的写法就是把所有代码——从界面按钮监听器到SQL拼接再到业务判断——全部堆在一个类里。这样的代码几乎无法维护和测试。我们必须采用分层架构通常分为四层实体层Entity 对应数据库表是简单的Java Bean。例如Student类包含id、name、studentNumber等属性及其getter/setter。数据访问层DAO 职责单一只负责与数据库交互执行增删改查。每个实体类对应一个DAO接口及其实现类如StudentDao和StudentDaoImpl。这里会集中处理SQL语句和PreparedStatement使用DbUtils简化操作。业务逻辑层Service 封装具体的业务规则。例如“学生选课”这个业务在Service层里可能会依次调用CourseDao检查课程容量、StudentDao检查学生状态、SelectionDao插入选课记录并在此过程中进行事务控制。它是连接界面层和DAO层的桥梁。表示层View 即Swing界面。它的职责是接收用户输入、展示数据并将用户请求委托给Service层处理。界面层应尽可能“笨”不包含业务逻辑。此外还需要一些辅助层数据库连接与配置层 使用单例模式或静态工厂方法提供DataSourceHikariCP确保整个应用共用同一个连接池。工具层 包含一些通用方法如日期格式转换、对话框弹出、表格模型封装等。注意 很多新手会忽略事务处理。在Service层的一个方法中如果涉及多次数据库更新如扣库存和生成订单必须使用事务来保证原子性。纯JDBC环境下我们需要手动获取连接、关闭自动提交、在try-catch-finally中处理提交或回滚。这是Service层的核心职责之一。3. 数据库设计与核心表结构解析数据库设计是系统的基石设计不好后期编码会处处掣肘。教务系统的核心实体通常包括用户管理员、教师、学生、课程、班级、成绩、选课记录等。这里给出一个高度简化的核心表结构并解释设计用意。3.1 核心表结构设计-- 用户表统一存储管理员、教师、学生通过user_type区分 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL COMMENT 登录名, password VARCHAR(255) NOT NULL COMMENT 加密后的密码, real_name VARCHAR(50) NOT NULL COMMENT 真实姓名, user_type TINYINT NOT NULL COMMENT 用户类型0-管理员1-教师2-学生, student_number VARCHAR(20) NULL COMMENT 学号仅学生有, teacher_number VARCHAR(20) NULL COMMENT 工号仅教师有, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT系统用户表; -- 课程表 CREATE TABLE course ( id INT PRIMARY KEY AUTO_INCREMENT, course_code VARCHAR(20) UNIQUE NOT NULL COMMENT 课程代码, course_name VARCHAR(100) NOT NULL COMMENT 课程名称, credit DECIMAL(3,1) NOT NULL COMMENT 学分, total_hours INT NOT NULL COMMENT 总学时, teacher_id INT NOT NULL COMMENT 授课教师ID, max_students INT DEFAULT 60 COMMENT 最大选课人数, current_students INT DEFAULT 0 COMMENT 当前选课人数, FOREIGN KEY (teacher_id) REFERENCES sys_user(id) ) COMMENT课程信息表; -- 选课记录表学生与课程的多对多关系 CREATE TABLE course_selection ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, course_id INT NOT NULL, selection_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 选课时间, status TINYINT DEFAULT 1 COMMENT 状态1-已选2-已退选, UNIQUE KEY uk_student_course (student_id, course_id), -- 防止重复选课 FOREIGN KEY (student_id) REFERENCES sys_user(id), FOREIGN KEY (course_id) REFERENCES course(id) ) COMMENT选课记录表; -- 成绩表 CREATE TABLE score ( id INT PRIMARY KEY AUTO_INCREMENT, selection_id INT NOT NULL UNIQUE COMMENT 关联的选课记录ID, usual_score DECIMAL(5,2) COMMENT 平时成绩, exam_score DECIMAL(5,2) COMMENT 考试成绩, total_score DECIMAL(5,2) GENERATED ALWAYS AS (COALESCE(usual_score,0)*0.3 COALESCE(exam_score,0)*0.7) STORED COMMENT 总评成绩生成列, teacher_comment TEXT COMMENT 教师评语, FOREIGN KEY (selection_id) REFERENCES course_selection(id) ) COMMENT成绩表;3.2 设计要点与避坑指南密码存储 绝对不要明文存储密码password字段使用VARCHAR(255)在Java端使用BCrypt或PBKDF2等强哈希算法进行加密后存储。校验时比较哈希值。用户表设计 采用单表继承user_type区分还是多表student,teacher表这里采用单表好处是查询简单权限控制统一。缺点是字段会有冗余学生的teacher_number为NULL。对于中小型系统单表更简单高效。外键与索引 像course_selection表中的student_id和course_id必须建立外键约束以保证数据完整性。同时为它们创建复合唯一索引uk_student_course这是业务逻辑一个学生不能重复选同一门课在数据库层的保障。生成列的使用score表中的total_score是一个“生成列”其值由usual_score和exam_score自动计算得出。这保证了数据的一致性避免了在应用层重复计算可能带来的错误。这是MySQL 5.7和MariaDB提供的实用特性。数据字典 像user_type、status这类字段建议在项目内维护一个常量类而不是在代码中硬编码数字。例如public interface UserType { int ADMIN 0; int TEACHER 1; int STUDENT 2; }4. 核心模块实现与Swing界面开发实战4.1 数据库连接池与通用DAO封装首先我们解决数据库访问的基础问题。使用HikariCP配置连接池。// DatabaseConfig.java public class DatabaseConfig { private static volatile DataSource dataSource; private DatabaseConfig() {} public static DataSource getDataSource() { if (dataSource null) { synchronized (DatabaseConfig.class) { if (dataSource null) { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/edu_sys?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/Shanghai); config.setUsername(your_username); config.setPassword(your_password); config.setMaximumPoolSize(10); // 桌面应用10个连接足够 config.setMinimumIdle(2); config.setConnectionTimeout(30000); // 30秒 config.setIdleTimeout(600000); // 10分钟 config.setMaxLifetime(1800000); // 30分钟 // 其他优化配置... dataSource new HikariDataSource(config); } } } return dataSource; } }接着封装一个基于DbUtils的通用JdbcTemplate简化DAO层的编写。// JdbcTemplate.java (简化版) public class JdbcTemplate { private final QueryRunner queryRunner new QueryRunner(DatabaseConfig.getDataSource()); // 查询单条记录 public T T queryForObject(String sql, ResultSetHandlerT rsh, Object... params) throws SQLException { return queryRunner.query(sql, rsh, params); } // 查询列表 public T ListT queryForList(String sql, ResultSetHandlerListT rsh, Object... params) throws SQLException { return queryRunner.query(sql, rsh, params); } // 执行更新增、删、改 public int update(String sql, Object... params) throws SQLException { return queryRunner.update(sql, params); } // 在事务中执行操作 public T T executeTransaction(TransactionCallbackT action) throws SQLException { Connection conn null; try { conn DatabaseConfig.getDataSource().getConnection(); conn.setAutoCommit(false); T result action.doInTransaction(conn); conn.commit(); return result; } catch (SQLException e) { if (conn ! null) { conn.rollback(); } throw e; } finally { DbUtils.closeQuietly(conn); } } }有了这个模板DAO层的实现就非常清晰了。以StudentDaoImpl为例// StudentDaoImpl.java public class StudentDaoImpl implements StudentDao { private JdbcTemplate jdbcTemplate new JdbcTemplate(); private BeanProcessor beanProcessor new BeanProcessor(); Override public Student findById(int id) throws SQLException { String sql SELECT * FROM sys_user WHERE id ? AND user_type ?; // 使用自定义的ResultSetHandler将结果集映射为Student对象 return jdbcTemplate.queryForObject(sql, new ResultSetHandlerStudent() { Override public Student handle(ResultSet rs) throws SQLException { if (rs.next()) { return beanProcessor.toBean(rs, Student.class); } return null; } }, id, UserType.STUDENT); } Override public ListStudent findAll(int page, int size) throws SQLException { String sql SELECT * FROM sys_user WHERE user_type ? LIMIT ?, ?; int offset (page - 1) * size; return jdbcTemplate.queryForList(sql, new BeanListHandler(Student.class), UserType.STUDENT, offset, size); } // ... 其他方法 }4.2 Swing界面开发以学生信息管理为例Swing开发的核心是理解**事件分发线程EDT和模型-视图-控制器MVC**模式。所有UI的创建和更新都必须在EDT上执行而耗时的操作如数据库查询必须放在后台线程否则界面会卡死。我们构建一个学生信息管理界面包含查询表格和表单。// StudentManagementPanel.java - 主面板 public class StudentManagementPanel extends JPanel { private JTable studentTable; private StudentTableModel tableModel; // 自定义的TableModel private JTextField searchField; private JButton searchButton, addButton, editButton, deleteButton; private StudentService studentService new StudentServiceImpl(); public StudentManagementPanel() { initUI(); loadStudentData(1, 20); // 加载第一页数据 } private void initUI() { setLayout(new BorderLayout(10, 10)); // 1. 顶部工具栏 JPanel topPanel new JPanel(new FlowLayout(FlowLayout.LEFT)); searchField new JTextField(20); searchButton new JButton(查询); addButton new JButton(新增); editButton new JButton(编辑); deleteButton new JButton(删除); topPanel.add(new JLabel(姓名:)); topPanel.add(searchField); topPanel.add(searchButton); topPanel.add(Box.createHorizontalStrut(20)); // 添加间距 topPanel.add(addButton); topPanel.add(editButton); topPanel.add(deleteButton); // 2. 中央表格 tableModel new StudentTableModel(); studentTable new JTable(tableModel); studentTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); studentTable.getColumnModel().getColumn(0).setPreferredWidth(60); // ID列宽 studentTable.getColumnModel().getColumn(1).setPreferredWidth(100); // 学号 studentTable.getColumnModel().getColumn(2).setPreferredWidth(80); // 姓名 JScrollPane scrollPane new JScrollPane(studentTable); // 3. 底部分页栏略 // JPanel bottomPanel ... add(topPanel, BorderLayout.NORTH); add(scrollPane, BorderLayout.CENTER); // add(bottomPanel, BorderLayout.SOUTH); // 4. 绑定事件监听器 setupEventListeners(); } private void setupEventListeners() { searchButton.addActionListener(e - onSearch()); addButton.addActionListener(e - onAddStudent()); editButton.addActionListener(e - onEditStudent()); deleteButton.addActionListener(e - onDeleteStudent()); } private void loadStudentData(int page, int size) { // 使用SwingWorker在后台加载数据避免阻塞EDT SwingWorkerListStudent, Void worker new SwingWorker() { Override protected ListStudent doInBackground() throws Exception { // 这里是耗时操作 return studentService.getStudentsByPage(page, size); } Override protected void done() { try { ListStudent students get(); tableModel.setStudents(students); // 更新表格模型 tableModel.fireTableDataChanged(); // 通知表格刷新 } catch (InterruptedException | ExecutionException e) { JOptionPane.showMessageDialog(StudentManagementPanel.this, 加载数据失败: e.getMessage(), 错误, JOptionPane.ERROR_MESSAGE); } } }; worker.execute(); } private void onSearch() { String keyword searchField.getText().trim(); // 同样使用SwingWorker执行查询... } private void onAddStudent() { // 弹出对话框使用StudentDialog自定义的对话框 StudentDialog dialog new StudentDialog((Frame) SwingUtilities.getWindowAncestor(this), 新增学生); dialog.setVisible(true); if (dialog.isConfirmed()) { Student newStudent dialog.getStudent(); // 调用Service层保存 try { studentService.addStudent(newStudent); loadStudentData(1, 20); // 重新加载数据 JOptionPane.showMessageDialog(this, 添加成功); } catch (ServiceException ex) { JOptionPane.showMessageDialog(this, 添加失败: ex.getMessage(), 错误, JOptionPane.ERROR_MESSAGE); } } } // ... 其他方法 }自定义TableModel是关键它连接了数据和JTable视图。// StudentTableModel.java public class StudentTableModel extends AbstractTableModel { private ListStudent students new ArrayList(); private String[] columnNames {ID, 学号, 姓名, 性别, 入学时间}; Override public int getRowCount() { return students.size(); } Override public int getColumnCount() { return columnNames.length; } Override public String getColumnName(int column) { return columnNames[column]; } Override public Object getValueAt(int rowIndex, int columnIndex) { Student student students.get(rowIndex); switch (columnIndex) { case 0: return student.getId(); case 1: return student.getStudentNumber(); case 2: return student.getRealName(); case 3: return student.getGender() 1 ? 男 : 女; case 4: return new SimpleDateFormat(yyyy-MM-dd).format(student.getEnrollmentDate()); default: return null; } } public void setStudents(ListStudent students) { this.students students; } public Student getStudentAt(int row) { if (row 0 row students.size()) { return students.get(row); } return null; } }4.3 业务逻辑层Service与事务控制Service层是业务规则的家。以“学生选课”为例它需要处理多个DAO操作并保证事务。// CourseSelectionServiceImpl.java public class CourseSelectionServiceImpl implements CourseSelectionService { private CourseSelectionDao selectionDao new CourseSelectionDaoImpl(); private CourseDao courseDao new CourseDaoImpl(); private JdbcTemplate jdbcTemplate new JdbcTemplate(); Override public void selectCourse(int studentId, int courseId) throws ServiceException { // 使用我们封装的JdbcTemplate执行事务 try { jdbcTemplate.executeTransaction(conn - { // 1. 检查课程是否存在且未满 Course course courseDao.findById(courseId, conn); // 需要传递连接 if (course null) { throw new SQLException(课程不存在); } if (course.getCurrentStudents() course.getMaxStudents()) { throw new SQLException(课程人数已满); } // 2. 检查学生是否已选该课程 if (selectionDao.exists(studentId, courseId, conn)) { throw new SQLException(不能重复选课); } // 3. 插入选课记录 CourseSelection selection new CourseSelection(); selection.setStudentId(studentId); selection.setCourseId(courseId); selectionDao.insert(selection, conn); // 4. 更新课程当前人数 courseDao.incrementCurrentStudents(courseId, conn); return null; // 回调函数需要返回值这里返回null即可 }); } catch (SQLException e) { // 将SQLException转换为业务异常 throw new ServiceException(选课失败: e.getMessage(), e); } } }实操心得 Service层的异常处理很重要。底层DAO抛出的SQLException是技术细节不应该直接抛给界面层。我们将其捕获并转换为自定义的ServiceException一个运行时异常这样界面层只需要处理一种业务异常代码更清晰。同时在事务回调中所有DAO方法都需要重载一个接收Connection参数的版本以保证它们在同一个事务连接中执行。5. 功能模块详解与高级技巧5.1 用户登录与权限控制一个完整的系统必须有权限控制。我们设计一个简单的基于角色的访问控制RBAC。登录验证 在UserService中根据用户名查询用户并使用BCrypt校验密码。登录成功后将用户对象包含user_type存入一个全局的SessionContext一个单例或使用ThreadLocal中。界面权限 在主界面如MainFrame初始化时根据SessionContext中的用户类型动态设置菜单项和按钮的可用状态setEnabled。例如学生看不到“课程管理”菜单。数据权限 在Service层的方法中加入权限校验。例如在TeacherService的submitScore方法里首先要判断当前登录教师是否是这门课的授课教师。// 简单的Session上下文 public class AppContext { private static final ThreadLocalSysUser currentUser new ThreadLocal(); public static void setCurrentUser(SysUser user) { currentUser.set(user); } public static SysUser getCurrentUser() { return currentUser.get(); } public static void clear() { currentUser.remove(); } } // 在登录成功后 SysUser user userService.login(username, password); AppContext.setCurrentUser(user);5.2 数据导出与报表生成教务系统经常需要导出学生名单、成绩单等。我们可以使用Apache POI库来生成Excel文件。// ExportService.java 片段 public void exportStudentList(ListStudent students, String filePath) throws IOException { Workbook workbook new XSSFWorkbook(); // 创建.xlsx格式工作簿 Sheet sheet workbook.createSheet(学生名单); // 创建标题行 Row headerRow sheet.createRow(0); String[] headers {学号, 姓名, 性别, 学院, 专业}; for (int i 0; i headers.length; i) { Cell cell headerRow.createCell(i); cell.setCellValue(headers[i]); // 可以设置单元格样式... } // 填充数据行 int rowNum 1; for (Student student : students) { Row row sheet.createRow(rowNum); row.createCell(0).setCellValue(student.getStudentNumber()); row.createCell(1).setCellValue(student.getRealName()); row.createCell(2).setCellValue(student.getGender() 1 ? 男 : 女); row.createCell(3).setCellValue(student.getCollege()); row.createCell(4).setCellValue(student.getMajor()); } // 自动调整列宽按内容 for (int i 0; i headers.length; i) { sheet.autoSizeColumn(i); } // 写入文件 try (FileOutputStream outputStream new FileOutputStream(filePath)) { workbook.write(outputStream); } finally { workbook.close(); } }在界面上添加一个“导出”按钮点击后弹出文件选择器JFileChooser选择保存路径后调用上述服务即可。5.3 使用嵌入式数据库简化部署对于课程设计或演示要求用户安装MySQL可能太麻烦。我们可以使用嵌入式数据库H2它可以将数据库打包在应用内。在Maven或Gradle中引入H2依赖。修改DatabaseConfig中的JDBC URL为内存模式或文件模式内存模式jdbc:h2:mem:edu_sys;DB_CLOSE_DELAY-1程序关闭后数据消失。文件模式jdbc:h2:file:~/.edu_sys/db数据持久化到用户目录。在应用启动时执行初始化SQL脚本schema.sql和data.sql来创建表和插入基础数据。这样最终用户只需要有JRE环境双击JAR包就能运行完整系统体验非常好。6. 项目打包、部署与常见问题排查6.1 使用Maven Shade Plugin打包可执行JAR一个专业的项目需要打包成包含所有依赖的“胖JAR”uber JAR。Maven的maven-shade-plugin可以很好地完成这个任务。!-- 在pom.xml中配置 -- build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-shade-plugin/artifactId version3.4.1/version executions execution phasepackage/phase goals goalshade/goal /goals configuration transformers !-- 合并多个依赖中的META-INF/services文件 -- transformer implementationorg.apache.maven.plugins.shade.resource.ServicesResourceTransformer/ !-- 指定主类 -- transformer implementationorg.apache.maven.plugins.shade.resource.ManifestResourceTransformer mainClasscom.yourcompany.edusys.MainApp/mainClass /transformer /transformers filters filter !-- 排除签名文件避免冲突 -- artifact*:*/artifact excludes excludeMETA-INF/*.SF/exclude excludeMETA-INF/*.DSA/exclude excludeMETA-INF/*.RSA/exclude /excludes /filter /filters /configuration /execution /executions /plugin /plugins /build运行mvn clean package后会在target目录下生成一个*-shaded.jar文件直接使用java -jar your-app-shaded.jar命令即可运行。6.2 常见问题与排查技巧实录在开发过程中你几乎一定会遇到下面这些问题问题1界面卡死无响应现象 点击查询按钮后整个程序界面卡住鼠标变成等待状态直到数据库查询完成才恢复。原因 在Swing的事件分发线程EDT中执行了耗时的数据库操作。解决 必须使用SwingWorker、Thread或ExecutorService将耗时操作放到后台线程。如上面loadStudentData方法所示。记住黄金法则在EDT中只做UI更新所有I/O、网络、复杂计算都丢到后台。问题2数据库连接泄露现象 程序运行一段时间后无法获取新的数据库连接最终导致连接池耗尽。原因 没有正确关闭Connection、Statement或ResultSet。解决使用try-with-resources语法Java 7确保资源自动关闭。使用DbUtils的DbUtils.closeQuietly(...)方法它内部处理了null检查和异常捕获。在DAO层确保每个方法在最后都释放了资源。我们的JdbcTemplate和DbUtils的QueryRunner已经帮我们做了这件事。问题3中文字符乱码现象 从数据库读出的中文或写入数据库的中文显示为问号“???”。原因 数据库连接字符集不匹配。解决 确保三处编码统一为UTF-8数据库/表创建时指定CHARACTER SET utf8mb4MySQL。JDBC连接URL中加入参数?useUnicodetruecharacterEncodingUTF-8。Java源文件本身保存为UTF-8格式。问题4表格JTable刷新后数据错乱或显示旧数据现象 新增或删除数据后调用fireTableDataChanged()但表格显示不对。原因 直接修改了TableModel背后的数据列表但没有在修改后立即通知表格。解决在TableModel中提供完整的数据操作方法如addRow(Student s)、removeRow(int row)。在这些方法内部先更新数据列表再调用对应的fire方法fireTableRowsInserted、fireTableRowsDeleted、fireTableDataChanged。永远在EDT中更新表格模型和调用fire方法。问题5程序打包后找不到数据库驱动或配置文件现象 在IDE里运行正常打包成JAR后运行报ClassNotFoundException: com.mysql.cj.jdbc.Driver或找不到config.properties。原因 依赖的JAR包没有被打进去或配置文件路径不对。解决使用上述的Shade Plugin打包确保所有依赖都在最终的JAR里。对于配置文件不要使用绝对路径或相对于当前工作目录的路径。应该将配置文件如config.properties放在src/main/resources目录下然后使用ClassLoader.getResourceAsStream(“config.properties”)来读取。这样无论JAR在哪里运行都能正确找到资源。问题6界面在不同分辨率下布局错乱现象 在自己电脑上布局完美在别人高分辨率屏幕上控件挤在一起或位置偏移。原因 使用了绝对布局setBounds或固定大小的组件。解决优先使用Swing的布局管理器如BorderLayout、GridBagLayout、MigLayout第三方更强大。使用JPanel嵌套来组合复杂布局。为组件设置最小、最大、首选尺寸setMinimumSize,setPreferredSize。考虑使用JFrame.pack()方法让窗口根据内容自动调整大小。把这个项目从头到尾实现一遍你会对Java桌面应用开发有一个非常扎实和全面的理解。它就像一块敲门砖掌握了这些核心模式和技巧无论是转向Java EE开发还是深入学习客户端框架都会轻松很多。最关键的是在这个过程中培养出来的分层设计、异常处理和问题排查能力是任何Java开发者都不可或缺的硬实力。
返回列表