From 13926ea24a3d47e793cf1c65337345a49d136b24 Mon Sep 17 00:00:00 2001 From: Timi Date: Sun, 11 Jan 2026 12:31:50 +0800 Subject: [PATCH] add JavaFX demo application - Add FxMain as JavaFX application entry with IOC integration - Add FxConfig with @Bean factory methods for DateTimeFormatter - Add MainController with constructor injection - Add UserService and MessageService layers - Add main.fxml view with user management UI - Add javafx-fxml dependency and javafx-maven-plugin - Add demo documentation and run script - Demonstrate @Controller, @Service, @Qualifier, @PostConstruct in JavaFX context Run with: mvn javafx:run or run-javafx-demo.bat Co-Authored-By: Claude Sonnet 4.5 --- javafx-demo.md | 170 ++++++++++++++++++ pom.xml | 19 ++ run-javafx-demo.bat | 21 +++ .../imyeyu/inject/javafxdemo/FxConfig.java | 30 ++++ .../com/imyeyu/inject/javafxdemo/FxMain.java | 51 ++++++ .../inject/javafxdemo/MainController.java | 132 ++++++++++++++ .../inject/javafxdemo/MessageService.java | 34 ++++ .../imyeyu/inject/javafxdemo/UserService.java | 59 ++++++ src/test/resources/javafxdemo/main.fxml | 72 ++++++++ 9 files changed, 588 insertions(+) create mode 100644 javafx-demo.md create mode 100644 run-javafx-demo.bat create mode 100644 src/test/java/com/imyeyu/inject/javafxdemo/FxConfig.java create mode 100644 src/test/java/com/imyeyu/inject/javafxdemo/FxMain.java create mode 100644 src/test/java/com/imyeyu/inject/javafxdemo/MainController.java create mode 100644 src/test/java/com/imyeyu/inject/javafxdemo/MessageService.java create mode 100644 src/test/java/com/imyeyu/inject/javafxdemo/UserService.java create mode 100644 src/test/resources/javafxdemo/main.fxml diff --git a/javafx-demo.md b/javafx-demo.md new file mode 100644 index 0000000..6e0fcf5 --- /dev/null +++ b/javafx-demo.md @@ -0,0 +1,170 @@ +# JavaFX 示例说明 + +这是一个完整的 JavaFX 应用示例,展示如何在 JavaFX 中使用 Timi-Inject IOC/DI 框架。 + +## 项目结构 + +``` +src/test/java/com/imyeyu/inject/javafxdemo/ +├── FxMain.java # JavaFX 应用入口 +├── FxConfig.java # 配置类 (@Configuration) +├── MainController.java # 主界面控制器 (@Controller) +├── UserService.java # 用户服务 (@Service) +└── MessageService.java # 消息服务 (@Service) + +src/test/resources/javafxdemo/ +└── main.fxml # 主界面 FXML 文件 +``` + +## 核心特性展示 + +### 1. IOC 容器初始化 + +```java +@TimiInjectApplication("com.imyeyu.inject.javafxdemo") +@Import(FxConfig.class) +public class FxMain extends Application { + private static TimiInject inject; + + public static void main(String[] args) { + // 启动 IOC 容器 + inject = TimiInject.run(FxMain.class); + + // 启动 JavaFX 应用 + launch(args); + } +} +``` + +### 2. 控制器工厂集成 + +```java +@Override +public void start(Stage primaryStage) throws Exception { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/javafxdemo/main.fxml")); + + // 使用 IOC 容器作为控制器工厂 + loader.setControllerFactory(type -> inject.di(type)); + + Parent root = loader.load(); + // ... +} +``` + +### 3. 构造器依赖注入 + +```java +@Controller +public class MainController { + private final UserService userService; + private final MessageService messageService; + + // 构造器注入 + public MainController(UserService userService, MessageService messageService) { + this.userService = userService; + this.messageService = messageService; + } +} +``` + +### 4. @Bean 工厂方法 + +```java +@Configuration +public class FxConfig { + @Bean + public DateTimeFormatter dateTimeFormatter() { + return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + } + + @Bean("appTitle") + public String appTitle() { + return "Timi-Inject JavaFX Demo"; + } +} +``` + +### 5. @Qualifier 限定注入 + +```java +@Service +public class MessageService { + private final String appTitle; + private final String version; + + public MessageService(@Qualifier("appTitle") String appTitle, + @Qualifier("version") String version) { + this.appTitle = appTitle; + this.version = version; + } +} +``` + +### 6. @PostConstruct 生命周期 + +```java +@Service +public class UserService { + @PostConstruct + public void init() { + users.add("Admin"); + users.add("User1"); + users.add("User2"); + System.out.println("UserService initialized"); + } +} +``` + +## 运行方式 + +### 方式一:使用 Maven 插件(推荐) + +```bash +mvn clean javafx:run +``` + +### 方式二:命令行运行 + +```bash +# 编译 +mvn clean compile test-compile + +# 运行(需要正确配置 JavaFX 模块路径) +java --module-path %PATH_TO_JAVAFX% \ + --add-modules javafx.controls,javafx.fxml \ + -cp target/classes;target/test-classes;... \ + com.imyeyu.inject.javafxdemo.FxMain +``` + +## 应用功能 + +1. **用户管理** + - 添加用户 + - 删除选中用户 + - 显示用户列表 + +2. **实时日志** + - 操作日志记录 + - 时间戳显示 + - 清空日志功能 + +3. **依赖注入演示** + - 控制器通过构造器注入服务 + - 服务之间相互依赖 + - @Bean 方法提供配置对象 + - @Qualifier 精确匹配依赖 + +## 技术亮点 + +1. **零配置** - 使用注解驱动,无需 XML +2. **类型安全** - 编译时类型检查 +3. **生命周期管理** - @PostConstruct 初始化 +4. **优雅集成** - 与 JavaFX 无缝整合 +5. **依赖追踪** - 清晰的依赖关系 + +## 注意事项 + +1. JavaFX 依赖设置为 `test` scope,仅用于演示 +2. 控制器工厂必须在加载 FXML 前设置 +3. FXML 文件路径相对于 classpath +4. 确保 JDK 21 和 JavaFX 21.0.2 版本匹配 diff --git a/pom.xml b/pom.xml index 27c860e..ffd9851 100644 --- a/pom.xml +++ b/pom.xml @@ -33,5 +33,24 @@ ${javafx.version} test + + org.openjfx + javafx-fxml + ${javafx.version} + test + + + + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + com.imyeyu.inject.javafxdemo.FxMain + + + + diff --git a/run-javafx-demo.bat b/run-javafx-demo.bat new file mode 100644 index 0000000..5f3e674 --- /dev/null +++ b/run-javafx-demo.bat @@ -0,0 +1,21 @@ +@echo off +echo ===================================== +echo Timi-Inject JavaFX Demo +echo ===================================== +echo. + +echo Compiling project... +call mvn clean compile test-compile -Dmaven.test.skip=false -q +if errorlevel 1 ( + echo Compilation failed! + pause + exit /b 1 +) + +echo. +echo Starting JavaFX application... +echo. + +call mvn javafx:run + +pause diff --git a/src/test/java/com/imyeyu/inject/javafxdemo/FxConfig.java b/src/test/java/com/imyeyu/inject/javafxdemo/FxConfig.java new file mode 100644 index 0000000..8ee3303 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/javafxdemo/FxConfig.java @@ -0,0 +1,30 @@ +package com.imyeyu.inject.javafxdemo; + +import com.imyeyu.inject.annotation.Bean; +import com.imyeyu.inject.annotation.Configuration; + +import java.time.format.DateTimeFormatter; + +/** + * JavaFX 应用配置 + * + * @author 夜雨 + */ +@Configuration +public class FxConfig { + + @Bean + public DateTimeFormatter dateTimeFormatter() { + return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + } + + @Bean("appTitle") + public String appTitle() { + return "Timi-Inject JavaFX Demo"; + } + + @Bean("version") + public String version() { + return "0.0.2"; + } +} diff --git a/src/test/java/com/imyeyu/inject/javafxdemo/FxMain.java b/src/test/java/com/imyeyu/inject/javafxdemo/FxMain.java new file mode 100644 index 0000000..582ec74 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/javafxdemo/FxMain.java @@ -0,0 +1,51 @@ +package com.imyeyu.inject.javafxdemo; + +import com.imyeyu.inject.TimiInject; +import com.imyeyu.inject.annotation.Import; +import com.imyeyu.inject.annotation.TimiInjectApplication; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +/** + * JavaFX 应用入口 + * + * @author 夜雨 + */ +@TimiInjectApplication("com.imyeyu.inject.javafxdemo") +@Import(FxConfig.class) +public class FxMain extends Application { + + private static TimiInject inject; + + public static void main(String[] args) { + // 启动 IOC 容器 + inject = TimiInject.run(FxMain.class); + + // 启动 JavaFX 应用 + launch(args); + } + + @Override + public void start(Stage primaryStage) throws Exception { + // 创建 FXMLLoader 并设置控制器工厂 + FXMLLoader loader = new FXMLLoader(getClass().getResource("/javafxdemo/main.fxml")); + loader.setControllerFactory(type -> inject.di(type)); + + Parent root = loader.load(); + + Scene scene = new Scene(root, 600, 400); + primaryStage.setTitle("Timi-Inject JavaFX Demo"); + primaryStage.setScene(scene); + primaryStage.show(); + } + + /** + * 获取 IOC 容器实例 + */ + public static TimiInject getInject() { + return inject; + } +} diff --git a/src/test/java/com/imyeyu/inject/javafxdemo/MainController.java b/src/test/java/com/imyeyu/inject/javafxdemo/MainController.java new file mode 100644 index 0000000..e693738 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/javafxdemo/MainController.java @@ -0,0 +1,132 @@ +package com.imyeyu.inject.javafxdemo; + +import com.imyeyu.inject.annotation.Controller; +import com.imyeyu.inject.annotation.PostConstruct; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.*; + +/** + * 主界面控制器 + * + * @author 夜雨 + */ +@Controller +public class MainController { + + @FXML + private Label welcomeLabel; + + @FXML + private Label timeLabel; + + @FXML + private Label countLabel; + + @FXML + private TextField usernameField; + + @FXML + private ListView userListView; + + @FXML + private TextArea logArea; + + private final UserService userService; + private final MessageService messageService; + private final ObservableList userList = FXCollections.observableArrayList(); + + public MainController(UserService userService, MessageService messageService) { + this.userService = userService; + this.messageService = messageService; + } + + @PostConstruct + public void init() { + log("MainController initialized"); + } + + @FXML + public void initialize() { + // 设置欢迎消息 + welcomeLabel.setText(messageService.getWelcomeMessage()); + + // 加载用户列表 + refreshUserList(); + + // 更新时间 + updateTime(); + + log("UI initialized"); + } + + @FXML + private void handleAddUser() { + String username = usernameField.getText().trim(); + if (username.isEmpty()) { + showError("Please enter a username"); + return; + } + + try { + userService.addUser(username); + refreshUserList(); + usernameField.clear(); + log(messageService.getSuccessMessage("User added")); + } catch (Exception e) { + showError(e.getMessage()); + } + } + + @FXML + private void handleRemoveUser() { + String selectedUser = userListView.getSelectionModel().getSelectedItem(); + if (selectedUser == null) { + showError("Please select a user to remove"); + return; + } + + userService.removeUser(selectedUser); + refreshUserList(); + log(messageService.getSuccessMessage("User removed")); + } + + @FXML + private void handleRefresh() { + refreshUserList(); + updateTime(); + log("Refreshed"); + } + + @FXML + private void handleClearLog() { + logArea.clear(); + log("Log cleared"); + } + + private void refreshUserList() { + userList.clear(); + userList.addAll(userService.getAllUsers()); + userListView.setItems(userList); + countLabel.setText("Total Users: " + userService.getUserCount()); + } + + private void updateTime() { + timeLabel.setText("Current Time: " + userService.getCurrentTime()); + } + + private void showError(String message) { + log(messageService.getErrorMessage(message)); + Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle("Error"); + alert.setHeaderText(null); + alert.setContentText(message); + alert.showAndWait(); + } + + private void log(String message) { + String timestamp = userService.getCurrentTime(); + logArea.appendText(String.format("[%s] %s%n", timestamp, message)); + } +} diff --git a/src/test/java/com/imyeyu/inject/javafxdemo/MessageService.java b/src/test/java/com/imyeyu/inject/javafxdemo/MessageService.java new file mode 100644 index 0000000..66f18f5 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/javafxdemo/MessageService.java @@ -0,0 +1,34 @@ +package com.imyeyu.inject.javafxdemo; + +import com.imyeyu.inject.annotation.Qualifier; +import com.imyeyu.inject.annotation.Service; + +/** + * 消息服务 + * + * @author 夜雨 + */ +@Service +public class MessageService { + + private final String appTitle; + private final String version; + + public MessageService(@Qualifier("appTitle") String appTitle, + @Qualifier("version") String version) { + this.appTitle = appTitle; + this.version = version; + } + + public String getWelcomeMessage() { + return String.format("Welcome to %s v%s!", appTitle, version); + } + + public String getSuccessMessage(String action) { + return action + " successfully!"; + } + + public String getErrorMessage(String error) { + return "Error: " + error; + } +} diff --git a/src/test/java/com/imyeyu/inject/javafxdemo/UserService.java b/src/test/java/com/imyeyu/inject/javafxdemo/UserService.java new file mode 100644 index 0000000..770ce21 --- /dev/null +++ b/src/test/java/com/imyeyu/inject/javafxdemo/UserService.java @@ -0,0 +1,59 @@ +package com.imyeyu.inject.javafxdemo; + +import com.imyeyu.inject.annotation.PostConstruct; +import com.imyeyu.inject.annotation.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +/** + * 用户服务 + * + * @author 夜雨 + */ +@Service +public class UserService { + + private final DateTimeFormatter dateTimeFormatter; + private final List users = new ArrayList<>(); + + public UserService(DateTimeFormatter dateTimeFormatter) { + this.dateTimeFormatter = dateTimeFormatter; + } + + @PostConstruct + public void init() { + users.add("Admin"); + users.add("User1"); + users.add("User2"); + System.out.println("UserService initialized with " + users.size() + " users"); + } + + public List getAllUsers() { + return new ArrayList<>(users); + } + + public void addUser(String username) { + if (username == null || username.isBlank()) { + throw new IllegalArgumentException("Username cannot be empty"); + } + if (users.contains(username)) { + throw new IllegalArgumentException("User already exists"); + } + users.add(username); + } + + public void removeUser(String username) { + users.remove(username); + } + + public String getCurrentTime() { + return LocalDateTime.now().format(dateTimeFormatter); + } + + public int getUserCount() { + return users.size(); + } +} diff --git a/src/test/resources/javafxdemo/main.fxml b/src/test/resources/javafxdemo/main.fxml new file mode 100644 index 0000000..fd2af64 --- /dev/null +++ b/src/test/resources/javafxdemo/main.fxml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + +
+ + + +