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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章