开发者

Injecting generated classes without writing too much module configuration code

开发者 https://www.devze.com 2023-02-27 02:47 出处:网络
Here\'s the situation: I have an abstract class with a constructor that takes a boolean (which controls some caching behavior):

Here's the situation: I have an abstract class with a constructor that takes a boolean (which controls some caching behavior):

abstract class BaseFoo { protected BaseFoo(boolean cache) {...} }

The implementations are all generated source code (many dozens of them). I want to create bindings for all of them automatically, i.e. without explicit hand-coding for each type being bound. I want the injection sites to be able to specify either caching or non-caching (true/false ctor param). For example I might have two injections like:

DependsOnSomeFoos(@Inject @NonCaching AFoo aFoo, @Inject @Caching BFoo bFoo) {...}

(Arguably that's a bad thing to do, since the decision to cache or not might better be in a module. But it seems useful given what I'm working with.)

The question then is: what's the best way to configure bindings to produce a set of generated types in a uniform way, that supports a binding annotation as well as constructor param on the concrete class?

Previously I just had a default constructor on the implementation classes, and simply put an @ImplementedBy on each of the generated interfaces. E.g.:

// This is all generated source...
@ImplementedBy(AFooImpl.class)
interface AFoo { ... }

class AFooImpl extends BaseFoo implements AFoo {  AFooImpl() { super(true); } }

But, now I want to allow individual injection points to decide if true or false is passed to BaseFoo, instead of it always defaulting to true. I tried to set up an injection listener to (sneakily) change the true/false value p开发者_JS百科ost-construction, but I couldn't see how to "listen" for a range of types injected with a certain annotation.

The problem I keep coming back to is that bindings need to be for a specific type, but I don't want to enumerate all my types centrally.

I also considered:

  1. Writing some kind of scanner to discover all the generated classes and add a pair of bindings for each of them, perhaps using Google Reflections.
  2. Creating additional, trivial "non caching" types (e.g. AFoo.NoCache extends AFoo), which would allow me to go back to @ImplementedBy.
  3. Hard wiring each specific type as either caching/non-caching when it's generated.

I'm not feeling great about any of those ideas. Is there a better way?


UPDATE: Thanks for the comment and answer. I think generating a small module alongside each type and writing out a list of the modules to pull in at runtime via getResources is the winner.

That said, after talking w/ a coworker, we might just dodge the question as I posed it and instead inject a strategy object with a method like boolean shouldCache(Class<? extends BaseFoo> c) into each generated class. The strategy can be implemented on top of the application config and would provide coarse and fine grained control. This gives up on the requirement to vary the behavior by injection site. On the plus side, we don't need the extra modules.


There are two additional approaches to look at (in addition to what you mentioned):

  1. Inject Factory classes instead of your real class; that is, your hand-coded stuff would end up saying:

    @Inject
    DependsOnSomeFoos(AFoo.Factory aFooFactory, BFoo.Factory bFooFactory) {
      AFoo aFoo = aFooFactory.caching();
      BFoo bFoo = bFooFactory.nonCaching();
      ...
    }
    

    and your generated code would say:

    // In AFoo.java
    interface AFoo {
      @ImplementedBy(AFooImpl.Factory.class)
      interface Factory extends FooFactory<AFoo> {}
      // ...
    }
    
    // In AFooImpl.java
    class AFooImpl extends BaseFoo implements AFoo {
      AFooImpl(boolean caching, StuffNeededByAFIConstructor otherStuff) {
        super(caching);
        // use otherStuff
      }
      // ...
      class Factory implements AFoo.Factory {
        @Inject Provider<StuffNeededByAFIConstructor> provider;
        public AFoo caching() {
          return new AFooImpl(true, provider.get());
        }
        // ...
      }
    }
    

    Of course this depends on an interface FooFactory:

    interface FooFactory<T> {
      T caching();
      T nonCaching();
    }
    
  2. Modify the process that does your code generation to generate also a Guice module that you then use in your application setup. I don't know how your code generation is currently structured, but if you have some way of knowing the full set of classes at code generation time you can either do this directly or append to some file that can then be loaded with ClassLoader.getResources as part of a Guice module that autodiscovers what classes to bind.

0

精彩评论

暂无评论...
验证码 换一张
取 消