Better Controller Injection

This entry is part 2 of 15 in the series Building JEEapplications in JavaFX 2.0

There are two things about our controller injection inour last post that I’m not entirely happy with. (在上一篇文章中有兩件關於控制注入的事情讓我不是很滿意)The first is that the SampleApp is the one responsiblefor loading the FXML file and wiring it up to the controller.(第一個不滿意的是SampleApp是負責加載FXML文件和把FXML文件綁定到控制的) We have a factoryclass for our UI (SampleAppFactory) so ideally all the work of creating andwiring up all the GUI elements should be done in here.(我們給UI提供了工廠類SampleAppFactory,所以典型的做法是把所有創建和綁定界面元素的工作都應該在哪裏做)

The second is that Richard’s post came from his earlierexploration of future directions for FXML and the discussions around that. (第二個不滿意的地方是Richard關於FXML將來方向的早期探索和討論)Some of these features are not available yet,(一些特徵還沒有發佈) so Richard has foundsome creative ways to demo the concepts using scripting and the namespace.(所以Richard已經發現了一些創造性的方法用腳本和命名空間去演示這些概念) We probably don’t want to be using this style of coding for commercialapps so until the next release of FXML we need to work with what we’ve got (我們可能不想直到下一個我們想要的FXML版本發佈之後纔在商業軟件中使用這種風格的代碼)– i.e. we need to have an explicitly namedcontroller class in the FXML and let the loader instantiate our class and doour bindings.(比如我們需要在FXML有一個明確名字的控制器類,讓這個加載器實例化我們的類做好綁定) This has some drawbacks but using Spring (or Guice’s)annotation based configuration, we can make it work well enough.(使用Spring或者Guice的註解配置雖有缺點,但是我們已經讓它工作得更好了)

For those that want to skip the details and just see thecode: http://code.google.com/p/jfxee/source/browse/trunk/jfxspring2

So let’s first revert to the standard way of loadingcontrollers in FXML and ditch the magical namespace and scripting. (讓我們回顧一下在FXML中加載控制器的經典方法和探索一下神奇命名空間和腳本)This standard way is well documented in the official FXML (這個標準的方法在官方的FXML文檔中寫得很清楚)guide: http://download.oracle.com/javafx/2.0/api/javafx/fxml/doc-files/introduction_to_fxml.html

Our controller is now going to be defined and created inthe FXML file(我們的控制器現在就要在FXML文件中定義和創建了), but using Spring’s (or Guice’s) annotation based injection we can stillinject all the dependencies (so long as we use field injection and notconstructor injection)(但是使用Spring或者Guice的註解注入,我們仍然要注入所有的依賴(只要我們用的是屬性注入而不是構造函數注入)). One challenge though will be that both our controllerand our view need to be available through the factory.(一個挑戰性的想法是我們的控制器和我們的視圖都要通過工廠創建) The controller needs to be  exposed in order to get the Person beaninjected into it, but our view needs to be exposed so that it can be added tothe scene.(爲了把Person對象注入控制器,控制器必須暴露出來,而爲了把視圖添加到scene中,我們的視圖必須暴露出來) The complication is that the FXML loader creates both ina single call, so we need to get creative.FXML加載器要在一個調用中創建這兩個對象是很複雜的,所以我們必須要有創造性的想法)

There are a lot of ways to solve this problem, but theone that works best both now and for future benefits is to give the controllera reference to its view.(解決這個問題有很多中方法,但是有一種現在能夠工作得很好對將來也是有益的方法就是給控制器視圖的引用) Anyone wanting the view, can then just access thecontroller from the factory and retrieve the view from it.(任何想要視圖的人都可以訪問訪問控制器從控制器中獲取視圖)

So if we revert our controller back to the moretraditional form, giving it access to its view is a simple case of binding theroot node of the FXML to a variable in the controller.(所以如果我們把我們的控制改成更傳統的方式,給視圖的訪問接口是一件很簡單的事情,只要把FXML的根節點綁定到控制器中的一個變量) To keep life simple we’re not going to bother includingthe Person name on the button for now(爲了使事情簡單一點我們將不會在按鈕上麻煩的包含人名) – we’ll add this back in later, it just confuses thingsat this stage.(我們等一下添加按鈕,在這個階段它會讓事情變得迷惑) Here’s how it looks:(現在它是這樣子的)

