Merge pull request #88 from asibross/patch/injector-creation-timing

Delay injector creation until after registerBeanPostProcessors() phase
This commit is contained in:
Stephane Maldini
2021-11-18 14:36:25 -08:00
committed by GitHub
5 changed files with 183 additions and 102 deletions

View File

@@ -41,6 +41,9 @@ 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.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.ResolvableType;
@@ -70,8 +73,7 @@ import java.util.stream.Collectors;
*/
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
class ModuleRegistryConfiguration
implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
class ModuleRegistryConfiguration implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
private static final String SPRING_GUICE_DEDUPE_BINDINGS_PROPERTY_NAME = "spring.guice.dedup";
private static final String SPRING_GUICE_AUTOWIRE_JIT_PROPERTY_NAME = "spring.guice.autowireJIT";
@@ -80,39 +82,59 @@ class ModuleRegistryConfiguration
private final Log logger = LogFactory.getLog(getClass());
private ApplicationContext applicationContext;
private List<Module> modules;
private AtomicBoolean injectorCreated = new AtomicBoolean(false);
private boolean enableJustInTimeBinding = true;
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);
beanFactory.registerSingleton("injector", injector);
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
this.enableJustInTimeBinding = applicationContext.getEnvironment()
.getProperty(SPRING_GUICE_AUTOWIRE_JIT_PROPERTY_NAME, Boolean.class, true);
}
private void mapBindings(Map<Key<?>, Binding<?>> bindings,
BeanDefinitionRegistry registry) {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
List<Module> modules = new ArrayList<>(((ConfigurableListableBeanFactory) registry)
.getBeansOfType(Module.class).values());
modules.add(new SpringModule((ConfigurableListableBeanFactory) registry, enableJustInTimeBinding));
Map<Key<?>, Binding<?>> bindings = new HashMap<Key<?>, Binding<?>>();
List<Element> elements = Elements.getElements(Stage.TOOL, modules);
if (applicationContext.getEnvironment().getProperty(
SPRING_GUICE_DEDUPE_BINDINGS_PROPERTY_NAME, Boolean.class, false)) {
elements = removeDuplicates(elements);
modules = Collections.singletonList(Elements.getModule(elements));
}
if (applicationContext.getEnvironment().containsProperty("spring.guice.modules.exclude")) {
String[] modulesToFilter = applicationContext.getEnvironment()
.getProperty("spring.guice.modules.exclude", "").split(",");
elements = elements.stream().filter(e -> elementFilter(modulesToFilter, e)).collect(Collectors.toList());
modules = Collections.singletonList(Elements.getModule(elements));
}
for (Element e : elements) {
if (e instanceof Binding) {
Binding<?> binding = (Binding<?>) e;
bindings.put(binding.getKey(), binding);
}
else if (e instanceof PrivateElements) {
extractPrivateElements(bindings, (PrivateElements) e);
}
}
mapBindings(bindings, registry);
// Register the injector initializer
RootBeanDefinition beanDefinition = new RootBeanDefinition(GuiceInjectorInitializer.class);
ConstructorArgumentValues args = new ConstructorArgumentValues();
args.addIndexedArgumentValue(0, modules);
args.addIndexedArgumentValue(1, applicationContext);
beanDefinition.setConstructorArgumentValues(args);
registry.registerBeanDefinition("guiceInjectorInitializer", beanDefinition);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) {
}
private void mapBindings(Map<Key<?>, Binding<?>> bindings, BeanDefinitionRegistry registry) {
Stage stage = applicationContext.getEnvironment().getProperty(SPRING_GUICE_STAGE_PROPERTY_NAME, Stage.class, Stage.PRODUCTION);
boolean ifLazyInit = stage.equals(Stage.DEVELOPMENT);
for (Entry<Key<?>, Binding<?>> entry : bindings.entrySet()) {
@@ -188,37 +210,6 @@ class ModuleRegistryConfiguration
}
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)
throws BeansException {
modules = new ArrayList<Module>(((ConfigurableListableBeanFactory) registry)
.getBeansOfType(Module.class).values());
modules.add(new SpringModule((ConfigurableListableBeanFactory) registry, enableJustInTimeBinding));
Map<Key<?>, Binding<?>> bindings = new HashMap<Key<?>, Binding<?>>();
List<Element> elements = Elements.getElements(Stage.TOOL, modules);
if (applicationContext.getEnvironment().getProperty(
SPRING_GUICE_DEDUPE_BINDINGS_PROPERTY_NAME, Boolean.class, false)) {
elements = removeDuplicates(elements);
modules = Collections.singletonList(Elements.getModule(elements));
}
if (applicationContext.getEnvironment().containsProperty("spring.guice.modules.exclude")) {
String[] modulesToFilter = applicationContext.getEnvironment()
.getProperty("spring.guice.modules.exclude", "").split(",");
elements = elements.stream().filter(e -> elementFilter(modulesToFilter, e)).collect(Collectors.toList());
modules = Collections.singletonList(Elements.getModule(elements));
}
for (Element e : elements) {
if (e instanceof Binding) {
Binding<?> binding = (Binding<?>) e;
bindings.put(binding.getKey(), binding);
}
else if (e instanceof PrivateElements) {
extractPrivateElements(bindings, (PrivateElements) e);
}
}
mapBindings(bindings, registry);
}
private boolean elementFilter(String[] modulesToFilter, Element element){
try {
return Arrays.stream(modulesToFilter)
@@ -298,42 +289,81 @@ class ModuleRegistryConfiguration
}
}
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
beanFactory.registerSingleton("guiceInjectorInitializer",
new GuiceInjectorInitializingBeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean,
String beanName) throws BeansException {
return bean;
}
/**
* Creates the Guice injector and registers it.
*
* The correct time to create the injector is after all Bean Post Processors were registered (after the
* registerBeanPostProcessors() phase), but before other beans get resolved. To achieve this, we create the injector
* when the first bean gets resolved - in its post-processing phase. However, this creates a possibility for a circular
* initialization error (i.e. if the first bean is also being dependant on by a Guice provided binding). To resolve
* this we publish an event that will be triggered in the registerListeners() phase, and create the injector then.
* Combining both initialization mechanisms (post-processor and the event publishing) ensures the injector will be
* created no later then the registerListeners() phase, but after the registerBeanPostProcessors() phase.
* For application contexts that override onRefresh() and create beans then (i.e. WebServer based application contexts)
* the post-processor initialization will kick-in and create the injector before.
*/
class GuiceInjectorInitializer implements BeanPostProcessor, ApplicationListener<GuiceInjectorInitializer.CreateInjectorEvent> {
private final AtomicBoolean injectorCreated = new AtomicBoolean(false);
private final List<Module> modules;
private final ConfigurableApplicationContext applicationContext;
@Override
public Object postProcessAfterInitialization(Object bean,
String beanName) throws BeansException {
if (injectorCreated.compareAndSet(false, true)) {
createInjector(modules, beanFactory);
}
return bean;
}
});
}
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
public GuiceInjectorInitializer(List<Module> modules,
ConfigurableApplicationContext applicationContext) {
this.modules = modules;
this.applicationContext = applicationContext;
this.enableJustInTimeBinding = applicationContext.getEnvironment()
.getProperty(SPRING_GUICE_AUTOWIRE_JIT_PROPERTY_NAME, Boolean.class, true);
applicationContext.publishEvent(new CreateInjectorEvent());
}
private static class GuiceInjectorInitializingBeanPostProcessor
implements BeanPostProcessor, Ordered {
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (injectorCreated.compareAndSet(false, true)) {
createInjector();
}
return bean;
}
@Override
public void onApplicationEvent(CreateInjectorEvent event) {
if (injectorCreated.compareAndSet(false, true)) {
createInjector();
}
}
private void createInjector() {
Injector injector = null;
try {
Map<String, InjectorFactory> beansOfType = applicationContext.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);
}
applicationContext.getBeanFactory().registerResolvableDependency(Injector.class, injector);
applicationContext.getBeanFactory().registerSingleton("injector", injector);
}
static class CreateInjectorEvent extends ApplicationEvent {
private static final long serialVersionUID = -6546970378679850504L;
public CreateInjectorEvent() {
super(serialVersionUID);
}
}
}

