diff --git a/pom.xml b/pom.xml index da2170b..3172c8a 100644 --- a/pom.xml +++ b/pom.xml @@ -30,7 +30,7 @@ com.google.inject guice - 3.0 + 4.0 compile diff --git a/src/main/java/org/springframework/guice/injector/GuiceAutowireCandidateResolver.java b/src/main/java/org/springframework/guice/injector/GuiceAutowireCandidateResolver.java index 145108b..38ce73e 100644 --- a/src/main/java/org/springframework/guice/injector/GuiceAutowireCandidateResolver.java +++ b/src/main/java/org/springframework/guice/injector/GuiceAutowireCandidateResolver.java @@ -13,36 +13,91 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.springframework.guice.injector; -import org.springframework.beans.factory.config.BeanDefinitionHolder; +import javax.inject.Provider; + +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.DependencyDescriptor; -import org.springframework.beans.factory.support.AutowireCandidateResolver; -import org.springframework.core.ResolvableType; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver; +import org.springframework.util.Assert; + +import com.google.inject.Injector; /** * @author Dave Syer + * @author Taylor Wicksell * */ -public class GuiceAutowireCandidateResolver implements AutowireCandidateResolver { +public class GuiceAutowireCandidateResolver extends ContextAnnotationAutowireCandidateResolver { + + private Provider injectorProvider; - @Override - public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, - DependencyDescriptor descriptor) { - return false; - } + public GuiceAutowireCandidateResolver(Provider injectorProvider) { + this.injectorProvider = injectorProvider; + } - @Override - public Object getSuggestedValue(DependencyDescriptor descriptor) { - ResolvableType resolvable = descriptor.getResolvableType(); - return null; - } + @Override + public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) { + return (isLazy(descriptor, beanName) ? buildLazyResolutionProxy(descriptor, beanName) : null); + } - @Override - public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, - String beanName) { - return null; - } + protected boolean isLazy(DependencyDescriptor descriptor, String beanName) { + Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory, + "BeanFactory needs to be a DefaultListableBeanFactory"); + final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory(); + try { + beanFactory.doResolveDependency(descriptor, beanName, null, null); + } catch (NoSuchBeanDefinitionException e) { + return true; + } + return super.isLazy(descriptor); + } + + protected Object buildLazyResolutionProxy(final DependencyDescriptor descriptor, final String beanName) { + Assert.state(getBeanFactory() instanceof DefaultListableBeanFactory, + "BeanFactory needs to be a DefaultListableBeanFactory"); + final DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory(); + TargetSource ts = new TargetSource() { + @Override + public Class getTargetClass() { + return descriptor.getDependencyType(); + } + + @Override + public boolean isStatic() { + return false; + } + + @Override + public Object getTarget() { + Object target = null; + try { + target = beanFactory.doResolveDependency(descriptor, beanName, null, null); + } catch (NoSuchBeanDefinitionException e) { + target = injectorProvider.get().getInstance(descriptor.getDependencyType()); + } + if (target == null) { + throw new NoSuchBeanDefinitionException(descriptor.getDependencyType(), + "Optional dependency not present for lazy injection point"); + } + return target; + } + + @Override + public void releaseTarget(Object target) { + } + }; + ProxyFactory pf = new ProxyFactory(); + pf.setTargetSource(ts); + Class dependencyType = descriptor.getDependencyType(); + if (dependencyType.isInterface()) { + pf.addInterface(dependencyType); + } + return pf.getProxy(beanFactory.getBeanClassLoader()); + } } diff --git a/src/main/java/org/springframework/guice/module/SpringModule.java b/src/main/java/org/springframework/guice/module/SpringModule.java index 6561969..0aefd82 100644 --- a/src/main/java/org/springframework/guice/module/SpringModule.java +++ b/src/main/java/org/springframework/guice/module/SpringModule.java @@ -20,64 +20,96 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.inject.Provider; + +import com.google.inject.AbstractModule; +import com.google.inject.Binder; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.ProvisionException; +import com.google.inject.Stage; +import com.google.inject.TypeLiteral; +import com.google.inject.matcher.Matchers; +import com.google.inject.name.Names; +import com.google.inject.spi.ProvisionListener; + import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.ApplicationContext; +import org.springframework.guice.injector.GuiceAutowireCandidateResolver; import org.springframework.util.ClassUtils; -import com.google.inject.Binder; -import com.google.inject.Module; -import com.google.inject.Key; -import com.google.inject.Provider; -import com.google.inject.ProvisionException; -import com.google.inject.TypeLiteral; -import com.google.inject.name.Names; - /** * @author Dave Syer * */ -public class SpringModule implements Module { - - private DefaultListableBeanFactory beanFactory; +public class SpringModule extends AbstractModule { private BindingTypeMatcher matcher = new GuiceModuleMetadata(); private Map> bound = new HashMap>(); + private ConfigurableListableBeanFactory beanFactory; + + private Provider beanFactoryProvider; + public SpringModule(ApplicationContext context) { - this((DefaultListableBeanFactory) context.getAutowireCapableBeanFactory()); + this((ConfigurableListableBeanFactory) context.getAutowireCapableBeanFactory()); } - public SpringModule(DefaultListableBeanFactory beanFactory) { + public SpringModule(ConfigurableListableBeanFactory beanFactory) { this.beanFactory = beanFactory; - if (beanFactory.getBeanNamesForType(GuiceModuleMetadata.class).length > 0) { - this.matcher = new CompositeTypeMatcher( - beanFactory.getBeansOfType(GuiceModuleMetadata.class).values()); - } } - public void configure(Binder binder) { - for (String name : this.beanFactory.getBeanDefinitionNames()) { - BeanDefinition definition = this.beanFactory.getBeanDefinition(name); + public SpringModule(Provider beanFactoryProvider) { + this.beanFactoryProvider = beanFactoryProvider; + } + + @Override + public void configure() { + if (binder().currentStage() != Stage.TOOL) { + if (beanFactory == null) { + beanFactory = beanFactoryProvider.get(); + } + if (beanFactory.getBeanNamesForType(ProvisionListener.class).length > 0) { + binder().bindListener(Matchers.any(), + beanFactory.getBeansOfType(ProvisionListener.class).values() + .toArray(new ProvisionListener[0])); + } + if (beanFactory instanceof DefaultListableBeanFactory) { + ((DefaultListableBeanFactory) beanFactory) + .setAutowireCandidateResolver(new GuiceAutowireCandidateResolver( + binder().getProvider(Injector.class))); + } + if (beanFactory.getBeanNamesForType(GuiceModuleMetadata.class).length > 0) { + this.matcher = new CompositeTypeMatcher( + beanFactory.getBeansOfType(GuiceModuleMetadata.class).values()); + } + } + bind(beanFactory); + } + + private void bind(ConfigurableListableBeanFactory beanFactory) { + for (String name : beanFactory.getBeanDefinitionNames()) { + BeanDefinition definition = beanFactory.getBeanDefinition(name); if (definition.isAutowireCandidate() && definition.getRole() == AbstractBeanDefinition.ROLE_APPLICATION) { - Class type = this.beanFactory.getType(name); + Class type = beanFactory.getType(name); final String beanName = name; - Provider typeProvider = BeanFactoryProvider.typed(this.beanFactory, - type); - Provider namedProvider = BeanFactoryProvider.named(this.beanFactory, + Provider typeProvider = BeanFactoryProvider.typed(beanFactory, type); + Provider namedProvider = BeanFactoryProvider.named(beanFactory, beanName, type); if (!type.isInterface() && !ClassUtils.isCglibProxyClass(type)) { - bindConditionally(binder, name, type, typeProvider, namedProvider); + bindConditionally(binder(), name, type, typeProvider, namedProvider); } for (Class iface : ClassUtils.getAllInterfacesForClass(type)) { - bindConditionally(binder, name, iface, typeProvider, namedProvider); + bindConditionally(binder(), name, iface, typeProvider, namedProvider); } for (Type iface : type.getGenericInterfaces()) { - bindConditionally(binder, name, iface, typeProvider, namedProvider); + bindConditionally(binder(), name, iface, typeProvider, namedProvider); } } } @@ -106,7 +138,7 @@ public class SpringModule implements Module { private static class BeanFactoryProvider implements Provider { - private DefaultListableBeanFactory beanFactory; + private ConfigurableListableBeanFactory beanFactory; private String name; @@ -114,19 +146,19 @@ public class SpringModule implements Module { private T result; - private BeanFactoryProvider(DefaultListableBeanFactory beanFactory, String name, - Class type) { + private BeanFactoryProvider(ConfigurableListableBeanFactory beanFactory, + String name, Class type) { this.beanFactory = beanFactory; this.name = name; this.type = type; } - public static Provider named(DefaultListableBeanFactory beanFactory, + public static Provider named(ConfigurableListableBeanFactory beanFactory, String name, Class type) { return new BeanFactoryProvider(beanFactory, name, type); } - public static Provider typed(DefaultListableBeanFactory beanFactory, + public static Provider typed(ConfigurableListableBeanFactory beanFactory, Class type) { return new BeanFactoryProvider(beanFactory, null, type); } @@ -184,5 +216,4 @@ public class SpringModule implements Module { return false; } } - } diff --git a/src/test/java/org/springframework/guice/annotation/GuiceModuleAnnotationTests.java b/src/test/java/org/springframework/guice/annotation/GuiceModuleAnnotationTests.java index b25dbba..53b50f7 100644 --- a/src/test/java/org/springframework/guice/annotation/GuiceModuleAnnotationTests.java +++ b/src/test/java/org/springframework/guice/annotation/GuiceModuleAnnotationTests.java @@ -25,7 +25,6 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; -import org.springframework.guice.annotation.GuiceModule; import org.springframework.guice.module.SpringModule; import com.google.inject.ConfigurationException; @@ -73,10 +72,7 @@ public class GuiceModuleAnnotationTests { } private Injector createInjector(Class... config) { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(config); - context.refresh(); - Injector injector = Guice.createInjector(new SpringModule(context)); + Injector injector = Guice.createInjector(new SpringModule(new AnnotationConfigApplicationContext(config))); return injector; } diff --git a/src/test/java/org/springframework/guice/module/SpringModuleGuiceBindingAwareTest.java b/src/test/java/org/springframework/guice/module/SpringModuleGuiceBindingAwareTest.java new file mode 100644 index 0000000..fec4d9d --- /dev/null +++ b/src/test/java/org/springframework/guice/module/SpringModuleGuiceBindingAwareTest.java @@ -0,0 +1,201 @@ +package org.springframework.guice.module; + +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.inject.Inject; +import javax.inject.Provider; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Scopes; +import com.google.inject.spi.ProvisionListener; +import com.google.inject.util.Providers; + +import org.junit.Test; + +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.guice.injector.GuiceAutowireCandidateResolver; +import org.springframework.test.util.AopTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +public class SpringModuleGuiceBindingAwareTest { + + @Test + public void testAllDependenciesInjectedAndLifeycleMethodsCalledOnce() { + Injector injector = Guice.createInjector(new SimpleGuiceModule(), + new SpringModule(new Provider() { + @Override + public ConfigurableListableBeanFactory get() { + PartiallyRefreshableApplicationContext context = new PartiallyRefreshableApplicationContext(); + context.register( + GuiceProjectWithSpringLibraryTestSpringConfig.class); + context.getBeanFactory().registerSingleton("refreshListener", + new ContextRefreshingProvisionListener(context)); + context.partialRefresh(); + return context.getBeanFactory(); + } + })); + + // check guice provided bindings + assertNotNull(injector.getInstance(GuiceDependency1.class)); + assertNotNull(injector.getInstance(IGuiceDependency1.class)); + + // check spring bindings as interface + ISpringBean springBean = injector.getInstance(ISpringBean.class); + assertNotNull(springBean); + assertNotNull(springBean.getDep1()); + assertNotNull(springBean.getDep2()); + assertNotNull(springBean.getDep3()); + + // invoke a method to make sure we aren't dealing with a lazy proxy + assertEquals("done", springBean.getDep1().doWork()); + + // check binding equality + assertSame(injector.getInstance(IGuiceDependency1.class), + AopTestUtils.getTargetObject(springBean.getDep1())); + assertSame(injector.getInstance(IGuiceDependency2.class), + AopTestUtils.getTargetObject(springBean.getDep2())); + assertSame(injector.getInstance(IGuiceDependency3.class), + AopTestUtils.getTargetObject(springBean.getDep3())); + } + + static class SimpleGuiceModule extends AbstractModule { + + @Override + protected void configure() { + bind(IGuiceDependency1.class).to(GuiceDependency1.class).in(Scopes.SINGLETON); // test + // normal + // binding + bind(IGuiceDependency2.class).toInstance(new IGuiceDependency2() { + }); // test instance binding + bind(IGuiceDependency3.class) + .toProvider(Providers.of(new IGuiceDependency3() { + })); // test provider binding + } + } + + @Configuration + static class GuiceProjectWithSpringLibraryTestSpringConfig { + + @Bean + public ISpringBean springDefinedSomething(IGuiceDependency1 dependency) { + return new SpringBean(dependency); + } + + @Bean + public ApplicationListener eventListener( + final IGuiceDependency1 dependency) { + return new ApplicationListener() { + @Override + public void onApplicationEvent(ApplicationEvent event) { + dependency.doWork(); + } + }; + } + } + + static interface IGuiceDependency1 { + String doWork(); + } + + static interface IGuiceDependency2 { + } + + static interface IGuiceDependency3 { + } + + static class GuiceDependency1 implements IGuiceDependency1 { + public String doWork() { + return "done"; + } + } + + static interface ISpringBean { + IGuiceDependency1 getDep1(); + + IGuiceDependency2 getDep2(); + + IGuiceDependency3 getDep3(); + } + + static class SpringBean implements ISpringBean { + + private final IGuiceDependency1 dep1; + @Inject + private IGuiceDependency2 dep2; + @Inject + private IGuiceDependency3 dep3; + + @Inject + public SpringBean(IGuiceDependency1 dependency) { + this.dep1 = dependency; + } + + @Override + public IGuiceDependency1 getDep1() { + return dep1; + } + + @Override + public IGuiceDependency2 getDep2() { + return dep2; + } + + @Override + public IGuiceDependency3 getDep3() { + return dep3; + } + } + + private static final class PartiallyRefreshableApplicationContext + extends AnnotationConfigApplicationContext { + + private final AtomicBoolean partiallyRefreshed = new AtomicBoolean(false); + + /* + * Initializes beanFactoryPostProcessors only to ensure that all BeanDefinition's + * are available + */ + void partialRefresh() { + invokeBeanFactoryPostProcessors(getBeanFactory()); + } + + @Override + protected void invokeBeanFactoryPostProcessors( + ConfigurableListableBeanFactory beanFactory) { + if (partiallyRefreshed.compareAndSet(false, true)) { + super.invokeBeanFactoryPostProcessors(beanFactory); + } + } + } + + private static final class ContextRefreshingProvisionListener + implements ProvisionListener { + private final ConfigurableApplicationContext context; + private final AtomicBoolean initialized = new AtomicBoolean(false); + + private ContextRefreshingProvisionListener( + ConfigurableApplicationContext context) { + this.context = context; + } + + @Override + public void onProvision(ProvisionInvocation provision) { + if (!initialized.getAndSet(true) && !context.isActive()) { + context.refresh(); + } + provision.provision(); + } + } + +} diff --git a/src/test/java/org/springframework/guice/module/SpringModuleWiringTests.java b/src/test/java/org/springframework/guice/module/SpringModuleWiringTests.java index 69e0f66..58b8d35 100644 --- a/src/test/java/org/springframework/guice/module/SpringModuleWiringTests.java +++ b/src/test/java/org/springframework/guice/module/SpringModuleWiringTests.java @@ -15,6 +15,7 @@ package org.springframework.guice.module; import org.junit.Rule; import org.junit.rules.ExpectedException; + import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -34,10 +35,8 @@ public class SpringModuleWiringTests extends AbstractCompleteWiringTests { @Override protected Injector createInjector() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(TestConfig.class); - context.refresh(); - return Guice.createInjector(new SpringModule(context)); + return Guice.createInjector(new SpringModule( + new AnnotationConfigApplicationContext(TestConfig.class))); } @Configuration @@ -61,10 +60,11 @@ public class SpringModuleWiringTests extends AbstractCompleteWiringTests { public Thing that() { return new Thing(); } - + @Bean public Parameterized parameterizedBean() { - return new Parameterized(){}; + return new Parameterized() { + }; } } }