SampleController.java

importjavafx.event.ActionEvent;
importjavafx.fxml.FXML;
importjavafx.scene.Node;
importorg.springframework.beans.factory.annotation.Autowired;
 
publicclassSampleController
{
    @FXMLprivateNode view;
    @AutowiredprivatePerson person;
 
    publicNode getView()
    {
        returnview;
    }
 
    publicPerson getPerson()
    {
        returnperson;
    }
 
    publicvoidprint(ActionEvent event)
    {
        System.out.println("Well done, "+ person.getFirstName() + "!");
    }
}


sample.fxml

<?xml version="1.0"encoding="UTF-8"?>
 
<?language javascript?>
<?importjavafx.scene.control.*?>
<?importjavafx.scene.layout.*?>
 
<StackPane fx:id="view"
           fx:controller="com.zenjava.jfxspring.SampleController"
           xmlns:fx="http://javafx.com/fxml">
    <children>
        <Button text="Click Me"fx:id="printBtn"onAction="#print"/>
    </children>
</StackPane>



Great, we now have a nice, simple, traditional controller.
(好的,現在我們有了好的簡單的傳統的控制器)The whole point of this however was to get dependency injection working. (現在全部的焦點是讓依賴注入工作)Haven’t we lost this now that the controller is being created by theFXMLLoader? (讓FXML加載器加載控制器,我們是不是在這裏迷惑了?)Not quite, luckily with Spring’s annotation based configuration we arefree to create the controller anyway we want, the injected properties are setonly when we return the controller from a factory method marked with @Bean. (不完全是這樣子,幸運的是使用spring註解配置我們可以自由地按照我們的想要的去創建控制器,注入屬性只有在我們從工廠標註了@Bean方法返回控制器的時候纔會被設置)Let’s update our factory then to use the new controller.(讓我們使用新的控制器去更新工廠) At the same time we will also remove the FXML loading from the SampleAppclass and move it inside the factory – this was the second problem we wanted tosolve.(同時我們也要從SampleApp中移除FXML加載器把它一道工廠中,這個我們想要去解決的問題) 

Here’s how our factory now looks:

importjavafx.fxml.FXMLLoader;
importorg.springframework.context.annotation.Bean;
importorg.springframework.context.annotation.Configuration;
 
importjava.io.IOException;
importjava.io.InputStream;
 
@Configuration
publicclassSampleAppFactory
{
    @Bean
    publicPerson person()
    {
        returnnewPerson("Richard");
    }
 
    @Bean
    publicSampleController sampleController() throwsIOException
    {
        return(SampleController) loadController("/sample.fxml");
    }
 
    protectedObject loadController(String url) throwsIOException
    {
        InputStream fxmlStream = null;
        try
        {
            fxmlStream = getClass().getResourceAsStream(url);
            FXMLLoader loader = newFXMLLoader();
            loader.load(fxmlStream);
            returnloader.getController();
        }
        finally
        {
            if(fxmlStream != null)
            {
                fxmlStream.close();
            }
        }
    }
}


And here’s the start method for our SampleApp class. (這是我們的SampleApp類的start方法)We’ve been able toremove the FXMLLoading from here – the application has no knowledge of how theviews are loaded. (我們有能力從這裏移除FXMLLoading,這個應用完全不知道視圖是怎麼加載的)If we wanted to change the view to be a normal Java classinstead of FXML neither the controller, nor the application would need to beupdated. (如果我們想要改從FXML視圖改編成普通的java類視圖,控制和應用都不要做改變)Only the factory, which is responsible for creating the view, would needto be changed as you would expect.(只有負責創建視圖的工廠需要按照你想要的去改變)



 

public void start(Stage stage) throws Exception
{
    AnnotationConfigApplicationContext context
            = new AnnotationConfigApplicationContext(SampleAppFactory.class);
 
    SampleController sampleController = context.getBean(SampleController.class);
    Scene scene = new Scene((Parent) sampleController.getView(), 320, 240);
    scene.getStylesheets().add("fxmlapp.css");
    stage.setScene(scene);
    stage.setTitle("JFX2.0 Sprung");
    stage.show();
}

