简体   繁体   English

JavaFX和Spring - bean不会自动装配

[英]JavaFX and Spring - beans doesn't Autowire

I have problems with combining JavaFX and Spring. 我在组合JavaFX和Spring方面遇到了问题。 I have simple JavaFX application, which works fine. 我有简单的JavaFX应用程序,它工作正常。 Now I am trying to add some Spring to it. 现在我想尝试添加一些Spring。 I followed JavaFX 2 with Spring Tutorial . 在Spring Tutorial中使用了JavaFX 2 My code: 我的代码:

src/main
|
|_java/mycompany/imageviewer
|   |
|   |_Startup.java
|   |_controller/ImageViewController.java
|   |_dataprovider
|       |impl/DataProviderImpl.java
|   |_config
|       |_SpringFxmlLoader.java
|       |_SpringApplicationConfig.java
|_resources/mycompany/view/ImageViewer.fxml

Startup.java is file with main: Startup.java是带有main的文件:

public class Startup extends Application {

    private static final SpringFxmlLoader loader = new SpringFxmlLoader();

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

    @Override
    public void start(Stage primaryStage) throws Exception {
        ...    
        Parent root = (Parent) loader.load("/mycompany/imageviewer/view/ImageViewer.fxml","mycompany/imageviewer/bundle/bundle");
        Scene scene = new Scene(root);
        ...css etc...
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

ImageviewerController.java : ImageviewerController.java

@Controller
public class ImageViewerController {
    private static final Logger LOG = Logger.getLogger(ImageViewerController.class);
    @FXML
    ...    
    @Autowired
    private DataProvider dataProvider;

    public ImageViewerController() {
        LOG.debug("Controller initialized. DataProvider is null: "+(dataProvider==null));
    }

DataProviderImpl.java : DataProviderImpl.java

@Service("dataProvider")
public class DataProviderImpl implements DataProvider {
    private static final Logger LOG = Logger.getLogger(DataProviderImpl.class);

    public DataProviderImpl() {
        LOG.debug("DataProviderImpl initialized.");
    }
    ...methods...
}

My SpringFxmlLoader looks similar to this in tutorial: 我的SpringFxmlLoader在教程中看起来与此类似:

public class SpringFxmlLoader {

    private static final ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringApplicationConfig.class);

    public Object load(String url, String resources) {
        FXMLLoader loader = new FXMLLoader();
        loader.setControllerFactory(clazz -> applicationContext.getBean(clazz));
        try {
            return loader.load(getClass().getResource(url), ResourceBundle.getBundle(resources));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

My SpringApplicationConfig: 我的SpringApplicationConfig:

@Configuration
@ComponentScan(basePackages = {"mycompany.imageviewer.controller", "mycompany.imageviewer.dataprovider.impl" })
public class SpringApplicationConfig {
    private static final Logger LOG = Logger.getLogger(SpringApplicationConfig.class);

    @Bean
    public DataProvider dataProvider() {
        LOG.debug("Initializing dataProvider via SpringApplicationConfig");
        return new DataProviderImpl();
    }

    @Bean
    public ImageViewerController imageViewerController() {
        LOG.debug("Initializing ImageViewerController via SpringApplicationConfig");
        return new ImageViewerController();
    }
}

In my app I have ImageViewer.fxml with binded controller: 在我的应用程序中,我有带有绑定控制器的ImageViewer.fxml

<AnchorPane fx:controller="mycompany.imageviewer.controller.ImageViewerController" xmlns="http://javafx.com/javafx/8.0.51" xmlns:fx="http://javafx.com/fxml/1" >

When I run program I get logs: 当我运行程序时,我得到日志:

DEBUG [main] mycompany.imageviewer.controller.ImageViewerController:74 - Controller initialized. DataProviderImpl is null: true
DEBUG [main] mycompany.imageviewer.dataprovider.impl.DataProviderImpl:22 - DataProviderImpl initialized.
DEBUG [JavaFX Application Thread] mycompany.imageviewer.controller.ImageViewerController:74 - Controller initialized. DataProviderImpl is null: true

Which shows, that my controller is initialized twice, and the dataProvider is not properly binded. 这表明,我的控制器初始化了两次,并且dataProvider没有正确绑定。 What made me confused, was that when I accidentaly wrote wrong basePackages in ComponentScan in this way with wrong packages: 令我困惑的是,当我意外地在ComponentScan中用错误的包写错了basePackages

@ComponentScan(basePackages = {"mycompany.imageviewer.dataprovider.controller", "mycompany.imageviewer.dataprovider.dataprovider.impl" })

Beans initialize methods in SpringApplicationConfig.java run and I get logs from them: Bean在SpringApplicationConfig.java运行中初始化方法,我从中获取日志:

2015-09-06 16:52:29,420 DEBUG [main] com.capgemini.starterkit.imageviewer.config.SpringApplicationConfig:19 - Initializing dataProvider via SpringApplicationConfig
2015-09-06 16:52:29,431 DEBUG [main] com.capgemini.starterkit.imageviewer.dataprovider.impl.DataProviderImpl:22 - DataProviderImpl initialized.
DEBUG [main] mycompany.imageviewer.config.SpringApplicationConfig:25 - Initializing ImageViewerController via SpringApplicationConfig
DEBUG [main] mycompany.imageviewer.controller.ImageViewerController:74 - Controller initialized. DataProviderImpl is null: true
DEBUG [JavaFX Application Thread] mycompany.imageviewer.controller.ImageViewerController:74 - Controller initialized. DataProviderImpl is null: true

When I run basePackages = "com.capgemini.starterkit.imageviewer" effect is the same like in the first case. 当我运行basePackages = "com.capgemini.starterkit.imageviewer"效果与第一种情况相同。 I am new to spring and probably I made some simple mistakes, but I am not able to find them, so if anybody can help me configure spring that would be great.:-) 我是春天的新手,可能我犯了一些简单的错误,但是我找不到它们,所以如果有人能帮我配置那个很棒的弹簧。:-)

The FXMLLoader.load(URL, ResourceBundle) method you are calling is a static method - so it actually pays no attention to the FXMLLoader instance you instantiated, and consequently ignores the controllerFactory which references your Spring bean factory. 您调用的FXMLLoader.load(URL, ResourceBundle)方法是一个static方法 - 因此它实际上不关注您实例化的FXMLLoader实例,因此忽略了引用您的Spring bean工厂的controllerFactory

Rewrite your SpringFXMLLoader class as follows: 重写SpringFXMLLoader类,如下所示:

public class SpringFxmlLoader {

    private static final ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringApplicationConfig.class);

    public Object load(String url, String resources) {
        FXMLLoader loader = new FXMLLoader();
        loader.setControllerFactory(clazz -> applicationContext.getBean(clazz));
        loader.setLocation(getClass().getResource(url));
        loader.setResources(ResourceBundle.getBundle(resources));
        try {
            return loader.load();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}

This uses the instance method loader.load() which will use your controller factory: ie it will use Spring to instantiate the controller. 这使用实例方法loader.load() ,它将使用你的控制器工厂:即它将使用Spring来实例化控制器。

The reason you see the controller loaded twice is that by default the bean factory gives the controller singleton scope and makes it eagerly created, so as soon as you create the bean factory ( applicationContext ) it creates a controller. 您看到控制器加载两次的原因是,默认情况下,bean工厂为控制器提供单例作用域并使其热切创建,因此只要您创建bean工厂( applicationContext ),它就会创建一个控制器。 That controller will have its dataProvider initialized (but only after the constructor has completed, of course). 该控制器将初始化其dataProvider (当然,仅构造函数完成后)。 Then the call to the static FXMLLoader.load(...) method creates a second controller by the usual mechanism (ie by calling its no-arg constructor). 然后调用静态FXMLLoader.load(...)方法通过常规机制创建第二个控制器(即通过调用其无参数构造函数)。 That instance will not have its dataProvider initialized at any time. 该实例不会在任何时候初始化其dataProvider

As an aside, you probably don't want controllers to be singletons. 顺便说一句,你可能不希望控制器成为单身人士。 If you were to load your FXML file twice, to get two instances of the Parent , you would likely need each instance to have its own controller, otherwise strange behavior would ensue. 如果你要加载你的FXML文件两次,得到两个Parent实例,你可能需要每个实例都有自己的控制器,否则会出现奇怪的行为。 I would recommend making the controller a prototype (which means the bean factory will create a new instance every time one is requested, instead of reusing a single instance). 我建议将控制器作为原型(这意味着bean工厂将在每次请求时创建一个新实例,而不是重用单个实例)。 You can do this with the following in your config class: 您可以在config类中执行以下操作:

@Configuration
@ComponentScan(basePackages = {"mycompany.imageviewer.controller", "mycompany.imageviewer.dataprovider.impl" })
public class SpringApplicationConfig {
    private static final Logger LOG = Logger.getLogger(SpringApplicationConfig.class);

    @Bean
    public DataProvider dataProvider() {
        LOG.debug("Initializing dataProvider via SpringApplicationConfig");
        return new DataProviderImpl();
    }

    @Bean
    @Scope("prototype")
    public ImageViewerController imageViewerController() {
        LOG.debug("Initializing ImageViewerController via SpringApplicationConfig");
        return new ImageViewerController();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM