We try to refactoring a project with Guice. The idea is to bind all the Language interface to a concreate object like French or Polish.
We have a module for binding:
public class StandardModule extends AbstractModule {
@Override
protected void configure() {
bind(Language.class).to(Polish.class);
}
}
And a classe (AboutDialog.java) that use this injected object :
@Inject Language language;
public AboutDialog(JFrame parent) {
super(parent, "", true);
this.language=language;
this.setTitle(language.getLanguageInUse().getString("AboutDialog.title"));
this.parent = parent;
try {
jbInit();
} catch (Exception e) {
e.printStackTrace();
}
pack();
}
And we have as result:
java.lang.NullPointerException at net.sf.jmoney.gui.AboutDialog.<init>(AboutDialog.java:67)
Line 67 is:
this.setTitle(language.getLanguageInUse().getString("AboutDialog.title"));
Our interface is:
public interface Language {
public ResourceBundle getLanguageInUse();
}
And the Polish class is:
public class Polish implements Language {
private ResourceBundle languageInUse;
public Polish() {
languageInUse = ResourceBundle.getBundle(Constants.LANGUAGE_PL);
}
public ResourceBundle getLanguageInUse() {
return languageInUse;
}
}
We are lost...
You're using "field injection". This will make it hard to use your injected values in a constructor; even if Guice were to create the object (which is not happening now) or you were to use injector.injectMembers(aboutDialog)
, the constructor would run before the injector has a chance to inject the field you want.
It's a little more tricky to create a class that takes a varying parameter as well as an injected parameter. This leaves you with a few options:
Inject the JFrame. If you know what JFrame you're going to use when the constructor is being created, then just use bind(JFrame.class).toInstance(myJFrame);
in your Module. Then Guice can create the AboutDialog entirely.
Create a Factory manually. That way you can inject AboutDialog.Factory
and just call create
to get your AboutDialog
. It'll look something like this:
public class AboutDialog extends JDialog {
/** Injectable factory. */
public static class Factory {
@Inject private Language language;
public AboutDialog create(JFrame parent) {
return new AboutDialog(parent, language);
}
}
// no @Inject parameter; you're calling "new" yourself above!
public AboutDialog(JFrame parent, Language language) {
super(parent, "", true);
this.language = language;
// ... other initialization
}
}
Create a Factory and let Guice wire it up for you via assisted injection.
public class AboutDialog extends JDialog {
public interface Factory {
public AboutDialog create(JFrame parent);
}
// you need the @Inject, and also the @Assisted to tell Guice to
// use the parameter instead of Guice bindings
@Inject
public AboutDialog(@Assisted JFrame parent, Language language) {
super(parent, "", true);
this.language = language;
// ... other initialization
}
}
public class StandardModule extends AbstractModule {
@Override protected void configure() {
bind(Language.class).to(Polish.class);
// here every method in AboutDialog.Factory will be implemented
// to create the method's return type [AboutDialog] based on
// the parameters (like JFrame) and the bindings (like Language)
install(new FactoryModuleBuilder().build(AboutDialog.Factory.class));
}
}
As noted in the question comments, make sure you're getting your AboutDialog
(or AboutDialog.Factory
via an @Inject
ed constructor/field or from the Injector
itself, or else Guice will not know to inject the parameters.