All in all, a somewhat cleaner setup.(總而言之,比較簡潔的應用建立起來了)

It’s worth noting that there are some definitelimitations and disadvantages to using the traditional controller bindingapproach. (值得注意的是使用傳統的控制器綁定方法有一些明確的顯示和缺點)All of these have been raised with the JFX team (in JIRA)and several are being actively explored for future releases.(這些問題已經提交給了JFX團隊了,在開發新版本中已經在積極探索如何避免這些問題) In my opinion, although these drawbacks are annoying, at this stage theyare worth living with since by using the official controller option we don’thave to use scripting or namespaces, so our app will be easier to maintain andbe better supported by RAD tools and will be easier to get help and find doccoon.(在我看來,儘管這些缺點很令人討厭,但是眼下它們還是值得忍受的,因爲通過使用官方控制器選項我們不必不得不使用腳本和命令空間,所以我們的程序是容易管理和使用RAD工具支持,也是容易獲得幫助和找到

For the record however the limitations to be aware of arethese:(爲了記錄備案,需要注意的限制如下)

·                      The Controller classis specified in two places – the FXML and then cast-to in the factory. (控制器類在兩個地方指定,FXML和在工廠類進行轉換)If you forget toupdate your FXML (very easy to do) then you will get a class cast exception inthe factory.(如果你忘記更新你的FXML(經常容易忘記)你將會在工廠類得到轉換異常) Ideally we would not have to specify the controller inthe FXML at all.(理想的是我們不必在FXML指定控制器)

·                      You cannot useconstructor injection – since the FXML is instantiating the controller, it musthave an empty constructor.(你不能使用構造器注入,因爲FXML實例化控制器,它必須是一個空的構造器)

·                      Callback methods forbutton clicks must have an ActionEvent as part of the signature. This is easyto forget and will result in a runtime method. With the scripting option youdid not have this restriction (the tradeoff being that you could not get theMouseEvent for mouse-style callbacks).(按鈕點擊的回調方法必須有ActionEvent作爲方法的簽名的一部分,這個容易忘記和導致方法運行時出現問題。使用腳本你沒有這個限制,這個折中就是你沒有辦法爲鼠標回調獲得MouseEvent

·                      You cannot use thesame FXML definition with different instances of a controller, i.e. you cannotattach sample.fxml to a controller other than SampleController withoutduplicating the FXML file.(你不能使用相同的FXML定義,即使這些FXML使用不能的控制器實例,例如,你不能不用複製FXML文件就把sample.fxml綁定到不同於SampleController的控制器)

·                      You are limited to asingle controller per FXML file, so you couldn’t have sample.fxml triggercallbacks in both SampleController and another controller.(你被限制一個FXML文件只能有一個控制器,所以你不能同時在SampleController和其他的控制器中有sample.fxml觸發回調)

·                      You cannot share thesame controller across multiple views as the loader will create a newcontroller for each FXML file. You couldn’t reuse the same SampleControllerinstance used by sample.fxml with another fxml file, there will be twoinstances of SampleController created.(你不能在多個視圖中共享相同的控制器,因爲加載器爲每一個FXML文件創建新的控制器。你不能重用相同的SampleController對象在sample.fxml和其他的fxml文件中,那是兩個不能SampleController對象)

·                      FXML does not supportcontroller base classes. If your controller extends a base class and that baseclass has an @FXML annotated field on it, it will not get picked up by theFXMLLoader. You must define all @FXML attributes in the actual controller classitself.fxml不支持控制器基礎類,如果你的控制器繼承一個基類,那個基類有一個@FXML的註釋屬性,這個屬性將不會被FXMLLoader加載,你必須定義所有的@FXML屬性在目前的控制器類中)

There are probably a few other minor limitations but they are the big onesthat I can think of.(還有其他的次要限制,但是它們是我想到的最大的限制)

In the next post we're going to look at what happens when you have acouple of controllers and ways to share information and navigate between them.(在下一篇文章中我們將要看看在你有一對控制器和在它們之間分享信息和導航時會發生什麼事情)

Posted in Uncategorized.

發佈了17 篇原創文章 · 獲贊 11 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章