19

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...

Daniel Werner
  • 1,350
  • 16
  • 26
user1810567
  • 1,129
  • 3
  • 14
  • 22
  • You should change your code a little bit, to see where the NPE is thrown. `code ResourceBundle bundle = language.getLanguageInUse(); String label = bundle.getString("AboutDialog.title"); ` – Daniel Manzke Nov 08 '12 at 21:31
  • The null object is Language. We think that the injection doesn't work. – user1810567 Nov 08 '12 at 21:43
  • how do you create the AboutDialog? I don't think you are creating the dialog with Guice, because Guice needs a empty constructor or do you bind jframe somewhere? – Daniel Manzke Nov 08 '12 at 21:44
  • Try printing the current Thread stack trace with a log/System.out instruction in the constructor of the Polish class. In this way you can see if it is built by the Guice framework. Is the this.language=language; instruction really needed? – Tony Rad Nov 08 '12 at 21:50
  • 2
    ResourceBundle can load Local-specific properties, no need to wrap it in a Language class: http://docs.oracle.com/javase/tutorial/i18n/resbundle/concept.html – Jan Galinski Nov 09 '12 at 07:00

2 Answers2

14

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 @Injected constructor/field or from the Injector itself, or else Guice will not know to inject the parameters.

Jeff Bowman
  • 90,959
  • 16
  • 217
  • 251
  • We have succeed to inject the object and the project works perfectly. Thank you for your help ^^. – user1810567 Nov 09 '12 at 19:46
  • You're welcome! If you problem is solved please remember to [accept an answer](http://stackoverflow.com/faq#howtoask). Good luck with your project! – Jeff Bowman Nov 10 '12 at 01:00
  • 2
    This sentence "the constructor would run before the injector has a chance to inject the field you want" saved my day, as I was not understanding why my injected fields being used inside my default construtor were allways null. Thanks!!! – mljrg Nov 14 '15 at 02:07
11

I assume that your are not creating your AboutDialog with the help of Guice.

What you could do is use injector.injectMembers(this) where this is the AboutDialog.

The best way would be that the AboutDialog will be created by Guice, so all members will be injected.

Daniel Werner
  • 1,350
  • 16
  • 26
Daniel Manzke
  • 310
  • 2
  • 8