3

I'm trying to write a framework where arbitrary bean classes are injected with classes from my API, and they can interact with both those classes as well have triggered callbacks based on defined annotations. Here's an example bean:

@Experiment
static class TestExperiment {
    private final HITWorker worker;
    private final ExperimentLog log;
    private final ExperimentController controller;

    @Inject
    public TestExperiment(
        HITWorker worker,
        ExperimentLog expLog,
        ExperimentController controller
        ) {         
        this.worker = worker;
        this.expLog = expLog;
        this.controller = controller;
    }

    @SomeCallback
    void callMeBack() {
        ... do something

        log.print("I did something");
    }

}

I'm trying to use Guice to inject these beans and handle the interdependencies between the injected classes. However, I have two problems:

  • One of the classes I pass in (HITWorker) is already instantiated. I couldn't see how to move this to a Provider without significantly complicating my code. It is also persistent, but not to the Guice-defined session or request scope, so I am managing it myself for now. (Maybe if the other issues are overcome I can try to put this in a provider.)
  • More importantly, I need a reference to the other injected classes so I can do appropriate things to them. When Guice injects them, I can't access them because the bean class is arbitrary.

Here's some really bad code for what I basically need to do, which I am sure is violating all the proper dependency injection concepts. Note that hitw is the only instance that I need to pass in, but I'm creating the other dependent objects as well because I need references to them. With this code, I'm basically only using Guice for its reflection code, not its dependency resolution.

private void initExperiment(final HITWorkerImpl hitw, final String expId) {         
    final ExperimentLogImpl log = new ExperimentLogImpl();
    final ExperimentControllerImpl cont = new ExperimentControllerImpl(log, expManager);

    // Create an experiment instance with specific binding to this HITWorker
    Injector child = injector.createChildInjector(new AbstractModule() {
        @Override
        protected void configure() {
            bind(HITWorker.class).toInstance(hitw);
            bind(ExperimentLog.class).toInstance(log);
            bind(ExperimentController.class).toInstance(cont);
        }           
    });     

    Object experimentBean = child.getInstance(expClass);        

    expManager.processExperiment(expId, experimentBean);

    // Initialize controller, which also initializes the log
    cont.initialize(expId);

    expManager.triggerStart(expId);

    tracker.newExperimentStarted(expId, hitw, cont.getStartTime());
}

Am I screwed and just have to write my own injection code, or is there a way to do this properly? Also, should I just forget about constructor injection for these bean classes, since I don't know what they contain exactly anyway? Is there any way to get the dependencies if I am asking Guice to inject the bean instead of doing it myself?

For context, I've been reading the Guice docs and looking at examples for several days about this, to no avail. I don't think I'm a complete programming idiot, but I can't figure out how to do this properly!

Andrew Mao
  • 35,740
  • 23
  • 143
  • 224

1 Answers1

1

Your "experiment" seems to be something like a "request" in the sense that it has a defined lifecycle and some associated stuff the experiment can pull in at will.

Therefore I think you should wrap all that into a custom scope as described in the docs about Custom Scopes. This matches your case in several points:

  • You can "seed" the scope with some objects (your HITWorker)
  • The lifecycle: do "enter scope" before you setup the experiment and "exit scope" after you finished your work.
  • Access to "shared" stuff like ExperimentLog and ExperimentController: Bind them to the scope. Then both the framework and the experiment instance can simple @Inject them and get the same instance.
A.H.
  • 63,967
  • 15
  • 92
  • 126
  • This seems like almost exactly what I need, but maybe you could provide some additional clarification. It seems like I could create an `ExperimentScope` which is seeded with `HITworker`. In fact, both the `ExperimentLog` and the `ExperimentController` will be scoped to the experiment as well, but they refer to some global `@Singleton` classes. However, each scope is necessarily going to have to be accessed with multiple threads (since an experiment involves multiple users). If I have multiple experiments (scopes) actives each with instances that I want, how do I access each of them? – Andrew Mao Dec 04 '12 at 20:57
  • Regarding the `ExperimentLog` and `@Singleton`: This is not a problem - "inner" scopes can receive stuff from "outer" scopes. Regarding multiple threads: The linkes example uses `ThreadLocal`. In your case you must _additionally_ transfer this to other threads. This should be no problem _if_ the threads (or `Future`) are created after entering your scope because then creating new threads or futures can use injection as well and hence access the current scope and propagate it. – A.H. Dec 04 '12 at 21:37