diff --git a/src/main/java/org/springframework/guice/module/BeanFactoryProvider.java b/src/main/java/org/springframework/guice/module/BeanFactoryProvider.java new file mode 100644 index 0000000..d865a01 --- /dev/null +++ b/src/main/java/org/springframework/guice/module/BeanFactoryProvider.java @@ -0,0 +1,154 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.guice.module; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.inject.Provider; + +import com.google.inject.spi.ProvisionListener; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.OrderComparator; + +/** + * A {@link Provider} for a {@link BeanFactory} from an {@link ApplicationContext} that + * will not be refreshed until the Guice injector wants to resolve dependencies. Delaying + * the refresh means that the bean factory can resolve dependencies from Guice modules + * (and vice versa). + * + * @author Dave Syer + * + */ +public class BeanFactoryProvider implements Provider { + + private Class[] config; + private String[] basePackages; + private List> initializers = new ArrayList>(); + + /** + * Create an application context by scanning these base packages. + * + * @param basePackages + * @return a provider + */ + public static BeanFactoryProvider from(String... basePackages) { + return new BeanFactoryProvider(null, basePackages); + } + + /** + * Create an application context using these configuration classes. + * + * @param config + * @return a provider + */ + public static BeanFactoryProvider from(Class... config) { + return new BeanFactoryProvider(config, null); + } + + public BeanFactoryProvider initializer( + ApplicationContextInitializer... initializers) { + this.initializers.addAll(Arrays.asList(initializers)); + return this; + } + + private BeanFactoryProvider(Class[] config, String[] basePackages) { + this.config = config; + this.basePackages = basePackages; + } + + @Override + public ConfigurableListableBeanFactory get() { + // TODO: how to close the context? + PartiallyRefreshableApplicationContext context = new PartiallyRefreshableApplicationContext(); + if (config != null && config.length > 0) { + context.register(config); + } + if (basePackages != null && basePackages.length > 0) { + context.scan(basePackages); + } + context.partialRefresh(); + if (initializers != null && !initializers.isEmpty()) { + OrderComparator.sort(initializers); + for (ApplicationContextInitializer initializer : initializers) { + initializer.initialize(context); + } + } + return context.getBeanFactory(); + } + + 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 + */ + private void partialRefresh() { + getBeanFactory().registerSingleton("refreshListener", + new ContextRefreshingProvisionListener(this)); + invokeBeanFactoryPostProcessors(getBeanFactory()); + } + + private void delayedRefresh() throws BeansException, IllegalStateException { + super.refresh(); + } + + @Override + public void refresh() { + } + + @Override + protected void invokeBeanFactoryPostProcessors( + ConfigurableListableBeanFactory beanFactory) { + if (partiallyRefreshed.compareAndSet(false, true)) { + super.invokeBeanFactoryPostProcessors(beanFactory); + } + } + } + + private static final class ContextRefreshingProvisionListener + implements ProvisionListener { + private final PartiallyRefreshableApplicationContext context; + private final AtomicBoolean initialized = new AtomicBoolean(false); + + private ContextRefreshingProvisionListener( + PartiallyRefreshableApplicationContext context) { + this.context = context; + } + + @Override + public void onProvision(ProvisionInvocation provision) { + if (!initialized.getAndSet(true) && !context.isActive()) { + context.delayedRefresh(); + } + provision.provision(); + } + } + +} diff --git a/src/test/java/org/springframework/guice/module/SpringModuleGuiceBindingAwareTest.java b/src/test/java/org/springframework/guice/module/SpringModuleGuiceBindingAwareTest.java deleted file mode 100644 index 5d5d91d..0000000 --- a/src/test/java/org/springframework/guice/module/SpringModuleGuiceBindingAwareTest.java +++ /dev/null @@ -1,200 +0,0 @@ -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.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/SpringModuleGuiceBindingAwareTests.java b/src/test/java/org/springframework/guice/module/SpringModuleGuiceBindingAwareTests.java index d8ef350..a1ba9ac 100644 --- a/src/test/java/org/springframework/guice/module/SpringModuleGuiceBindingAwareTests.java +++ b/src/test/java/org/springframework/guice/module/SpringModuleGuiceBindingAwareTests.java @@ -1,31 +1,44 @@ +/* + * Copyright 2016-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ package org.springframework.guice.module; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; - import javax.inject.Inject; -import org.junit.Test; -import org.springframework.context.annotation.AnnotationConfigApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.guice.annotation.EnableGuiceModules; -import org.springframework.guice.injector.SpringInjector; -import org.springframework.test.util.AopTestUtils; - import com.google.inject.AbstractModule; +import com.google.inject.Guice; import com.google.inject.Injector; import com.google.inject.Scopes; import com.google.inject.util.Providers; +import org.junit.Test; + +import org.springframework.context.ApplicationEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +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 SpringModuleGuiceBindingAwareTests { @Test public void testAllDependenciesInjectedAndLifeycleMethodsCalledOnce() { - AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - context.register(GuiceProjectWithSpringLibraryTestSpringConfig.class, SimpleGuiceModule.class); - context.refresh(); - Injector injector = new SpringInjector(context); + Injector injector = Guice.createInjector(new SimpleGuiceModule(), + new SpringModule(BeanFactoryProvider + .from(GuiceProjectWithSpringLibraryTestSpringConfig.class))); // check guice provided bindings assertNotNull(injector.getInstance(GuiceDependency1.class)); @@ -38,31 +51,34 @@ public class SpringModuleGuiceBindingAwareTests { 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())); - - context.close(); + 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() { - // test normal binding - bind(IGuiceDependency1.class).to(GuiceDependency1.class).in(Scopes.SINGLETON); - // test instance binding + bind(IGuiceDependency1.class).to(GuiceDependency1.class).in(Scopes.SINGLETON); // test + // normal + // binding bind(IGuiceDependency2.class).toInstance(new IGuiceDependency2() { - }); - // test provider binding - bind(IGuiceDependency3.class).toProvider(Providers.of(new IGuiceDependency3() { - })); + }); // test instance binding + bind(IGuiceDependency3.class) + .toProvider(Providers.of(new IGuiceDependency3() { + })); // test provider binding } } @Configuration - @EnableGuiceModules static class GuiceProjectWithSpringLibraryTestSpringConfig { @Bean @@ -70,9 +86,20 @@ public class SpringModuleGuiceBindingAwareTests { 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 { @@ -82,6 +109,9 @@ public class SpringModuleGuiceBindingAwareTests { } static class GuiceDependency1 implements IGuiceDependency1 { + public String doWork() { + return "done"; + } } static interface ISpringBean { @@ -120,4 +150,5 @@ public class SpringModuleGuiceBindingAwareTests { return dep3; } } + }