package ru.vyarus.guice.ext.core.generator.anchor;
import com.google.inject.AbstractModule;
import com.google.inject.internal.DynamicClassProvider;
import com.google.inject.internal.DynamicSingletonProvider;
import javax.inject.Singleton;
/**
* Support module used to tie dynamic binding for generated class (generated with {@link DynamicClassProvider}) to
* exact injector in injectors hierarchy. Also, allows using JIT resolution for generated classes in private modules
* (without binding them explicitly).
* <p>
* Situation without this module: suppose we have root and child injector and aop interceptors
* (which must intercept generated abstract method calls) are registered in child module. If abstract bean
* resolved with guice JIT ({@code @ProvidedBy(DynamicClassProvider.class)} then it's binding will be created
* in upper-most injector: root injector. As a result, calling any abstract method will fail, because interceptors
* are not present in root module (only in child module).
* <p>
* To fix this problem we need to "tie" generated class binding to child injector. Guice will not bubble it up
* only if it depends on some other bean located in child injector. For this purpose, module registers
* dummy service {@link AnchorBean} which is only required to "hold" generated been in child injector.
* Also, provider classes are explicitly bound to be able to operate with child injector in provider.
* Both {@link DynamicClassProvider} and {@link DynamicSingletonProvider} will check first if anchor bean
* is available in current injector and inject dependency on this bean into generated class (add new constructor
* parameter for abstract class with constructor and generate new constructor for interface implementation or
* abstract class without constructor).
* <p>
* Also, module will prevent improper guice injector usage. For example, anchor module registered in child injector
* and some service DynService depends (injects) on abstract class annotated
* with {@code ProvidedBy(DynamicClassProvider.class)}. If DynService is not bound in child injector and we try
* to create instance of (using JIT binding) it from root injector, then guice will try to obtain DynamicClassProvider
* in root context and fail with duplicate binding definition (without anchor module, everything would pass,
* but aop will not work, because it was registered in child injector).
* <p>
* Example usage:
* <pre><code>
* Injector childInjector = Guice.createInjector() // root injector
* .createChildInjector(new GeneratorAnchorModule(), new MyAopModule());
* // JIT binding for MyAbstractService (generated class) will be in child injector and so aop will work
* childInjector.getInstance(MyAbstractService.class).someAMethodHandledWithAop();
* </code></pre>
* <p>
* Private module example:
* <pre><code>
* public class MyPrivateModule extends PrivateModule {
* protected void configure() {
* install(new MyAopModule());
* install(new GeneratorAnchorModule());
* }
*
* {@literal @}Provides @Exposed @Named("myBean")
* MyAbstractBean provide(MyAbstractBean bean) {
* return bean;
* }
* }
*
* Injector injector = Injector.createModule(new MyPrivateModule());
* // obtain exposed named instance outside of private module
* // note that abstract bean was not bound and resolved with JIT
* injector.getInstance(Key.get(MyAbstractBean.class, Names.named("myBean")))
* </code></pre>
*
* @author Vyacheslav Rusakov
* @since 21.09.2016
*/
public class GeneratorAnchorModule extends AbstractModule {
@Override
protected void configure() {
// providers bound explicitly to tie them to child injector
bind(DynamicClassProvider.class);
bind(DynamicSingletonProvider.class);
// anchor bean used to tie dynamically provided beans to child injector
bind(AnchorBean.class).in(Singleton.class);
}
}