【问题标题】:How to autowire Spring beans into a FXML controller class via a Spring XML config file如何通过 Spring XML 配置文件将 Spring bean 自动装配到 FXML 控制器类中
【发布时间】:2015-12-23 17:16:41
【问题描述】:

我有一个 FXML 控制器,它有一些 Spring Bean 依赖项。由于我使用的是自定义 FXML 加载器,因此我找不到在加载控制器之前及时自动连接它们的方法:

@Bean
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "prototype")
public UserProfile attachDocController() throws IOException {
    return (UserProfile) loadController("/myproject/Forms/userProfile.fxml");
}

FXMLLoader loader = null;

protected Object loadController(String url) throws IOException {
    loader = new FXMLLoader(getClass().getResource(url));
    loader.load();
    return loader.getController();
}

使用这种方法,我只能通过 @Autowired 注释直接注入 bean 来自动装配它们:

public class UserProfile {

    @Autowired
    MyDependency myDependency;

这让我依赖于 Spring,并在以后给我留下代码可维护性问题。如何将 Spring XML 文件配置中的依赖关系自动装配到 FXML 控制器类中?比如:

<bean id="UserProfile" class="myproject.controllerinjection.UserProfile" scope="prototype">
    <aop:scoped-proxy proxy-target-class="true"/>

    <property name="myDependency" ref="myDependency" />
</bean>

<bean id="myDependency" class="myproject.controllerinjection.MyDependency" scope="prototype">
    <aop:scoped-proxy proxy-target-class="true"/>
</bean>

随着项目变得越来越大,考虑到项目的长期可维护性,这似乎是一条更好的路线。

更新:

我不太习惯 Lambda 表达式。我进行了一些研究,但将@James_D 的建议整合如下:

protected Object loadBeanController(String url) throws IOException {
    loader = new FXMLLoader(getClass().getResource(url));
    ApplicationContext ctx = WakiliProject.getCtx();

    if (ctx != null) {
        System.out.println("Load Bean...............");
        loader.setControllerFactory(ctx::getBean);

    } else {
        System.out.println("No App.ctx...............");
    }

    return loader.getController();
}

每当我尝试调用MyDependency 的方法时都会给出一个空指针。 MyDependency myDependency 永远不会被注入 UserProfile

【问题讨论】:

  • 这对我来说没有多大意义。如果你丢弃loader.load() 的结果,那么控制器将附加到一个永远无法显示的 UI 根目录,所以它没有用。您需要以不同的顺序执行所有这些操作。
  • 我想要的是在 Spring XML 配置文件中有这个配置。这可能吗,怎么做?我如何做到这一点并不重要,只要将配置推送到 XML 配置文件即可。
  • 当然,解决问题可以让您以任何方式配置应用程序上下文。您想要将其移至 XML 的原因与误解有关,因此如果您进行修复,您可能不再需要/不想将其移至 XML。见答案。
  • 请看我上面@James_D的更新
  • 在你更新的代码中,你没有调用loader.load(),所以控制器永远不会被创建,loader.getController()返回null

标签: java xml spring-mvc javafx autowired


【解决方案1】:

当您调用FXMLLoader.load() 时,它会加载 FXML 文件。如果根元素中有fx:controller 属性,它会根据指定的类创建一个控制器(并将fx:id-attributed 元素注入该控制器实例等)。然后加载程序返回 FXML 文件的根。控制器本质上链接到该 FXML 根。

默认情况下,FXMLLoader 通过反射将控制器类映射到实例,调用 controllerClass.newInstance()(它有效地调用控制器类的无参数构造函数)。您可以通过在FXMLLoader 上指定controllerFactory 来配置它,覆盖默认行为。

controllerFactory 是一个将Class&lt;?&gt; 对象(由fx:controller 属性中指定的类名构造)映射到控制器实例的函数。如果你使用 Spring 来管理你的控制器实例,你只需要这个函数来询问 Spring 应用程序上下文(bean 工厂)来为你生成控制器实例。所以你基本上可以做fxmlLoader.setControllerFactory(applicationContext::getBean);。使用此设置,只需通过 FXMLLoader 加载 fxml 文件将导致 FXMLLoader 从应用程序上下文中请求控制器类。应用程序上下文可以通过 Spring 允许的任何方式进行配置。

所以你的配置看起来像

@Configuration
public class Config {
    @Bean
    @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "prototype")
    public UserProfile attachDocController() throws IOException {
        return new UserProfile();
    }
}

当然,你现在可以在配置类中注入依赖:

    @Bean
    @Scope(proxyMode = ScopedProxyMode.TARGET_CLASS, value = "prototype")
    public UserProfile attachDocController(MyDependency myDependency) throws IOException {
        return new UserProfile(myDependency);
    }

    @Bean
    public MyDependency createDependency() {
        return new MyDependencyImpl();
    }

然后在你的 UI 工作中你可以做到

FXMLLoader loader = new FXMLLoader(getClass().getResource("/myproject/Forms/userProfile.fxml"));
loader.setControllerFactory(applicationContext::getBean);
Parent root = loader.load();

// since everything can be initialized in the controller by D.I., you
// shouldn't need to access it, but if you do for some reason you can do

UserProfile controller = loader.getController();

applicationContext 是您的 Spring 应用程序上下文。无论应用程序上下文使用 XML 配置、基于注解的配置还是 Java 配置,这都有效。

更新

如果由于某种原因您不能使用 Java 8 或更高版本,则对与 Java 7 兼容的 setControllerFactory 的调用如下所示:

loader.setControllerFactory(new Callback<Class<?>, Object>() {
    @Override
    public Object call(Class<?> c) {
        return applicationContext.getBean(c);
    }
});

您需要将 applicationContext 作为字段或 final 局部变量才能在 Java 7 中工作。请注意,在撰写本文时,Oracle 尚未公开支持 Java 7。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2018-09-18
  • 2020-09-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-10-19
  • 2012-10-25
相关资源
最近更新 更多