初始化Spring定义的FXMLController时注入远程代理抛出NoSuchBeanDefinitionException
解决RMI Service Bean获取时的NoSuchBeanDefinitionException问题
嘿,我看了你的代码,发现问题主要出在Spring上下文的创建方式以及JavaFX Controller和Spring的整合上,下面给你拆解一下问题根源和解决办法:
问题到底出在哪?
- 你在
LoginController的initialize()里手动new AnnotationConfigApplicationContext("Configurations.ClientConfig"),首先如果这个包路径写得不对,Spring根本扫不到ClientConfig;就算路径对,每次初始化Controller都新建一个上下文不仅浪费资源,而且这个新上下文和你原本的Spring环境完全脱节,不是最佳实践。 - 另外,
LoginController是由FXMLLoader创建的,默认不属于Spring容器管理,你手动去拉上下文的方式本身就绕开了Spring的依赖注入机制,很容易出问题。
两种解决方案,推荐第一种
方案一:让Spring接管JavaFX Controller,直接注入Service(最规范)
这是Spring整合JavaFX的标准做法,让Spring来管理Controller的生命周期,直接把Service注入进去:
- 先修改
ClientConfig,给FXMLLoader设置Spring的控制器工厂,这样Controller会由Spring创建:
@Configuration public class ClientConfig { @Bean public Stage primaryStage() { return new Stage(); } @Bean RmiProxyFactoryBean service() { RmiProxyFactoryBean rmiProxyFactory = new RmiProxyFactoryBean(); rmiProxyFactory.setServiceUrl("rmi://127.0.0.1:1099/Service"); rmiProxyFactory.setServiceInterface(Service.class); return rmiProxyFactory; } @Bean public Scene loginScene(ApplicationContext context) { final FXMLLoader loader = new FXMLLoader(getClass().getResource("/FXMLs/LoginFXML.fxml")); // 让Spring负责创建Controller loader.setControllerFactory(context::getBean); try { return new Scene(loader.load()); } catch (IOException e) { // 把异常抛出来,方便调试,不要只打印堆栈 throw new RuntimeException("加载LoginFXML.fxml失败", e); } } // 把LoginController注册成Spring Bean @Bean public LoginController loginController() { return new LoginController(); } }
- 然后修改
LoginController,去掉手动创建上下文的代码,直接让Spring注入Service:
@Data @Component // 或者用上面@Bean的方式注册,选一个就行 public class LoginController { // 直接注入Service,不用自己去拿 @Autowired private Service service; @FXML private TextField textfield_username; @FXML private PasswordField textfield_password; @FXML public void initialize(){ // 这里啥都不用干了,Spring已经把service注入好了 } // 后面的事件方法保持不变 @FXML private void textUsernameKeypressed(KeyEvent keyEvent) { onEnterLogin(keyEvent); } @FXML private void textPasswordKeypressed(KeyEvent keyEvent) { onEnterLogin(keyEvent); } @FXML private void buttonKeypressed(KeyEvent keyEvent) { onEnterLogin(keyEvent); } private void onEnterLogin(KeyEvent keyEvent){ if (keyEvent.getCode().equals(KeyCode.ENTER)) login(); } @FXML private void loginButtonPressed(ActionEvent actionEvent) { login(); } private void login(){ try { service.login(textfield_username.getText(), textfield_password.getText()); Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setContentText("Login succesfull"); alert.show(); } catch (ServiceException e) { Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setContentText("Login failed: " + e.getMessage()); alert.show(); } } }
- 最后检查你的
LoginFXML.fxml,确保fx:controller指向正确的Controller类:
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.control.*?> <?import javafx.scene.layout.*?> <AnchorPane fx:controller="你的包路径.LoginController" xmlns:fx="http://javafx.com/fxml"> <!-- 你的控件定义,比如: --> <TextField fx:id="textfield_username" layoutX="50" layoutY="50"/> <PasswordField fx:id="textfield_password" layoutX="50" layoutY="100"/> <Button fx:id="loginButton" text="Login" layoutX="50" layoutY="150" onAction="#loginButtonPressed"/> </AnchorPane>
方案二:临时修复手动获取上下文的方式(不推荐)
如果你暂时不想改架构,只想快速解决问题,那可以修正上下文的创建方式:
@FXML public void initialize(){ // 直接传入ClientConfig.class,避免包路径写错的问题 factory = new AnnotationConfigApplicationContext(ClientConfig.class); service = (Service) factory.getBean("service"); }
注意:这种方式每次初始化Controller都会新建一个Spring上下文,会创建多余的RMI连接,非常不推荐,只适合临时调试用。
额外要检查的点
- 确保
Service接口是客户端和服务端共用的(比如放在Common模块),否则会出现类加载不兼容的问题,也可能导致获取Bean失败。 - 先确认服务端的RMI注册表已经启动,1099端口正常监听,
Service已经成功导出,可以用rmiregistry命令或者jconsole工具检查服务是否存在。
内容的提问来源于stack exchange,提问作者Vlad Ilie
相关产品推荐
相关产品推荐

