JavaFx自定义控件和springdi

JavaFx自定义控件和springdi,java,spring,javafx,dependency-injection,Java,Spring,Javafx,Dependency Injection,我正在尝试将自定义控件添加到我在此处找到的示例中: (代码:) 当然,我的目标是能够在控制器中自动连接业务服务: @Service public class TestService { public void test() { System.out.println("test"); } } 我可以通过非自定义控件中的构造函数注入来实现这一点,如下所示: @Component public class SimpleUiController { pri

我正在尝试将自定义控件添加到我在此处找到的示例中: (代码:)

当然,我的目标是能够在控制器中自动连接业务服务:

@Service
public class TestService {
  public void test() {
    System.out.println("test");
  }
}
我可以通过非自定义控件中的构造函数注入来实现这一点,如下所示:

@Component
public class SimpleUiController {

    private final HostServices hostServices;

    @FXML
    public Label label;

    @FXML
    public Button button;

    private TestService testService;

    public SimpleUiController(HostServices hostServices, TestService testService) {
        Assert.notNull(testService);
        this.hostServices = hostServices;
        this.testService = testService;
    }

    @FXML
    public void initialize () {
        this.button.setOnAction(actionEvent -> this.testService.test());
    }
}

<!--ui.fxml-->
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.example.javafx.SimpleUiController">
      <Button fx:id="button" text="Button" />
      <Label fx:id="label" text="Label" />

      <CustomControl text="100"/>
</VBox>
接下来我想在fxml中添加一个自定义控件,并附带一个控制器。 因此,我天真地复制了这里的代码片段:

现在我可以像这样使用自定义控件:

@Component
public class SimpleUiController {

    private final HostServices hostServices;

    @FXML
    public Label label;

    @FXML
    public Button button;

    private TestService testService;

    public SimpleUiController(HostServices hostServices, TestService testService) {
        Assert.notNull(testService);
        this.hostServices = hostServices;
        this.testService = testService;
    }

    @FXML
    public void initialize () {
        this.button.setOnAction(actionEvent -> this.testService.test());
    }
}

<!--ui.fxml-->
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
      fx:controller="com.example.javafx.SimpleUiController">
      <Button fx:id="button" text="Button" />
      <Label fx:id="label" text="Label" />

      <CustomControl text="100"/>
</VBox>

但是当我添加自定义控件时,我得到了以下异常

Exception in Application start method
Caused by: java.lang.NoSuchMethodException: com.example.javafx.CustomControl.<init>()
应用程序启动方法中出现异常 原因:java.lang.NoSuchMethodException:com.example.javafx.CustomControl。()
我对JavaFx相当陌生,我很高兴找到一些关于与Spring集成的文章。但我没有找到任何关于自定义控件和spring的参考资料。几乎感觉我是唯一一个试图实现这一点的人,我是不是遗漏了什么?

根据James_D的建议,通过添加自定义生成器工厂解决了问题

@Component
public class BeanBuilderFactory implements BuilderFactory {

  @Autowired
  private ConfigurableApplicationContext context;

  @Override
  public Builder<?> getBuilder(Class<?> type) {
    try {
      Object bean = this.context.getBean(type);
      if (bean.getClass().isAssignableFrom(type)) {

        if (bean instanceof CustomControl) {
          return new CustomControlBuilder((CustomControl) bean);
        }
      }
      JavaFXBuilderFactory javaFXBuilderFactory = new JavaFXBuilderFactory();
      return javaFXBuilderFactory.getBuilder(bean.getClass());
    } catch (BeansException e) {
      return null;
    }
  }
@组件
公共类BeanBuilderFactory实现了BuilderFactory{
@自动连线
私有配置应用程序上下文上下文;
@凌驾
公共生成器getBuilder(类类型){
试一试{
objectbean=this.context.getBean(类型);
if(bean.getClass().isAssignableFrom(类型)){
if(CustomControl的bean实例){
返回新的CustomControlBuilder((CustomControl)bean);
}
}
JavaFXBuilderFactory JavaFXBuilderFactory=新的JavaFXBuilderFactory();
返回javaFXBuilderFactory.getBuilder(bean.getClass());
}捕获(BeansException e){
返回null;
}
}
我必须为我的自定义组件创建一个构建器。我想也有机会使用反射,但我没有做到这一点

public class CustomControlBuilder implements Builder<CustomControl> {

  private CustomControl customControl;

  public CustomControlBuilder(CustomControl customControl) {
    this.customControl = customControl;
  }

  public String getText() {
    return customControl.getText();
  }

  public void setText(String value) {
    customControl.setText(value);
  }

  @Override
  public CustomControl build() {
    return customControl;
  }
}
公共类CustomControlBuilder实现了{
私人海关控制;
公共CustomControlBuilder(CustomControl CustomControl){
this.customControl=customControl;
}
公共字符串getText(){
返回customControl.getText();
}
公共void setText(字符串值){
customControl.setText(值);
}
@凌驾
公共自定义控件生成(){
返回控制;
}
}
下面是我添加了builder工厂的原始示例中的stageListener:

@Component
public class StageListener implements ApplicationListener<JavafxApplication.StageReadyEvent> {

    private final String applicationTitle;
    private final Resource fxml;
    private final ApplicationContext applicationContext;
    private final BeanBuilderFactory beanBuilderFactory;

    public StageListener(@Value("${spring.application.ui.title}") String applicationTitle,
                         @Value("classpath:/ui.fxml") Resource fxml, ApplicationContext applicationContext,
                         BeanBuilderFactory aBeanBuilderFactory) {
        this.applicationTitle = applicationTitle;
        this.fxml = fxml;
        this.applicationContext = applicationContext;
        this.beanBuilderFactory = aBeanBuilderFactory;
    }

    @Override
    public void onApplicationEvent(JavafxApplication.StageReadyEvent stageReadyEvent) {
        try {
            Stage stage = stageReadyEvent.getStage();
            URL url = fxml.getURL();
            FXMLLoader fxmlLoader = new FXMLLoader(url);
            fxmlLoader.setControllerFactory(applicationContext::getBean);
            fxmlLoader.setBuilderFactory(beanBuilderFactory);
            Parent root = fxmlLoader.load();
            Scene scene = new Scene(root, 600, 600);
            stage.setScene(scene);
            stage.setTitle(this.applicationTitle);
            stage.show();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
@组件
公共类StageListener实现ApplicationListener{
私有最终字符串applicationTitle;
私有最终资源fxml;
专用最终应用程序上下文应用程序上下文;
私人最终BeanBuilderFactory BeanBuilderFactory;
公共StageListener(@Value(${spring.application.ui.title}”)字符串applicationTitle,
@值(“classpath:/ui.fxml”)资源fxml,ApplicationContext ApplicationContext,
BeanBuilderFactory(aBeanBuilderFactory){
this.applicationTitle=applicationTitle;
this.fxml=fxml;
this.applicationContext=applicationContext;
this.beanBuilderFactory=aBeanBuilderFactory;
}
@凌驾
ApplicationEvent(JavafxApplication.StageReadyEvent StageReadyEvent)上的公共无效{
试一试{
Stage Stage=stageReadyEvent.getStage();
URL URL=fxml.getURL();
FXMLLoader FXMLLoader=新的FXMLLoader(url);
setControllerFactory(applicationContext::getBean);
fxmlLoader.setBuilderFactory(beanBuilderFactory);
父根=fxmloader.load();
场景=新场景(root,600600);
舞台场景;
stage.setTitle(this.applicationTitle);
stage.show();
}捕获(IOE异常){
抛出新的运行时异常(e);
}
}
}

在FXML文件中包含这样一个自定义控件时,会发生的情况是
FXMLLoader
通过反射、调用(默认情况下)实例化控件无参数构造函数。您需要安排
fxmloader
从spring应用程序上下文检索控件实例。我认为这样做的方法是在
fxmloader
上使用自定义。我认为您的如果无法从Spring获取对象,答案是返回null。(因此我怀疑它在FXML中的
按钮和
标签上失败,而不是在自定义控件上失败。在自定义生成器工厂中创建
JavaFxBuilderFactory
,并返回
getBuilder()的结果)
如果没有符合条件的bean,则会出现这种情况。我还遇到以下错误:属性“text”不存在或是只读的。因为我正在创建的生成器不会从我的bean中公开我的getter和setter。您是否也有一个优雅的解决方案@James_D;o)我认为这只是将包打开到
javafx.fxml
以便它可以使用反射,但我不确定。