Add SpringModule automatically to Injector
This commit is contained in:
64
README.md
64
README.md
@@ -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).
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user