2015-09-06 56 views
1

我在将JavaFX和Spring结合时遇到问题。我有简单的JavaFX应用程序,它工作正常。现在我正在尝试添加一些Spring。我跟着JavaFX 2 with Spring Tutorial。我的代码:JavaFX和Spring - bean不Autofire

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是文件与主:

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

@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

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

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

我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; 
    } 
} 

我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(); 
    } 
} 

在我的应用我有ImageViewer.fxml与绑定控制器:

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

当我运行程序,我得到的日志:

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 

其中显示我的控制器已初始化两次,并且dataProvider未正确绑定。是什么让我困惑,是我在ComponentScan以这种方式与错包不期而遇写错basePackages

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

豆初始化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 

当我运行basePackages = "com.capgemini.starterkit.imageviewer"的效果与第一种情况相同。 我是新来的春天,可能我犯了一些简单的错误,但我无法找到它们,所以如果有人可以帮我配置春天,那将是很棒的。:-)

回答

3

你调用的方法是FXMLLoader.load(URL, ResourceBundle)一个static方法 - 所以它实际上不关注你实例化的FXMLLoader实例,因此忽略引用你的Spring bean工厂的controllerFactory

重写你的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; 
    } 
} 

它使用实例方法loader.load()将使用您的控制器工厂:即它会使用Spring来实例化控制器。

您看到控制器加载两次的原因是,默认情况下,bean工厂给出了控制器的单例作用域,并且使其成为热切创建的对象,因此只要创建bean工厂(applicationContext),它就会创建一个控制器。该控制器将初始化其dataProvider(但当然,构造函数已完成之后只有)。然后,对静态方法FXMLLoader.load(...)的调用通过常规机制(即通过调用其无参数构造函数)创建第二个控制器。该实例不会在任何时候初始化它的dataProvider

另外,你可能不希望控制器是单身人士。如果要加载FXML文件两次,要获得Parent的两个实例,则可能需要每个实例都有自己的控制器,否则会出现奇怪的行为。我建议使控制器成为一个原型(这意味着bean工厂将在每次请求时创建一个新实例,而不是重复使用一个实例)。你可以在你的配置类中使用以下内容:

@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(); 
    } 
} 
+0

非常感谢你,你的回答解决了我的问题。但我不得不承认,我不理解你的最后一段,但我明白,我不应该使用我的'ImageViewerController'作为bean。你能解释一下这个问题有什么好的解决方法吗? –

+0

我不是说你不应该把它当作bean来使用,只是你不应该让它具有singleton范围。 Singleton作用域意味着每当请求时,bean工厂将返回相同的实例:您(几乎肯定)每次请求时都需要一个新实例。要做到这一点,只需将其作为“原型”范围即可:请参阅更新后的答案。 –