Add SpringModule automatically to Injector

This commit is contained in:
Dave Syer
2014-04-17 08:04:27 -07:00
parent 97d5fa1d96
commit 81ac3999f9
6 changed files with 167 additions and 26 deletions

View File

@@ -18,6 +18,31 @@ Note that the `ApplicationContext` in this example might contain the
(`MyModule`), or if `Service` is a concrete class it could be neither,
but Guice creates an instance and wires it for us.
If the `ApplicationConfiguration` is annotated `@GuiceModule` then it
can filter the types of bean that are registered with the Guice
binder. Example:
```java
@Configuration
@GuiceModule(includeFilters=@Filter(pattern=.*\\.Service))
public class ApplicationConfiguration {
@Bean
public MyService service() {
...
}
}
```
In this case, only bean types (or interfaces) called "Service" will
match the include filter, and only those beans will be bound.
If there are multiple `@Beans` of the same type in the
`ApplicationContext` then the `SpringModule` will register them all,
and there will be a runtime exception if an `Injector` needs one. As
with normal Spring dependency resolution, you can add the `@Primary`
marker to a single bean to differentiate and hint to the `Injector`
which instance to use.
## Using existing Guice Modules in a Spring ApplicationContext
The main feature here is a Spring `@Configuration` annotation:
@@ -65,6 +90,39 @@ If there is a `@Bean` of type `Service` it will be returned from the
create it and autowire its dependencies for you. A side effect of this
is that a `BeanDefinition` *will* be created.
In the example above, if `ApplicationConfiguration` was annotated
`@EnableGuiceModules` then there is an `Injector` bean already waiting
to be used, including wiring it into the application itself if you
need it. So this works as well:
```java
@Configuration
@EnableGuiceModules
public class ApplicationConfiguration {
@Autowired
private Injector injector;
@Bean
public Foo foo() {
// Guice creates and does the wiring of Foo instead of Spring
return injector.getInstance(Foo.class);
}
}
```
In this example if the `Injector` has a binding for a `Provider` of
`Foo.class` then the `foo()` method is redundant - it is already
resolvable as a Spring dependency. But if Guice is being used as a
factory for new objects that it doesn't have bindings for, then it
makes sense.
**Note:** if you also have `@GuiceModule` in your context, then using
the injector to create a `@Bean` directly is a bad idea (there's a
dependency cycle). You *can* do it, and break the cycle, if you
exclude the `@Bean` type from the `Injector` bindings using the
`@GuiceModule` exclude filters.
## Limitations
* So far there is no support for the Guice SPI methods in
@@ -80,6 +138,6 @@ is that a `BeanDefinition` *will* be created.
* `SpringModule` treats all beans as singletons.
* `SpringModule` binds all interfaces of a bean it can find. This will
cause issues sooner rather than later (e.g. when 2 beans implement
the same interface).
* `SpringModule` binds all interfaces of a bean it can find. This
should work out OK, as long as those interfaces are not needed for
injection (and if there is no `@Primary` bean).

View File

