How to @Inject into existing object hierarchy using Guice?

前端 未结 2 1637
萌比男神i
萌比男神i 2021-02-02 17:41

I have an existing object hierarchy where some objects have fields that need to be injected. Also there are some other objects that are constructed using Google Guice and need t

相关标签:
2条回答
  • 2021-02-02 18:30

    This solution will work, but I'd like to propose a slightly different one to you.

    Specifically, since you're going to traverse a deep object structure, this really looks like a job for the Visitor pattern. Also, what you're describing seems to call out for a two-stage injector: a "bootstrap" stage that can inject stuff needed by the pivot-created hierarchy (but can't inject any pivot-created elements) and a second stage that is the real injector used by your app (that can inject anything).

    What I would suggest is this basic pattern: make a visitor that traverses the hierarchy and, as it goes, it does injection on those things that need it and records those things that need to be injected elsewhere. Then, when it is done visitng everything, it uses Injector.createChildInjector to make a new Injector that can inject stuff from the original Injector and stuff from the pivot-created hierarchy.

    First define a visitor that can hit everything in this hierarchy:

    public interface InjectionVisitor {
      void needsInjection(Object obj);
      <T> void makeInjectable(Key<T> key, T instance);
    }
    

    Then define an interface for all your pivot-created elements:

    public interface InjectionVisitable {
      void acceptInjectionVisitor(InjectionVisitor visitor);
    }
    

    You'd implement this interface in your pivot-created classes as (assuming this code in the FooContainer class):

    public void acceptInjectionVisitor(InjectionVisitor visitor) {
      visitor.needsInjection(this);
      visitor.makeInjectable(Key.get(FooContainer.class), this);
      for (InjectionVisitable child : children) {
        child.acceptInjectionVisitor(visitor);
      }
    }
    

    Note that the first two statements are optional - it may be that some objects in the pivot hierarchy don't need injection and it could also be that some of them you wouldn't want to have injectable later. Also, notice the use of Key - this means that if you want some class to be injectable with a particular annotation you can do something like:

    visitor.makeInjectable(Key.get(Foo.class, Names.named(this.getName())), this);
    

    Now, how do you implement InjectionVisitor? Here's how:

    public class InjectionVisitorImpl implements InjectionVisitor {
      private static class BindRecord<T> {
        Key<T> key;
        T value;
      }
    
      private final List<BindRecord<?>> bindings = new ArrayList<BindRecord<?>>();
      private final Injector injector;
    
      public InjectionVisitorImpl(Injector injector) {
        this.injector = injector;
      }
    
      public void needsInjection(Object obj) {
        injector.injectMemebers(obj);
      }
    
      public <T> void makeInjectable(Key<T> key, T instance) {
        BindRecord<T> record = new BindRecord<T>();
        record.key = key;
        record.value = instance;
        bindings.add(record);
      }
    
      public Injector createFullInjector(final Module otherModules...) {
        return injector.createChildInjector(new AbstractModule() {
          protected void configure() {
            for (Module m : otherModules) { install(m); }
            for (BindRecord<?> record : bindings) { handleBinding(record); }
          }
          private <T> handleBinding(BindRecord<T> record) {
            bind(record.key).toInstance(record.value);
          }
        });
      }
    }
    

    You then use this in your main method as:

    PivotHierarchyTopElement top = ...; // whatever you need to do to make that
    Injector firstStageInjector = Guice.createInjector(
       // here put all the modules needed to define bindings for stuff injected into the
       // pivot hierarchy.  However, don't put anything for stuff that needs pivot
       // created things injected into it.
    );
    InjectionVisitorImpl visitor = new InjectionVisitorImpl(firstStageInjector);
    top.acceptInjectionVisitor(visitor);
    Injector fullInjector = visitor.createFullInjector(
      // here put all your other modules, including stuff that needs pivot-created things
      // injected into it.
    );
    RealMainClass realMain = fullInjector.getInstance(RealMainClass.class);
    realMain.doWhatever();
    

    Note that the way createChildInjector works ensures that if you have any @Singleton things bound in the stuff injected into the pivot hierarchy, you'll get the same instances injected by your real injector - the fullInjector will delegate injectoion to the firstStageInjector so long as the firstStageInjector is able to handle the injection.

    Edited to add: An interesting extension of this (if you want to delve into deep Guice magic) is to modify InjectionImpl so that it records the place in your source code that called makeInjectable. This then lets you get better error messages out of Guice when your code accidentally tells the visitor about two different things bound to the same key. To do this, you'd want to add a StackTraceElement to BindRecord, record the result of new RuntimeException().getStackTrace()[1] inside the method makeInjectable, and then change handleBinding to:

    private <T> handleBinding(BindRecord<T> record) {
      binder().withSource(record.stackTraceElem).bind(record.key).toInstance(record.value);
    }
    
    0 讨论(0)
  • 2021-02-02 18:31

    You could inject MembersInjectors to inject nested fields. For example, this will deeply inject an existing Car instance:

    public class Car {
      Radio radio;
      List<Seat> seats;
      Engine engine;
    
      public Car(...) {...}
    
      @Inject void inject(RadioStation radioStation,
          MembersInjector<Seat> seatInjector,
          MembersInjector<Engine> engineInjector) {
        this.radio.setStation(radioStation);
        for (Seat seat : seats) {
          seatInjector.injectMembers(seat);
        }
        engineInjector.injectMembers(engine);
      }
    }
    
    public class Engine {
      SparkPlug sparkPlug;
      Turbo turbo
    
      public Engine(...) {...}
    
      @Inject void inject(SparkPlug sparkplug,
          MembersInjector<Turbo> turboInjector) {
        this.sparkPlug = sparkPlug;
        turboInjector.injectMembers(turbo);
      }
    }
    
    0 讨论(0)
提交回复
热议问题