View File

@@ -87,7 +87,7 @@ class BeanPostProcessorTestConfig {
}
public static class TestBeanPostProcessor implements BeanPostProcessor, Ordered {
public static class TestBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if(bean instanceof PostProcessedBean) {
@@ -100,11 +100,6 @@ class BeanPostProcessorTestConfig {
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public int getOrder() {
return 0;
}
}
@Bean

View File

@@ -1,12 +1,12 @@
package org.springframework.guice;
import com.google.inject.AbstractModule;
import com.google.inject.CreationException;
import com.google.inject.Module;
import com.google.inject.multibindings.OptionalBinder;
import org.junit.AfterClass;
import org.junit.Test;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -35,7 +35,7 @@ public class BindingDeduplicationTests {
context.close();
}
@Test(expected = BeanCreationException.class)
@Test(expected = CreationException.class)
public void verifyDuplicateBindingErrorWhenDedupeNotEnabled() {
System.setProperty("spring.guice.dedup", "false");
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(

View File

@@ -4,6 +4,7 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -31,7 +32,7 @@ public class InjectorFactoryTests {
context.close();
}
@Test(expected = BeanCreationException.class)
@Test(expected = ApplicationContextException.class)
public void testMultipleInjectorFactoriesThrowsApplicationContextException() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(InjectorFactoryConfig.class,
SecondInjectorFactoryConfig.class, ModulesConfig.class);

View File

@@ -19,6 +19,8 @@ import javax.inject.Named;
import com.google.inject.AbstractModule;
import com.google.inject.Injector;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import org.junit.After;
import org.junit.Test;
@@ -75,6 +77,13 @@ public class EnableGuiceModulesTests {
context.close();
}
@Test
public void testInjectorCreationDoesNotCauseCircularDependencyError() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
assertNotNull(context.getBean(SpringProvidedBean.class));
context.close();
}
interface Service {
}
@@ -150,4 +159,50 @@ public class EnableGuiceModulesTests {
}
public static class SpringProvidedBean {
public SpringProvidedBean(GuiceProvidedBean guiceProvidedBean) {
}
}
public static class GuiceProvidedBean {
}
public static class GuiceService {
@Inject
public GuiceService(SpringProvidedBean springProvidedBean) {
}
}
public static class MyGuiceModule extends AbstractModule {
@Override
protected void configure() {
bind(GuiceService.class).asEagerSingleton();
}
@Provides
@Singleton
public GuiceProvidedBean guiceProvidedBean() {
return new GuiceProvidedBean();
}
}
@Configuration
@EnableGuiceModules
public static class MySpringConfig {
@Bean
public SpringProvidedBean baz(GuiceProvidedBean guiceProvidedBean) {
return new SpringProvidedBean(guiceProvidedBean);
}
@Bean
public MyGuiceModule bazModule() {
return new MyGuiceModule();
}
}
}