Guice#bindMembers() and listeners initialized by the class itself cause NPEs

67 views
Skip to first unread message

Evgeny Chesnokov

unread,
Nov 28, 2017, 5:54:49 AM11/28/17
to google-guice
Hi all!


I'm having a problem with Guice injection in one particular corner-case. I use Guice to inject model to the controller instances that are parameterized in the constructor, hence I'm using #bindMembers() and not the constructor injection. It seems that if a model callback field in the controller instance was instantiated before #bindMembers() call, then it fails to see the injected model classes. Here's a quick sample:

class MyController extends MyAbstractController {
  private int param;

  @Inject private MyModel model;

  private /*could also be final*/ PropertyChangeListener listener = new PropertyChangeListener() {
        @Override public void propertyChange( PropertyChangeEvent evt ) {
            model.doStuff(); // fails with NPE because model is null!
        }
    };

  public MyController(int param) {
    this.param = param;
  }

  // Injector#injectMembers(myController) gets called by my framework and then myController#init()  
  public init() {
    model.addPropertyChangeListener("prop", listener); // does NOT fail with NPE during initialization
  }
}

So I've been wondering which internal mechanims I keep missing that allow for this kind of a strange behaviour? Any ideas welcome.


Thanks in advance,
Evgeny.

Stephan Classen

unread,
Nov 28, 2017, 2:53:47 PM11/28/17
to google...@googlegroups.com

First of all I would not inject a model. Model classes are not meant to be handled with DI.
Second you can use assisted injection to use constructor injection if you have both parameters and dependencies in a constructor.

Non of the above helps clarify you question. But maybe it helps you think about your design.

Regarding your code:
Guice has no hidden mechanism which magically does things if you do not ask for it.
So after your framework has called injectMembers() guice will not change the controller unless your ask it to do it again.
I assume there is no other code which will later set the model back to null.

This boils it down to one most likely scenario"

Something happens before your framework calls injectMembers(). This something triggers the listener.propertyChange() method.
To be able to debug this a little better I would suggest that you add a setter for model and annotate the setter method with @Inject instead of the field.
This way you can add a breakpoint (and/or log output) to the setter. Do the same with the init() and the propertyChange() method and watch carefully in which order they are called.

Another less likely scenario is that you have more than one instance of MyController. In one instance you have a model and in the other instance model is null.

If you code is open source I can have a look into the issue

--
You received this message because you are subscribed to the Google Groups "google-guice" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-guice...@googlegroups.com.
To post to this group, send email to google...@googlegroups.com.
Visit this group at https://groups.google.com/group/google-guice.
To view this discussion on the web visit https://groups.google.com/d/msgid/google-guice/e66a5a5e-5fad-4605-9ae7-71b2634212d0%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Evgeny Chesnokov

unread,
Nov 29, 2017, 9:39:50 AM11/29/17
to google-guice
Thanks, stl!


First of all I would not inject a model. Model classes are not meant to be handled with DI.


Could you please elaborate this point of view a little further?
 

Second you can use assisted injection to use constructor injection if you have both parameters and dependencies in a constructor.

I'm trying to keep it as simple as possible and avoid creating several classes per one controller piece. Still thanks, that's a good alternative to keep in mind if I don't figure this out.
 

Something happens before your framework calls injectMembers(). This something triggers the listener.propertyChange() method.

That's the problem: the listener is only triggered by a model, and it's only added to a model in init() method. And the important thing to mention is that if I move instantiation of a listener from a field declaration to an init() method just prior to adding this listener to a model, not touching any other code - then everything works like a charm.

Still looking for an explanation to it, though. Thanks for your input anyway, I'll try debugging it through the setter method.
Reply all
Reply to author
Forward
0 new messages