package com.netflix.governator; import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Module; import com.google.inject.Stage; import com.google.inject.spi.Element; import com.google.inject.spi.ElementVisitor; import com.google.inject.spi.Elements; import com.google.inject.util.Modules; import com.netflix.governator.spi.InjectorCreator; import com.netflix.governator.spi.ModuleTransformer; import com.netflix.governator.visitors.IsNotStaticInjectionVisitor; import com.netflix.governator.visitors.KeyTracingVisitor; import com.netflix.governator.visitors.WarnOfStaticInjectionVisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.function.Consumer; /** * Simple DSL on top of Guice through which an injector may be created using a series * of operations and transformations of Guice modules. Operations are tracked using a * single module and are additive such that each operation executes on top of the entire * current binding state. Once all bindings have been defined the injector can be created * using an {@link InjectorCreator} strategy. * * <code> * InjectorBuilder * .fromModule(new MyApplicationModule()) * .overrideWith(new OverridesForTesting()) * .traceEachElement(new BindingTracingVisitor()) * .createInjector(); * </code> */ public final class InjectorBuilder { private static final Logger LOG = LoggerFactory.getLogger(InjectorBuilder.class); private static final Stage LAZY_SINGLETONS_STAGE = Stage.DEVELOPMENT; private Module module; /** * Start the builder using the specified module. * * @param module * @return */ public static InjectorBuilder fromModule(Module module) { return new InjectorBuilder(module); } public static InjectorBuilder fromModules(Module ... additionalModules) { return new InjectorBuilder(Modules.combine(additionalModules)); } public static InjectorBuilder fromModules(List<Module> modules) { return new InjectorBuilder(Modules.combine(modules)); } private InjectorBuilder(Module module) { this.module = module; } /** * Override all existing bindings with bindings in the provided modules. * This method uses Guice's build in {@link Modules#override} and is preferable * to using {@link Modules#override}. The approach here is to attempt to promote * the use of {@link Modules#override} as a single top level override. Using * {@link Modules#override} inside Guice modules can result in duplicate bindings * when the same module is installed in multiple placed. * @param modules */ public InjectorBuilder overrideWith(Module ... modules) { return overrideWith(Arrays.asList(modules)); } /** * @see InjectorBuilder#overrideWith(Module...) */ public InjectorBuilder overrideWith(Collection<Module> modules) { this.module = Modules.override(module).with(modules); return this; } /** * Add additional bindings to the module tracked by the DSL * @param modules */ public InjectorBuilder combineWith(Module ... modules) { List<Module> m = new ArrayList<>(); m.add(module); m.addAll(Arrays.asList(modules)); this.module = Modules.combine(m); return this; } /** * Iterator through all elements of the current module and write the output of the * ElementVisitor to the logger at debug level. 'null' responses are ignored * @param visitor * * @deprecated Use forEachElement(visitor, message -> LOG.debug(message)); instead */ @Deprecated public InjectorBuilder traceEachElement(ElementVisitor<String> visitor) { return forEachElement(visitor, message -> LOG.debug(message)); } /** * Iterate through all elements of the current module and pass the output of the * ElementVisitor to the provided consumer. 'null' responses from the visitor are ignored. * * This call will not modify any bindings * @param visitor */ public <T> InjectorBuilder forEachElement(ElementVisitor<T> visitor, Consumer<T> consumer) { Elements .getElements(module) .forEach(element -> Optional.ofNullable(element.acceptVisitor(visitor)).ifPresent(consumer)); return this; } /** * Call the provided visitor for all elements of the current module. * * This call will not modify any bindings * @param visitor */ public <T> InjectorBuilder forEachElement(ElementVisitor<T> visitor) { Elements .getElements(module) .forEach(element -> element.acceptVisitor(visitor)); return this; } /** * Log the current binding state. traceEachKey() is useful for debugging a sequence of * operation where the binding snapshot can be dumped to the log after an operation. */ public InjectorBuilder traceEachKey() { return forEachElement(new KeyTracingVisitor(), message -> LOG.debug(message)); } /** * Log a warning that static injection is being used. Static injection is considered a 'hack' * to alllow for backwards compatibility with non DI'd static code. */ public InjectorBuilder warnOfStaticInjections() { return forEachElement(new WarnOfStaticInjectionVisitor(), message -> LOG.debug(message)); } /** * Extend the core DSL by providing a custom ModuleTransformer. The output module * replaces the current module. * @param transformer */ public InjectorBuilder map(ModuleTransformer transformer) { this.module = transformer.transform(module); return this; } /** * Filter out elements for which the provided visitor returns true. * @param predicate */ public InjectorBuilder filter(ElementVisitor<Boolean> predicate) { List<Element> elements = new ArrayList<Element>(); for (Element element : Elements.getElements(module)) { if (element.acceptVisitor(predicate)) { elements.add(element); } } this.module = Elements.getModule(elements); return this; } /** * Filter out all bindings using requestStaticInjection */ public InjectorBuilder stripStaticInjections() { return filter(new IsNotStaticInjectionVisitor()); } /** * @return Return all elements in the managed module */ public List<Element> getElements() { return Elements.getElements(module); } /** * Create the injector in the specified stage using the specified InjectorCreator * strategy. The InjectorCreator will most likely perform additional error handling on top * of the call to {@link Guice#createInjector}. * * @param stage Stage in which the injector is running. It is recommended to run in Stage.DEVELOPEMENT * since it treats all singletons as lazy as opposed to defaulting to eager instantiation which * could result in instantiating unwanted classes. * @param creator */ public <I extends Injector> I createInjector(Stage stage, InjectorCreator<I> creator) { return creator.createInjector(stage, module); } /** * @see {@link InjectorBuilder#createInjector(Stage, InjectorCreator)} */ public <I extends Injector> I createInjector(InjectorCreator<I> creator) { return creator.createInjector(LAZY_SINGLETONS_STAGE, module); } /** * @see {@link InjectorBuilder#createInjector(Stage, InjectorCreator)} */ public LifecycleInjector createInjector(Stage stage) { return createInjector(stage, new LifecycleInjectorCreator()); } /** * @see {@link InjectorBuilder#createInjector(Stage, InjectorCreator)} */ public LifecycleInjector createInjector() { return createInjector(LAZY_SINGLETONS_STAGE, new LifecycleInjectorCreator()); } }