Ensure bean post processors are applied before injector is created
In this way, Guice modules can declare bindings asEagerSingleton()
This commit is contained in:
committed by
Dave Syer
parent
ee35353c02
commit
bd30107666
@@ -15,6 +15,10 @@ package org.springframework.guice.annotation;
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
|
||||
/**
|
||||
* Convenience class used to map a Guice {@link Provider} to a Spring bean.
|
||||
@@ -22,17 +26,20 @@ import org.springframework.beans.factory.FactoryBean;
|
||||
* @author Dave Syer
|
||||
*/
|
||||
class GuiceFactoryBean<T> implements FactoryBean<T> {
|
||||
private final Provider<T> provider;
|
||||
private final Key<T> key;
|
||||
private final Class<T> beanType;
|
||||
|
||||
@Autowired
|
||||
private Injector injector;
|
||||
|
||||
public GuiceFactoryBean(Class<T> beanType, Provider<T> provider) {
|
||||
this.provider = provider;
|
||||
public GuiceFactoryBean(Class<T> beanType, Key<T> key) {
|
||||
this.beanType = beanType;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getObject() throws Exception {
|
||||
return (T) provider.get();
|
||||
return (T) injector.getInstance(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,50 +14,87 @@
|
||||
package org.springframework.guice.annotation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import com.google.inject.Binding;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Stage;
|
||||
import com.google.inject.name.Named;
|
||||
import com.google.inject.spi.Element;
|
||||
import com.google.inject.spi.ElementSource;
|
||||
import com.google.inject.spi.Elements;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.ConstructorArgumentValues;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.ApplicationContextException;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.guice.module.SpringModule;
|
||||
|
||||
import com.google.inject.Binding;
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.name.Named;
|
||||
import com.google.inject.spi.ElementSource;
|
||||
|
||||
/**
|
||||
* Configuration postprocessor that registers all the bindings in Guice modules as Spring
|
||||
* beans.
|
||||
*
|
||||
* @author Dave Syer
|
||||
* @author Talylor Wicksell
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 10)
|
||||
class ModuleRegistryConfiguration implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
class ModuleRegistryConfiguration implements BeanDefinitionRegistryPostProcessor,
|
||||
ApplicationContextAware, ApplicationListener<CreateInjectorSignalEvent> {
|
||||
|
||||
ApplicationContext applicationContext;
|
||||
private List<Module> modules;
|
||||
private ConfigurableListableBeanFactory beanFactory;
|
||||
|
||||
private Injector createInjector(Collection<Module> modules) {
|
||||
return Guice.createInjector(modules);
|
||||
private void createInjector(List<Module> modules,
|
||||
ConfigurableListableBeanFactory beanFactory) {
|
||||
Injector injector = null;
|
||||
try {
|
||||
Map<String, InjectorFactory> beansOfType = beanFactory
|
||||
.getBeansOfType(InjectorFactory.class);
|
||||
if (beansOfType.size() > 1) {
|
||||
throw new ApplicationContextException("Found multiple beans of type "
|
||||
+ InjectorFactory.class.getName()
|
||||
+ " Please ensure that only one InjectorFactory bean is defined. InjectorFactory beans found: "
|
||||
+ beansOfType.keySet());
|
||||
}
|
||||
else if (beansOfType.size() == 1) {
|
||||
InjectorFactory injectorFactory = beansOfType.values().iterator().next();
|
||||
injector = injectorFactory.createInjector(modules);
|
||||
}
|
||||
}
|
||||
catch (NoSuchBeanDefinitionException e) {
|
||||
|
||||
}
|
||||
if (injector == null) {
|
||||
injector = Guice.createInjector(modules);
|
||||
}
|
||||
beanFactory.registerResolvableDependency(Injector.class, injector);
|
||||
}
|
||||
|
||||
private void mapBindings(Injector injector, BeanDefinitionRegistry registry)
|
||||
{
|
||||
for (Entry<Key<?>, Binding<?>> entry : injector.getBindings().entrySet()) {
|
||||
if (entry.getKey().getTypeLiteral().getRawType().equals(Injector.class) ||
|
||||
"spring-guice".equals(entry.getValue().getSource().toString())) {
|
||||
private void mapBindings(Map<Key<?>, Binding<?>> bindings,
|
||||
BeanDefinitionRegistry registry) {
|
||||
for (Entry<Key<?>, Binding<?>> entry : bindings.entrySet()) {
|
||||
if (entry.getKey().getTypeLiteral().getRawType().equals(Injector.class)
|
||||
|| "spring-guice".equals(entry.getValue().getSource().toString())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -68,22 +105,19 @@ class ModuleRegistryConfiguration implements BeanDefinitionRegistryPostProcessor
|
||||
RootBeanDefinition bean = new RootBeanDefinition(GuiceFactoryBean.class);
|
||||
ConstructorArgumentValues args = new ConstructorArgumentValues();
|
||||
args.addIndexedArgumentValue(0, key.getTypeLiteral().getRawType());
|
||||
args.addIndexedArgumentValue(1, binding.getProvider());
|
||||
args.addIndexedArgumentValue(1, key);
|
||||
bean.setConstructorArgumentValues(args);
|
||||
if (source != null && source instanceof ElementSource) {
|
||||
bean.setResourceDescription(((ElementSource) source).getDeclaringSource().toString());
|
||||
} else {
|
||||
bean.setResourceDescription(
|
||||
((ElementSource) source).getDeclaringSource().toString());
|
||||
}
|
||||
else {
|
||||
bean.setResourceDescription("spring-guice");
|
||||
}
|
||||
bean.setAttribute("spring-guice", true);
|
||||
registry.registerBeanDefinition(extractName(key), bean);
|
||||
}
|
||||
|
||||
if(injector.getParent() != null)
|
||||
{
|
||||
mapBindings(injector.getParent(), registry);
|
||||
}
|
||||
|
||||
((ConfigurableListableBeanFactory) registry).registerResolvableDependency(Injector.class, injector);
|
||||
}
|
||||
|
||||
private String extractName(Key<?> key) {
|
||||
@@ -94,40 +128,52 @@ class ModuleRegistryConfiguration implements BeanDefinitionRegistryPostProcessor
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
|
||||
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
|
||||
throws BeansException {
|
||||
modules = new ArrayList<Module>(((ConfigurableListableBeanFactory) registry)
|
||||
.getBeansOfType(Module.class).values());
|
||||
|
||||
Map<Key<?>, Binding<?>> bindings = new HashMap<Key<?>, Binding<?>>();
|
||||
for (Element e : Elements.getElements(Stage.TOOL, modules)) {
|
||||
if (e instanceof Binding) {
|
||||
Binding<?> binding = (Binding<?>) e;
|
||||
bindings.put(binding.getKey(), binding);
|
||||
}
|
||||
}
|
||||
mapBindings(bindings, registry);
|
||||
modules.add(new SpringModule(this.applicationContext));
|
||||
// This event can be published now and it wont actually be processed until later
|
||||
// (during onRefresh()). There's no other way to get a hook into this phase of the
|
||||
// lifecycle.
|
||||
applicationContext.publishEvent(new CreateInjectorSignalEvent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
|
||||
List<Module> modules = new ArrayList<Module>(
|
||||
((DefaultListableBeanFactory) registry).getBeansOfType(Module.class).values());
|
||||
modules.add(new SpringModule(this.applicationContext));
|
||||
Injector injector = null;
|
||||
try {
|
||||
Map<String, InjectorFactory> beansOfType = ((DefaultListableBeanFactory) registry).getBeansOfType(InjectorFactory.class);
|
||||
if (beansOfType.size() > 1) {
|
||||
throw new ApplicationContextException("Found multiple beans of type " + InjectorFactory.class.getName()
|
||||
+ " Please ensure that only one InjectorFactory bean is defined. InjectorFactory beans found: "
|
||||
+ beansOfType.keySet());
|
||||
}
|
||||
else if(beansOfType.size() == 1) {
|
||||
InjectorFactory injectorFactory = beansOfType.values().iterator().next();
|
||||
injector = injectorFactory.createInjector(modules);
|
||||
}
|
||||
} catch (NoSuchBeanDefinitionException e) {
|
||||
|
||||
}
|
||||
if (injector == null) {
|
||||
injector = createInjector(modules);
|
||||
}
|
||||
mapBindings(injector, registry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.applicationContext=applicationContext;
|
||||
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
|
||||
throws BeansException {
|
||||
this.beanFactory = beanFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext)
|
||||
throws BeansException {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(CreateInjectorSignalEvent event) {
|
||||
createInjector(modules, beanFactory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Signaling event used to trigger injector creation after BeanPostProcessors have been
|
||||
* applied.
|
||||
*/
|
||||
class CreateInjectorSignalEvent extends ApplicationEvent {
|
||||
private static final long serialVersionUID = -6546970378679850504L;
|
||||
|
||||
public CreateInjectorSignalEvent() {
|
||||
super(serialVersionUID);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import com.google.inject.Binder;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.ProvisionException;
|
||||
import com.google.inject.Scopes;
|
||||
import com.google.inject.Stage;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.matcher.Matchers;
|
||||
@@ -95,6 +96,9 @@ public class SpringModule extends AbstractModule {
|
||||
private void bind(ConfigurableListableBeanFactory beanFactory) {
|
||||
for (String name : beanFactory.getBeanDefinitionNames()) {
|
||||
BeanDefinition definition = beanFactory.getBeanDefinition(name);
|
||||
if(definition.hasAttribute("spring-guice")){
|
||||
continue;
|
||||
}
|
||||
if (definition.isAutowireCandidate()
|
||||
&& definition.getRole() == AbstractBeanDefinition.ROLE_APPLICATION) {
|
||||
Class<?> type = beanFactory.getType(name);
|
||||
@@ -140,8 +144,10 @@ public class SpringModule extends AbstractModule {
|
||||
if (this.bound.get(type) == null) {
|
||||
// Only bind one provider for each type
|
||||
binder.withSource("spring-guice").bind(Key.get(type))
|
||||
.toProvider(typeProvider);
|
||||
this.bound.put(type, typeProvider);
|
||||
.toProvider(typeProvider).in(Scopes.SINGLETON);
|
||||
if(binder.currentStage() != Stage.TOOL) {
|
||||
this.bound.put(type, typeProvider);
|
||||
}
|
||||
}
|
||||
// But allow binding to named beans
|
||||
binder.withSource("spring-guice").bind(TypeLiteral.get(type))
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package org.springframework.guice;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.guice.BeanPostProcessorTest.GuiceBeanThatWantsPostProcessedBean;
|
||||
import org.springframework.guice.BeanPostProcessorTest.GuiceBeanThatWantsSpringBean;
|
||||
import org.springframework.guice.BeanPostProcessorTest.PostProcessedBean;
|
||||
import org.springframework.guice.BeanPostProcessorTest.SpringBeanThatWantsPostProcessedBean;
|
||||
import org.springframework.guice.annotation.EnableGuiceModules;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Module;
|
||||
|
||||
public class BeanPostProcessorTest {
|
||||
|
||||
/**
|
||||
* Verify BeanPostProcessor's such as Spring Boot's
|
||||
* ConfigurationPropertiesBindingPostProcessor are applied.
|
||||
*/
|
||||
@Test
|
||||
public void testBeanPostProcessorsApplied() {
|
||||
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanPostProcessorTestConfig.class);
|
||||
PostProcessedBean postProcessedBean = context.getBean(PostProcessedBean.class);
|
||||
assertTrue(postProcessedBean.postProcessed);
|
||||
GuiceBeanThatWantsPostProcessedBean guiceBean1 = context.getBean(GuiceBeanThatWantsPostProcessedBean.class);
|
||||
assertTrue(guiceBean1.ppb.postProcessed);
|
||||
GuiceBeanThatWantsSpringBean guiceBean2 = context.getBean(GuiceBeanThatWantsSpringBean.class);
|
||||
assertTrue(guiceBean2.springBean.ppb.postProcessed);
|
||||
context.close();
|
||||
}
|
||||
|
||||
public static class PostProcessedBean {
|
||||
Boolean postProcessed = false;
|
||||
}
|
||||
|
||||
public static class SpringBeanThatWantsPostProcessedBean {
|
||||
PostProcessedBean ppb;
|
||||
public SpringBeanThatWantsPostProcessedBean(PostProcessedBean ppb) {
|
||||
this.ppb = ppb;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GuiceBeanThatWantsPostProcessedBean {
|
||||
PostProcessedBean ppb;
|
||||
@Inject
|
||||
public GuiceBeanThatWantsPostProcessedBean(PostProcessedBean ppb) {
|
||||
this.ppb = ppb;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GuiceBeanThatWantsSpringBean {
|
||||
SpringBeanThatWantsPostProcessedBean springBean;
|
||||
@Inject
|
||||
public GuiceBeanThatWantsSpringBean(SpringBeanThatWantsPostProcessedBean springBean) {
|
||||
this.springBean = springBean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@EnableGuiceModules
|
||||
@Configuration
|
||||
class BeanPostProcessorTestConfig {
|
||||
@Bean
|
||||
public BeanPostProcessor postProcessor() {
|
||||
return new BeanPostProcessor() {
|
||||
@Override
|
||||
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
|
||||
if(bean instanceof PostProcessedBean) {
|
||||
((PostProcessedBean)bean).postProcessed = true;
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
|
||||
return bean;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PostProcessedBean postProcessedBean() {
|
||||
return new PostProcessedBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SpringBeanThatWantsPostProcessedBean springBean(PostProcessedBean ppb) {
|
||||
return new SpringBeanThatWantsPostProcessedBean(ppb);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Module someGuiceModule() {
|
||||
return new AbstractModule() {
|
||||
|
||||
@Override
|
||||
protected void configure() {
|
||||
binder().requireExplicitBindings();
|
||||
bind(GuiceBeanThatWantsPostProcessedBean.class).asEagerSingleton();
|
||||
bind(GuiceBeanThatWantsSpringBean.class).asEagerSingleton();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
package org.springframework.guice.annotation;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Injector;
|
||||
@@ -68,10 +69,9 @@ public class EnableGuiceModulesTests {
|
||||
public static class Foo {
|
||||
|
||||
@Inject
|
||||
public Foo(Service service) {
|
||||
public Foo(@Named("service") Service service) {
|
||||
service.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
|
||||
Reference in New Issue
Block a user