@@ -39,7 +39,7 @@ import org.springframework.context.annotation.Import;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(GuiceModuleMetadataRegistrar.class)
@Import(GuiceModuleRegistrar.class)
public @interface GuiceModule {
/**

View File

@@ -48,7 +48,7 @@ import org.springframework.util.Assert;
* @author Dave Syer
*
*/
public class GuiceModuleMetadataRegistrar implements ImportBeanDefinitionRegistrar,
public class GuiceModuleRegistrar implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware {
private ResourceLoader resourceLoader = new DefaultResourceLoader();

View File

@@ -13,6 +13,7 @@
package org.springframework.guice;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
@@ -23,7 +24,7 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
@@ -41,7 +42,7 @@ import com.google.inject.Provider;
public class ModuleRegistryConfiguration implements BeanPostProcessor {
@Autowired
private ConfigurableListableBeanFactory beanFactory;
private DefaultListableBeanFactory beanFactory;
@Autowired(required=false)
private List<Module> modules = Collections.emptyList();
@@ -55,6 +56,8 @@ public class ModuleRegistryConfiguration implements BeanPostProcessor {
@PostConstruct
public void init() {
List<Module> modules = new ArrayList<Module>(this.modules);
modules.add(new SpringModule(beanFactory));
injector = Guice.createInjector(modules);
for (Entry<Key<?>, Binding<?>> entry : injector.getBindings().entrySet()) {
if (entry.getKey().getTypeLiteral().getRawType().equals(Injector.class)) {

View File

@@ -21,7 +21,7 @@ import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ClassUtils;
import com.google.inject.Binder;
@@ -41,12 +41,14 @@ public class SpringModule implements Module {
private Map<Class<?>, Provider<?>> bound = new HashMap<Class<?>, Provider<?>>();
public SpringModule(GenericApplicationContext context) {
this.beanFactory = (DefaultListableBeanFactory) context
.getAutowireCapableBeanFactory();
public SpringModule(ApplicationContext context) {
this((DefaultListableBeanFactory) context.getAutowireCapableBeanFactory());
}
public SpringModule(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
if (beanFactory.getBeanNamesForType(GuiceModuleMetadata.class).length > 0) {
this.matcher = new CompositeTypeMatcher(beanFactory.getBeansOfType(
GuiceModuleMetadata.class).values());
this.matcher = new CompositeTypeMatcher(beanFactory.getBeansOfType(GuiceModuleMetadata.class).values());
}
}
@@ -54,14 +56,12 @@ public class SpringModule implements Module {
public void configure(Binder binder) {
for (String name : beanFactory.getBeanDefinitionNames()) {
BeanDefinition definition = beanFactory.getBeanDefinition(name);
if (definition.isAutowireCandidate()
&& definition.getRole() == AbstractBeanDefinition.ROLE_APPLICATION) {
if (definition.isAutowireCandidate() && definition.getRole() == AbstractBeanDefinition.ROLE_APPLICATION) {
Class<?> type = beanFactory.getType(name);
@SuppressWarnings("unchecked")
final Class<Object> cls = (Class<Object>) type;
final String beanName = name;
Provider<Object> provider = new BeanFactoryProvider(beanFactory,
beanName, type);
Provider<Object> provider = new BeanFactoryProvider(beanFactory, beanName, type);
if (!cls.isInterface() && !ClassUtils.isCglibProxyClass(cls)) {
bindConditionally(binder, cls, provider);
}
@@ -74,8 +74,7 @@ public class SpringModule implements Module {
}
}
private void bindConditionally(Binder binder, Class<Object> type,
Provider<Object> provider) {
private void bindConditionally(Binder binder, Class<Object> type, Provider<Object> provider) {
if (bound.get(type) != null) {
// Only bind one provider for each type
return; // TODO: named beans
@@ -83,6 +82,9 @@ public class SpringModule implements Module {
if (!matcher.matches(type)) {
return;
}
if (type.getName().startsWith("com.google.inject")) {
return;
}
binder.bind(type).toProvider(provider);
bound.put(type, provider);
}
@@ -90,12 +92,14 @@ public class SpringModule implements Module {
private static class BeanFactoryProvider implements Provider<Object> {
private DefaultListableBeanFactory beanFactory;
private String name;
private Class<?> type;
private Object result;
public BeanFactoryProvider(DefaultListableBeanFactory beanFactory, String name,
Class<?> type) {
public BeanFactoryProvider(DefaultListableBeanFactory beanFactory, String name, Class<?> type) {
this.beanFactory = beanFactory;
this.name = name;
this.type = type;
@@ -104,11 +108,11 @@ public class SpringModule implements Module {
@Override
public Object get() {
if (result == null) {
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
beanFactory, type);
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, type);
if (names.length == 1) {
result = beanFactory.getBean(name, type);
} else {
}
else {
for (String name : names) {
if (beanFactory.getBeanDefinition(name).isPrimary()) {
result = beanFactory.getBean(name, type);
@@ -116,8 +120,7 @@ public class SpringModule implements Module {
}
}
if (result == null) {
throw new ProvisionException(
"No primary bean definition for type: " + type);
throw new ProvisionException("No primary bean definition for type: " + type);
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2013-2014 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;
import static org.junit.Assert.assertNotNull;
import javax.inject.Inject;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import com.google.inject.Injector;
/**
* @author Dave Syer
*
*/
public class EnableGuiceModulesTests {
@Test
public void test() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class);
assertNotNull(context.getBean(Foo.class));
context.close();
}
interface Service {
}
protected static class MyService implements Service {
}
public static class Foo {
@Inject
public Foo(Service service) {
}
}
@Configuration
@EnableGuiceModules
@GuiceModule(excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, value = Foo.class))
protected static class TestConfig {
@Autowired
private Injector injector;
@Bean
public Foo foo() {
return injector.getInstance(Foo.class);
}
@Bean
public Service service() {
return new MyService();
}
}
}