有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java Spring注入变量为null

我正在尝试将SpringDI与JavaFx结合使用,我有一个MainController类,它作为Bean加载到AppConfig中,然后是另一个MenuController类,它将使用MainService。但注入的服务在调用时为null

问题

  • 它没有注入变量的原因是什么
  • 在setControllerFactory方法的一个示例中,它们返回了appContext。getBean(clazz),但我不知道如何访问上下文。我需要如何设置工厂,以及如何设置工厂
  • 我需要递归地连接bean吗

我的代码

应用程序类:

public class App extends Application {

    private static final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfiguration.class);

    @Override
    public void start(Stage primaryStage) throws Exception {
        MainController mainController = context.getBean(MainController.class);
        Scene scene = new Scene(mainController.getView());
        primaryStage.setScene(scene);
        recursiveWire(context, mainController.getView());
        primaryStage.show();
    }  

    public void recursiveWire(AnnotationConfigApplicationContext context, Object root) throws Exception {
        context.getAutowireCapableBeanFactory().autowireBean(root);
        context.getAutowireCapableBeanFactory().initializeBean(root, null);

        for (Field field : root.getClass().getDeclaredFields()) {
            if (field.isAnnotationPresent(FXML.class) && !Node.class.isAssignableFrom(field.getType())) {
                recursiveWire(context, field.get(root));
            }
        }
    }

    public static void main(String[] args) {
        launch(args);
    }    
}

AppConfiguration类:

在这里,我尝试设置ControllerFactory,正如我看到一些建议一样,但它并没有改变结果

@Configuration
public class AppConfiguration {

    @Bean
    @Scope("prototype")
    public MainService mainService() {
        return new InMemoryMainService();
    }

    @Bean
    @Scope("prototype")
    @DependsOn("mainService")
    public MainController mainController() throws IOException {
        return (MainController) loadController("/java/com/akos/fxml/Main.fxml");
    }

    @Bean
    @Scope("prototype")
    public MenuController menuController() throws IOException {
        return (MenuController) loadController("/java/com/akos/fxml/Menu.fxml");
    }

    protected Object loadController(String url) throws IOException {
        InputStream fxmlStream = null;
        try {
            fxmlStream = getClass().getResourceAsStream(url);
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(getClass().getResource(url));
            Node view = loader.load(fxmlStream);
            AbstractController  controller = loader.getController();
            loader.setControllerFactory(clazz -> controller);
            controller.setView(view);
            return controller;
        } finally {
            if (fxmlStream != null) {
                fxmlStream.close();
            }
        }
    }    
}

菜单控制器类:

这里是我需要访问mainService的地方,但它是空的

public class MenuController extends AbstractController implements Initializable {
   ...

    @Inject
    MainService mainService;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

        disableMenuElements();
        mainService.currentProgramProperty().addListener((observable, oldValue, newValue) -> {
        ...
        });
    }
}

共 (1) 个答案

  1. # 1 楼答案

    问题在于事情发生的顺序

    从Spring的角度来看,当您请求bean时,它是通过调用应用程序配置中的menuController()方法创建的,然后初始化@Inject注释的字段(通过反射),然后返回bean

    但是,menuController()方法通过加载fxml文件,然后从FXMLLoader中检索控制器来创建控制器。控制器中的initialize()方法由FXMLLoader调用,作为load()进程的一部分。显然,这发生在menuController()返回之前(因为它发生在loader.load()返回之前);因此initialize()在Spring有机会初始化注入的字段之前被调用

    最快的修复方法可能是为服务定义setter方法,并在初始化服务时简单地调用服务上的方法:

    public class MenuController extends AbstractController implements Initializable {
        // ...
    
        private MainService mainService;
    
        @Inject
        public void setMainService(MainService mainService) {
            this.mainService = mainService ;
            mainService.currentProgramProperty().addListener((observable, oldValue, newValue) -> {
               //  ...
            });
        }
    
        @Override
        public void initialize(URL location, ResourceBundle resources) {
    
            disableMenuElements();
        }
    }
    

    在使用Spring管理JavaFX应用程序时,我倾向于使用完全不同的方法。我不是让控制器访问视图,然后从控制器检索视图,而是告诉FXMLLoader使用Spring通过controllerFactory实例化控制器。然后,当您在FXMLLoader上调用load()时,它从Spring以bean的形式请求控制器,因此FXMLLoader接收到一个注入了其所有依赖项的bean。然后,当它在控制器上调用initialize()时,依赖项已经存在

    所以

    @Configuration
    public class AppConfiguration {
    
        @Bean
        // ??? surely a service should be singleton, not prototype, scope...
        @Scope("prototype")
        public MainService mainService() {
            return new InMemoryMainService();
        }
    
        @Bean
        @Scope("prototype")
        public MainController mainController() throws IOException {
            return new MainController();
        }
    
        @Bean
        @Scope("prototype")
        public MenuController menuController() throws IOException {
            return new MenuController();
        }
    
    
    }
    

    你的菜单控制器和你的一样:

    public class MenuController extends AbstractController implements Initializable {
        // ...
    
        @Inject
        MainService mainService;
    
        @Override
        public void initialize(URL location, ResourceBundle resources) {
    
            disableMenuElements();
            mainService.currentProgramProperty().addListener((observable, oldValue, newValue) -> {
                // ...
            });
        }
    }
    

    现在你可以做了

    public class App extends Application {
    
        private static final AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfiguration.class);
    
        @Override
        public void start(Stage primaryStage) throws Exception {
            // should fix name, you should not start your own pacakge names "java"
            FXMLLoader loader = new FXMLLoader(getClass().getResource("/java/com/akos/fxml/Main.fxml"));
            loader.setControllerFactory(context::getBean);
            Scene scene = new Scene(loader.load());
            primaryStage.setScene(scene);
            primaryStage.show();
        }  
    
        public static void main(String[] args) {
            launch(args);
        }    
    }
    

    请注意,您可以通过调用

    MainController mainController = loader.getController();
    

    之后,您已经调用了loader.load()。这提供了对FXMLLoader创建的控制器的引用;i、 e.Spring创建的一个(因为控制器工厂指示FXMLLoader使用Spring)。(不过,在我看来,您真的不需要对控制器的引用;控制器特别知道如何在视图和模型(服务)之间进行通信);如果要从外部更改UI,则应更新模型以进行更改,然后控制器将观察模型中的更改并更新视图。)

    我不完全清楚“递归连接”应该做什么。如果通过主fxml文件中的<fx:include>加载菜单,控制器工厂将传播到包含的fxml文件,因此MenuController也将从spring上下文中实例化,并根据需要注入服务。如果在其他地方加载它,只需在加载时设置控制器工厂,如上面的主fxml文件所示。所有这些都假设您的控制器是在fxml文件中用<fx:controller>指定的,我认为您的其他代码中肯定是这样