简体   繁体   中英

How to create objects with Guice injectors?

I have in my code:

private static class BaseScriptInfoParser extends NodeParser<Asset.ScriptInfo> {

    private Asset.ScriptKindEnum scriptKind;

    private final NodeParser<Asset.TransformerKindEnum> transformerKindNodeParser;
    private final NodeParser<Asset.ValidatorKindEnum>   validatorKindNodeParser;

    @Inject
    BaseScriptInfoParser(
            // First two arguments are injectors
            @Named("transformerKind") NodeParser<Asset.TransformerKindEnum> transformerKindNodeParser,
            @Named("validatorKind") NodeParser<Asset.ValidatorKindEnum> validatorKindNodeParser,
            Asset.ScriptKindEnum scriptKind)
    {
        this.scriptKind = scriptKind;
        this.transformerKindNodeParser = transformerKindNodeParser;
        this.validatorKindNodeParser   = validatorKindNodeParser;
    }

    // ...

}

Now I want to create an instance of BaseScriptInfoParser without code like (because as I learned it should be only in Main function)

Injector injector = Guice.createInjector(new BoilerModule());
// ...

Can I just call a constructor to create an object of BaseScriptInfoParser class with one argument (of Asset.ScriptKindEnum type) and have the first two arguments to be injected automatically?

Or how does creation of objects with injectors work?

And how would it work if the constructor BaseScriptInfoParser didn't have the third parameter?

You are correct to avoid additional calls to createInjector , and to avoid passing around the Injector you create. For best Guice practices, you should be specifying exactly what objects any given component creates or depends on, and Injector is the opposite of that: it can create anything.

Instead, in general, you should inject the objects that you need from the graph. If you think you might only need an object later, or you might need several instances of an object, you can inject a Provider<T> instead (where T is any object available in the graph), and then you can request the instance later as if you called getInstance (but without creating a new object or making the rest of the graph available). This should make testing much easier as well, because simulating a Provider in tests is very easy , but simulating an Injector is difficult, and using a real Injector is expensive and complicated.

If BaseScriptInfoParser didn't have this manual third parameter, you could just inject Provider<BaseScriptInfoParser> : Guice would automatically handle this as long as BaseScriptInfoParser had a public parameterless constructor, an @Inject annotated constructor, or a bind(BaseScriptInfoParser.class) binding or @Provides BaseScriptInfoParser method in the module.


Now, about mixing injected constructor parameters and non-injected parameters:

Not every object in your graph needs to be Injectable: To use Miško Hevery's terminology from his "To new or not to new " article , your application will likely be composed of injectables that come from the graph, with some newables like "value objects" and "data objects" that have lots of state and no dependencies.

However, for certain objects, it makes sense to have constructor-provided immutable state while also accessing injectables from the graph , without separating the two into separate objects (which is also an option). Effectively, what you'd like is an object that your DI framework can provide, that fulfills this interface:

interface BaseScriptInfoParserFactory {
  /**
   * Calls the BaseScriptInfoParser constructor, with other constructor params
   * injected from the graph.
   */
  BaseScriptInfoParser create(Asset.ScriptKindEnum scriptKind);
}

Because this is a very well-defined class to write, Google offers a couple of different options for how to generate one automatically: you can use either Guice's reflective Assisted Injection or AutoFactory from the code-generating Google Auto package. The latter is a bit faster because it generates normal code rather than runtime reflective code, but the former integrates slightly better with Guice:

  1. Make sure the Guice Assisted Injection JAR is on the classpath. It's separate.

  2. Mark your constructor to say which parameters should come from Guice:

     @Inject BaseScriptInfoParser( @Named("transformerKind") NodeParser<...> transformerKindNodeParser, @Named("validatorKind") NodeParser<...> validatorKindNodeParser, @Assisted Asset.ScriptKindEnum scriptKind) 
  3. Write a Factory interface that you can inject, which I like to do as a nested interface:

     public class BaseScriptInfoParser { public interface Factory { // Any interface and method name works. These are the most common. BaseScriptInfoParser create(Asset.ScriptKindEnum scriptKind); } // ... rest of the class, including the above constructor } 
  4. Tell Guice to write an implementation and bind to it:

     public class YourModule extends AbstractModule { @Override public void configure() { install(new FactoryModuleBuilder() .build(BaseScriptInfoParser.Factory.class)); } } 
  5. Inject your BaseScriptInfoParser.Factory and call create(someScriptKind) whenever you need a new object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM