Search code examples
javahibernatejavafx-8guice

using guice with javafx and hibernate


I've got a javafx controller that looks like this

public class DictionaryFxController implements Initializable {

    @Inject
    private QueryTemplate queryTemplate;

    @SuppressWarnings("unchecked")
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        ....
        queryTemplate.insert(langs);
        ....
    }
}

My application class looks like this

public class Main extends Application {

    public static void main(String[] args) throws Exception {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        Injector injector = Guice.createInjector(new ApplicationModule());
        Scene scene = null;
        FXMLLoader loader = new FXMLLoader();
        try {
            Parent root = loader.load(getClass().getResourceAsStream("/dictionary.fxml"));
            loader.setControllerFactory(injector::getInstance);
            scene = new Scene(root);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }

        primaryStage.setTitle("Dictionary");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

}

My class that provides Hibernate session looks like this

@Singleton
public class HibernateController {
    private SessionFactory sessionFactory;

    public HibernateController() {
        sessionFactory = new Configuration()
                .configure()
                .buildSessionFactory();
    }

    public Session openSession() {
        return sessionFactory.openSession();
    }

    @PreDestroy
    private void close() {
        sessionFactory.close();
        StandardServiceRegistryBuilder.destroy(sessionFactory.getSessionFactoryOptions().getServiceRegistry());
    }
}

I wire this class into template that I use to peform queries

public class QueryTemplate {
    private HibernateController hibernateController;

    @Inject
    public QueryTemplate(HibernateController controller) {
        this.hibernateController = controller;
    }

    public <T> void insert(List<T> objects) {
        try (Session session = hibernateController.openSession()) {
            Transaction tx = session.beginTransaction();

            // generalize
            objects.forEach(session::save);

            tx.commit();
        }
    }
}

I bound this class in the module

public class ApplicationModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(QueryTemplate.class);
    }
}

But when I start the application, it fails on the line

   queryTemplate.insert(langs);

because queryTemplate is null. What is the problem?


Solution

  • You are setting the controller factory after loading the FXML; the controller will already have been created at that point. Since the FXMLLoader will use the default mechanism for creating the controller - i.e. simply calling the controller's default constructor - the controller is not managed by Guice and won't have its injected fields initialized.

    You can fix the problem simply by reversing the order of the lines with loader.setControllerFactory(...) and loader.load(...).

    As an aside, it is better to set the location of an FXMLLoader than to use the load(InputStream) method. If you use the method taking a Stream, the location is not set, and location resolution will not work properly in your FXML file.

    So replace

        FXMLLoader loader = new FXMLLoader();
        try {
            Parent root = loader.load(getClass().getResourceAsStream("/dictionary.fxml"));
            loader.setControllerFactory(injector::getInstance);
            scene = new Scene(root);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    

    with

        FXMLLoader loader = new FXMLLoader();
        loader.setLocation(getClass().getResource("/dictionary.fxml"));
        loader.setControllerFactory(injector::getInstance);
        try {
            Parent root = loader.load();
            scene = new Scene(root);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }
    

    You can combine the first two lines into the single line

        FXMLLoader loader = new FXMLLoader(getClass().getResource("/dictionary.fxml"));
    

    if you prefer.