support Guice binding annotations in SpringModule

This commit is contained in:
Taylor Wicksell
2018-03-26 16:08:17 -07:00
committed by Taylor Wicksell
parent 1680610faf
commit b2a767e51a
2 changed files with 195 additions and 5 deletions

View File

@@ -13,13 +13,17 @@
package org.springframework.guice.module;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.inject.Provider;
@@ -30,17 +34,23 @@ import com.google.inject.Key;
import com.google.inject.ProvisionException;
import com.google.inject.Stage;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.Annotations;
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.annotation.AnnotatedBeanDefinition;
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.core.annotation.AnnotationUtils;
import org.springframework.core.type.MethodMetadata;
import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
/**
* @author Dave Syer
@@ -95,9 +105,11 @@ 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_SOURCE)) {
continue;
}
Optional<Annotation> bindingAnnotation = getAnnotationForBeanDefinition(definition);
if (definition.isAutowireCandidate()
&& definition.getRole() == AbstractBeanDefinition.ROLE_APPLICATION) {
Class<?> type = beanFactory.getType(name);
@@ -109,21 +121,84 @@ public class SpringModule extends AbstractModule {
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, bindingAnnotation);
}
for (Class<?> iface : ClassUtils.getAllInterfacesForClass(type)) {
bindConditionally(binder(), name, iface, typeProvider, namedProvider);
bindConditionally(binder(), name, iface, typeProvider, namedProvider, bindingAnnotation);
}
for (Type iface : type.getGenericInterfaces()) {
bindConditionally(binder(), name, iface, typeProvider, namedProvider);
bindConditionally(binder(), name, iface, typeProvider, namedProvider, bindingAnnotation);
}
}
}
}
private Optional<Annotation> getAnnotationForBeanDefinition(BeanDefinition definition) {
if (definition instanceof AnnotatedBeanDefinition
&& ((AnnotatedBeanDefinition) definition).getFactoryMethodMetadata() != null) {
try {
Method factoryMethod = getFactoryMethod(beanFactory, definition);
return Arrays.stream(AnnotationUtils.getAnnotations(factoryMethod))
.filter(a -> Annotations.isBindingAnnotation(a.annotationType())).findFirst();
} catch (Exception e) {
return Optional.empty();
}
} else {
return Optional.empty();
}
}
private Method getFactoryMethod(ConfigurableListableBeanFactory beanFactory,
BeanDefinition definition) throws Exception {
if (definition instanceof AnnotatedBeanDefinition) {
MethodMetadata factoryMethodMetadata = ((AnnotatedBeanDefinition) definition)
.getFactoryMethodMetadata();
if (factoryMethodMetadata instanceof StandardMethodMetadata) {
return ((StandardMethodMetadata) factoryMethodMetadata)
.getIntrospectedMethod();
}
}
BeanDefinition factoryDefinition = beanFactory
.getBeanDefinition(definition.getFactoryBeanName());
Class<?> factoryClass = ClassUtils.forName(factoryDefinition.getBeanClassName(),
beanFactory.getBeanClassLoader());
return getFactoryMethod(definition, factoryClass);
}
private Method getFactoryMethod(BeanDefinition definition, Class<?> factoryClass) {
Method uniqueMethod = null;
for (Method candidate : getCandidateFactoryMethods(definition, factoryClass)) {
if (candidate.getName().equals(definition.getFactoryMethodName())) {
if (uniqueMethod == null) {
uniqueMethod = candidate;
}
else if (!hasMatchingParameterTypes(candidate, uniqueMethod)) {
return null;
}
}
}
return uniqueMethod;
}
private Method[] getCandidateFactoryMethods(BeanDefinition definition,
Class<?> factoryClass) {
return shouldConsiderNonPublicMethods(definition)
? ReflectionUtils.getAllDeclaredMethods(factoryClass)
: factoryClass.getMethods();
}
private boolean shouldConsiderNonPublicMethods(BeanDefinition definition) {
return (definition instanceof AbstractBeanDefinition)
&& ((AbstractBeanDefinition) definition).isNonPublicAccessAllowed();
}
private boolean hasMatchingParameterTypes(Method candidate, Method current) {
return Arrays.equals(candidate.getParameterTypes(), current.getParameterTypes());
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void bindConditionally(Binder binder, String name, Type type,
Provider typeProvider, Provider namedProvider) {
Provider typeProvider, Provider namedProvider, Optional<Annotation> bindingAnnotation) {
if (!this.matcher.matches(name, type)) {
return;
}
@@ -141,7 +216,8 @@ public class SpringModule extends AbstractModule {
StageTypeKey stageTypeKey = new StageTypeKey(binder.currentStage(), type);
if (this.bound.get(stageTypeKey) == null) {
// Only bind one provider for each type
binder.withSource(SPRING_GUICE_SOURCE).bind(Key.get(type))
Key<Object> key = bindingAnnotation.map(a ->(Key<Object>)Key.get(type, a)).orElse((Key<Object>)Key.get(type));
binder.withSource(SPRING_GUICE_SOURCE).bind(key)
.toProvider(typeProvider);
this.bound.put(stageTypeKey, typeProvider);
}

View File

@@ -0,0 +1,114 @@
package org.springframework.guice;
import static org.junit.Assert.assertNotNull;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.inject.Named;
import javax.inject.Qualifier;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.guice.BindingAnnotationTests.SomeDependencyWithBindingAnnotationOnProvider;
import org.springframework.guice.BindingAnnotationTests.SomeDependencyWithGuiceNamedAnnotationOnProvider;
import org.springframework.guice.BindingAnnotationTests.SomeDependencyWithNamedAnnotationOnProvider;
import org.springframework.guice.BindingAnnotationTests.SomeDependencyWithQualifierOnProvider;
import org.springframework.guice.BindingAnnotationTests.SomeDependencyWithQualifierOnProviderWhichImplementsSomeInterface;
import org.springframework.guice.BindingAnnotationTests.SomeInterface;
import org.springframework.guice.annotation.EnableGuiceModules;
import com.google.inject.BindingAnnotation;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.name.Names;
public class BindingAnnotationTests {
@Test
public void verifyBindingAnnotationsAreRespected() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
BindingAnnotationTestsConfig.class);
Injector injector = context.getBean(Injector.class);
//Check @Qualifier
SomeDependencyWithQualifierOnProvider someDependencyWithQualifierOnClass = injector.getInstance(Key.get(SomeDependencyWithQualifierOnProvider.class, SomeQualifierAnnotation.class));
assertNotNull(someDependencyWithQualifierOnClass);
//Check @BindingAnnotaiton
SomeDependencyWithQualifierOnProvider someDependencyWithBindingAnnotationOnProvider = injector.getInstance(Key.get(SomeDependencyWithQualifierOnProvider.class, SomeQualifierAnnotation.class));
assertNotNull(someDependencyWithBindingAnnotationOnProvider);
//Check javax @Named
SomeDependencyWithNamedAnnotationOnProvider someDependencyWithNamedAnnotationOnProvider = injector.getInstance(Key.get(SomeDependencyWithNamedAnnotationOnProvider.class, Names.named("javaxNamed")));
assertNotNull(someDependencyWithNamedAnnotationOnProvider);
//Check Guice @Named
SomeDependencyWithGuiceNamedAnnotationOnProvider someDependencyWithGuiceNamedAnnotationOnProvider = injector.getInstance(Key.get(SomeDependencyWithGuiceNamedAnnotationOnProvider.class, Names.named("guiceNamed")));
assertNotNull(someDependencyWithGuiceNamedAnnotationOnProvider);
//Check @Qualifier with Interface
SomeInterface someInterface = injector.getInstance(Key.get(SomeInterface.class, SomeQualifierAnnotation.class));
assertNotNull(someInterface);
context.close();
}
public static class SomeDependencyWithQualifierOnProvider {}
public static class SomeDependencyWithBindingAnnotationOnProvider {}
public static class SomeDependencyWithNamedAnnotationOnProvider {}
public static class SomeDependencyWithGuiceNamedAnnotationOnProvider {}
public static interface SomeInterface{}
public static class SomeDependencyWithQualifierOnProviderWhichImplementsSomeInterface implements SomeInterface {}
}
@Qualifier
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface SomeQualifierAnnotation {}
@BindingAnnotation
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface SomeBindingAnnotation {}
@EnableGuiceModules
@Configuration
class BindingAnnotationTestsConfig {
@Bean
@SomeQualifierAnnotation
public SomeDependencyWithQualifierOnProvider someDependencyWithQualifierOnProvider() {
return new SomeDependencyWithQualifierOnProvider();
}
@Bean
@SomeBindingAnnotation
public SomeDependencyWithBindingAnnotationOnProvider someDependencyWithBindingAnnotationOnProvider() {
return new SomeDependencyWithBindingAnnotationOnProvider();
}
@Bean
@Named("javaxNamed")
public SomeDependencyWithNamedAnnotationOnProvider someDependencyWithNamedAnnotationOnProvider() {
return new SomeDependencyWithNamedAnnotationOnProvider();
}
@Bean
@com.google.inject.name.Named("guiceNamed")
public SomeDependencyWithGuiceNamedAnnotationOnProvider someDependencyWithGuiceNamedAnnotationOnProvider() {
return new SomeDependencyWithGuiceNamedAnnotationOnProvider();
}
@Bean
@SomeQualifierAnnotation
public SomeInterface someInterface() {
return new SomeDependencyWithQualifierOnProviderWhichImplementsSomeInterface();
}
}