Make SpringModule aware of bindings in wrapping Injector
See gh-13, gh-12
This commit is contained in:
committed by
Dave Syer
parent
c42ba69532
commit
b26bdb4125
2
pom.xml
2
pom.xml
@@ -30,7 +30,7 @@
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
<version>3.0</version>
|
||||
<version>4.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -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<Injector> injectorProvider;
|
||||
|
||||
@Override
|
||||
public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder,
|
||||
DependencyDescriptor descriptor) {
|
||||
return false;
|
||||
}
|
||||
public GuiceAutowireCandidateResolver(Provider<Injector> 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Type, Provider<?>> bound = new HashMap<Type, Provider<?>>();
|
||||
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private Provider<ConfigurableListableBeanFactory> 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<ConfigurableListableBeanFactory> 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<T> implements Provider<T> {
|
||||
|
||||
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<T> type) {
|
||||
private BeanFactoryProvider(ConfigurableListableBeanFactory beanFactory,
|
||||
String name, Class<T> type) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public static <S> Provider<S> named(DefaultListableBeanFactory beanFactory,
|
||||
public static <S> Provider<S> named(ConfigurableListableBeanFactory beanFactory,
|
||||
String name, Class<S> type) {
|
||||
return new BeanFactoryProvider<S>(beanFactory, name, type);
|
||||
}
|
||||
|
||||
public static <S> Provider<S> typed(DefaultListableBeanFactory beanFactory,
|
||||
public static <S> Provider<S> typed(ConfigurableListableBeanFactory beanFactory,
|
||||
Class<S> type) {
|
||||
return new BeanFactoryProvider<S>(beanFactory, null, type);
|
||||
}
|
||||
@@ -184,5 +216,4 @@ public class SpringModule implements Module {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ConfigurableListableBeanFactory>() {
|
||||
@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<ApplicationEvent> eventListener(
|
||||
final IGuiceDependency1 dependency) {
|
||||
return new ApplicationListener<ApplicationEvent>() {
|
||||
@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 <T> void onProvision(ProvisionInvocation<T> provision) {
|
||||
if (!initialized.getAndSet(true) && !context.isActive()) {
|
||||
context.refresh();
|
||||
}
|
||||
provision.provision();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String> parameterizedBean() {
|
||||
return new Parameterized<String>(){};
|
||||
return new Parameterized<String>() {
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user