2015-09-06 53 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工廠將返回相同的實例:您(幾乎肯定)每次請求時都需要一個新實例。要做到這一點,只需將其作爲「原型」範圍即可:請參閱更新後的答案。 –