Make SpringModule aware of bindings in wrapping Injector

See gh-13, gh-12
This commit is contained in:
Taylor Wicksell
2017-05-23 11:23:13 -07:00
committed by Dave Syer
parent c42ba69532
commit b26bdb4125
6 changed files with 348 additions and 65 deletions

View File

@@ -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>

View File

@@ -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());
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}

View File

@@ -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();
}
}
}

View File

@@ -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>() {
};
}
}
}