Browse Source

Supplier registration support for annotated bean classes

Issue: SPR-14832
pull/1274/head
Juergen Hoeller 9 years ago
parent
commit
9005481a49
  1. 76
      spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java
  2. 91
      spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java
  3. 143
      spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java

76
spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java

@ -17,7 +17,9 @@ @@ -17,7 +17,9 @@
package org.springframework.context.annotation;
import java.lang.annotation.Annotation;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.annotation.AnnotatedGenericBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.AutowireCandidateQualifier;
@ -83,6 +85,7 @@ public class AnnotatedBeanDefinitionReader { @@ -83,6 +85,7 @@ public class AnnotatedBeanDefinitionReader {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
/**
* Return the BeanDefinitionRegistry that this scanner operates on.
*/
@ -117,28 +120,94 @@ public class AnnotatedBeanDefinitionReader { @@ -117,28 +120,94 @@ public class AnnotatedBeanDefinitionReader {
(scopeMetadataResolver != null ? scopeMetadataResolver : new AnnotationScopeMetadataResolver());
}
/**
* Register one or more annotated classes to be processed.
* <p>Calls to {@code register} are idempotent; adding the same
* annotated class more than once has no additional effect.
* @param annotatedClasses one or more annotated classes,
* e.g. {@link Configuration @Configuration} classes
*/
public void register(Class<?>... annotatedClasses) {
for (Class<?> annotatedClass : annotatedClasses) {
registerBean(annotatedClass);
}
}
/**
* Register a bean from the given bean class, deriving its metadata from
* class-declared annotations.
* @param annotatedClass the class of the bean
*/
@SuppressWarnings("unchecked")
public void registerBean(Class<?> annotatedClass) {
registerBean(annotatedClass, null, (Class<? extends Annotation>[]) null);
registerBean(annotatedClass, null, null, (Class<? extends Annotation>[]) null);
}
/**
* Register a bean from the given bean class, deriving its metadata from
* class-declared annotations.
* @param annotatedClass the class of the bean
* @param qualifiers specific qualifier annotations to consider,
* in addition to qualifiers at the bean class level
*/
@SuppressWarnings("unchecked")
public void registerBean(Class<?> annotatedClass, Class<? extends Annotation>... qualifiers) {
registerBean(annotatedClass, null, qualifiers);
registerBean(annotatedClass, null, null, qualifiers);
}
/**
* Register a bean from the given bean class, deriving its metadata from
* class-declared annotations.
* @param annotatedClass the class of the bean
* @param name an explicit name for the bean
* @param qualifiers specific qualifier annotations to consider,
* in addition to qualifiers at the bean class level
*/
@SuppressWarnings("unchecked")
public void registerBean(Class<?> annotatedClass, String name, Class<? extends Annotation>... qualifiers) {
registerBean(annotatedClass, null, name, qualifiers);
}
/**
* Register a bean from the given bean class, deriving its metadata from
* class-declared annotations, using the given supplier for obtaining a new
* instance (possibly declared as a lambda expression or method reference).
* @param annotatedClass the class of the bean
* @param instanceSupplier a callback for creating an instance of the bean
* (may be {@code null})
* @return the registered bean definition, or {@code null} if skipped due to
* a declared condition
* @since 5.0
*/
@SuppressWarnings("unchecked")
public <T> AnnotatedBeanDefinition registerBean(Class<T> annotatedClass, Supplier<T> instanceSupplier) {
return registerBean(annotatedClass, instanceSupplier, null, (Class<? extends Annotation>[]) null);
}
/**
* Register a bean from the given bean class, deriving its metadata from
* class-declared annotations.
* @param annotatedClass the class of the bean
* @param instanceSupplier a callback for creating an instance of the bean
* (may be {@code null})
* @param name an explicit name for the bean
* @param qualifiers specific qualifier annotations to consider, if any,
* in addition to qualifiers at the bean class level
* @return the registered bean definition, or {@code null} if skipped due to
* a declared condition
* @since 5.0
*/
@SuppressWarnings("unchecked")
public <T> AnnotatedBeanDefinition registerBean(Class<T> annotatedClass, Supplier<T> instanceSupplier,
String name, Class<? extends Annotation>... qualifiers) {
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
return null;
}
abd.setInstanceSupplier(instanceSupplier);
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
abd.setScope(scopeMetadata.getScopeName());
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));
@ -160,6 +229,7 @@ public class AnnotatedBeanDefinitionReader { @@ -160,6 +229,7 @@ public class AnnotatedBeanDefinitionReader {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
return abd;
}

91
spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2016 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.
@ -16,6 +16,9 @@ @@ -16,6 +16,9 @@
package org.springframework.context.annotation;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.GenericApplicationContext;
@ -135,6 +138,16 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex @@ -135,6 +138,16 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex
this.scanner.setScopeMetadataResolver(scopeMetadataResolver);
}
@Override
protected void prepareRefresh() {
this.scanner.clearCache();
super.prepareRefresh();
}
//---------------------------------------------------------------------
// Implementation of AnnotationConfigRegistry
//---------------------------------------------------------------------
/**
* Register one or more annotated classes to be processed.
@ -164,10 +177,78 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex @@ -164,10 +177,78 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex
}
@Override
protected void prepareRefresh() {
this.scanner.clearCache();
super.prepareRefresh();
//---------------------------------------------------------------------
// Convenient methods for registering individual beans
//---------------------------------------------------------------------
/**
* Register a bean from the given bean class, deriving its metadata from
* class-declared annotations, using the given supplier for obtaining a new
* instance (possibly declared as a lambda expression or method reference).
* <p>The bean name will be generated according to annotated component rules.
* @param annotatedClass the class of the bean
* @param instanceSupplier a callback for creating an instance of the bean
* @since 5.0
* @see #setBeanNameGenerator
*/
@SuppressWarnings("unchecked")
public <T> void registerBean(Class<T> annotatedClass, Supplier<T> instanceSupplier) {
registerBean(null, annotatedClass, instanceSupplier);
}
/**
* Register a bean from the given bean class, deriving its metadata from
* class-declared annotations, using the given supplier for obtaining a new
* instance (possibly declared as a lambda expression or method reference).
* @param beanName the name of the bean (may be {@code null})
* @param annotatedClass the class of the bean
* @param instanceSupplier a callback for creating an instance of the bean
* @since 5.0
*/
@SuppressWarnings("unchecked")
public <T> void registerBean(String beanName, Class<T> annotatedClass, Supplier<T> instanceSupplier) {
Assert.notNull(instanceSupplier, "Supplier must not be null");
this.reader.registerBean(annotatedClass, instanceSupplier, beanName);
}
/**
* Register a bean from the given bean class, deriving its metadata from
* class-declared annotations, and optionally providing explicit constructor
* arguments for consideration in the autowiring process.
* <p>The bean name will be generated according to annotated component rules.
* @param annotatedClass the class of the bean
* @param constructorArguments argument values to be fed into Spring's
* constructor resolution algorithm, resolving either all arguments or just
* specific ones, with the rest to be resolved through regular autowiring
* (may be {@code null} or empty)
* @since 5.0
* @see #setBeanNameGenerator
*/
@SuppressWarnings("unchecked")
public <T> void registerBean(Class<T> annotatedClass, Object... constructorArguments) {
registerBean(null, annotatedClass, constructorArguments);
}
/**
* Register a bean from the given bean class, deriving its metadata from
* class-declared annotations, and optionally providing explicit constructor
* arguments for consideration in the autowiring process.
* @param beanName the name of the bean (may be {@code null})
* @param annotatedClass the class of the bean
* @param constructorArguments argument values to be fed into Spring's
* constructor resolution algorithm, resolving either all arguments or just
* specific ones, with the rest to be resolved through regular autowiring
* (may be {@code null} or empty)
* @since 5.0
*/
@SuppressWarnings("unchecked")
public <T> void registerBean(String beanName, Class<T> annotatedClass, Object... constructorArguments) {
AnnotatedBeanDefinition abd = this.reader.registerBean(annotatedClass, null, beanName);
if (constructorArguments != null) {
for (Object arg : constructorArguments) {
abd.getConstructorArgumentValues().addGenericArgumentValue(arg);
}
}
}
}

143
spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 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.
@ -86,7 +86,7 @@ public class AnnotationConfigApplicationContextTests { @@ -86,7 +86,7 @@ public class AnnotationConfigApplicationContextTests {
public void getBeanByType() {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
TestBean testBean = context.getBean(TestBean.class);
assertNotNull("getBean() should not return null", testBean);
assertNotNull(testBean);
assertThat(testBean.name, equalTo("foo"));
}
@ -116,6 +116,102 @@ public class AnnotationConfigApplicationContextTests { @@ -116,6 +116,102 @@ public class AnnotationConfigApplicationContextTests {
assertNotNull(configObject);
}
@Test
public void individualBeans() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(BeanA.class, BeanB.class, BeanC.class);
context.refresh();
assertSame(context.getBean(BeanB.class), context.getBean(BeanA.class).b);
assertSame(context.getBean(BeanC.class), context.getBean(BeanA.class).c);
assertSame(context, context.getBean(BeanB.class).applicationContext);
}
@Test
public void individualNamedBeans() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean("a", BeanA.class);
context.registerBean("b", BeanB.class);
context.registerBean("c", BeanC.class);
context.refresh();
assertSame(context.getBean("b"), context.getBean("a", BeanA.class).b);
assertSame(context.getBean("c"), context.getBean("a", BeanA.class).c);
assertSame(context, context.getBean("b", BeanB.class).applicationContext);
}
@Test
public void individualBeanWithSupplier() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean(BeanA.class,
() -> new BeanA(context.getBean(BeanB.class), context.getBean(BeanC.class)));
context.registerBean(BeanB.class, BeanB::new);
context.registerBean(BeanC.class, BeanC::new);
context.refresh();
assertSame(context.getBean(BeanB.class), context.getBean(BeanA.class).b);
assertSame(context.getBean(BeanC.class), context.getBean(BeanA.class).c);
assertSame(context, context.getBean(BeanB.class).applicationContext);
}
@Test
public void individualNamedBeanWithSupplier() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.registerBean("a", BeanA.class,
() -> new BeanA(context.getBean(BeanB.class), context.getBean(BeanC.class)));
context.registerBean("b", BeanB.class, BeanB::new);
context.registerBean("c", BeanC.class, BeanC::new);
context.refresh();
assertSame(context.getBean("b", BeanB.class), context.getBean(BeanA.class).b);
assertSame(context.getBean("c"), context.getBean("a", BeanA.class).c);
assertSame(context, context.getBean("b", BeanB.class).applicationContext);
}
@Test
public void individualBeanWithSpecifiedConstructorArguments() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
BeanB b = new BeanB();
BeanC c = new BeanC();
context.registerBean(BeanA.class, b, c);
context.refresh();
assertSame(b, context.getBean(BeanA.class).b);
assertSame(c, context.getBean(BeanA.class).c);
assertNull(b.applicationContext);
}
@Test
public void individualNamedBeanWithSpecifiedConstructorArguments() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
BeanB b = new BeanB();
BeanC c = new BeanC();
context.registerBean("a", BeanA.class, b, c);
context.refresh();
assertSame(b, context.getBean("a", BeanA.class).b);
assertSame(c, context.getBean("a", BeanA.class).c);
assertNull(b.applicationContext);
}
@Test
public void individualBeanWithMixedConstructorArguments() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
BeanC c = new BeanC();
context.registerBean(BeanA.class, c);
context.registerBean(BeanB.class);
context.refresh();
assertSame(context.getBean(BeanB.class), context.getBean(BeanA.class).b);
assertSame(c, context.getBean(BeanA.class).c);
assertSame(context, context.getBean(BeanB.class).applicationContext);
}
@Test
public void individualNamedBeanWithMixedConstructorArguments() {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
BeanC c = new BeanC();
context.registerBean("a", BeanA.class, c);
context.registerBean("b", BeanB.class);
context.refresh();
assertSame(context.getBean("b", BeanB.class), context.getBean("a", BeanA.class).b);
assertSame(c, context.getBean("a", BeanA.class).c);
assertSame(context, context.getBean("b", BeanB.class).applicationContext);
}
@Test
public void getBeanByTypeRaisesNoSuchBeanDefinitionException() {
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
@ -123,12 +219,11 @@ public class AnnotationConfigApplicationContextTests { @@ -123,12 +219,11 @@ public class AnnotationConfigApplicationContextTests {
// attempt to retrieve a bean that does not exist
Class<?> targetType = Pattern.class;
try {
Object bean = context.getBean(targetType);
fail("should have thrown NoSuchBeanDefinitionException, instead got: " + bean);
context.getBean(targetType);
fail("Should have thrown NoSuchBeanDefinitionException");
}
catch (NoSuchBeanDefinitionException ex) {
assertThat(ex.getMessage(), containsString(
format("No qualifying bean of type '%s'", targetType.getName())));
assertThat(ex.getMessage(), containsString(format("No qualifying bean of type '%s'", targetType.getName())));
}
}
@ -188,6 +283,7 @@ public class AnnotationConfigApplicationContextTests { @@ -188,6 +283,7 @@ public class AnnotationConfigApplicationContextTests {
@Configuration
static class Config {
@Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
@ -198,6 +294,7 @@ public class AnnotationConfigApplicationContextTests { @@ -198,6 +294,7 @@ public class AnnotationConfigApplicationContextTests {
@Configuration("customConfigBeanName")
static class ConfigWithCustomName {
@Bean
public TestBean testBean() {
return new TestBean();
@ -205,6 +302,7 @@ public class AnnotationConfigApplicationContextTests { @@ -205,6 +302,7 @@ public class AnnotationConfigApplicationContextTests {
}
static class ConfigMissingAnnotation {
@Bean
public TestBean testBean() {
return new TestBean();
@ -213,18 +311,26 @@ public class AnnotationConfigApplicationContextTests { @@ -213,18 +311,26 @@ public class AnnotationConfigApplicationContextTests {
@Configuration
static class TwoTestBeanConfig {
@Bean TestBean tb1() { return new TestBean(); }
@Bean TestBean tb2() { return new TestBean(); }
@Bean TestBean tb1() {
return new TestBean();
}
@Bean TestBean tb2() {
return new TestBean();
}
}
@Configuration
static class NameConfig {
@Bean String name() { return "foo"; }
}
@Configuration
@Import(NameConfig.class)
static class AutowiredConfig {
@Autowired String autowiredName;
@Bean TestBean testBean() {
@ -234,6 +340,27 @@ public class AnnotationConfigApplicationContextTests { @@ -234,6 +340,27 @@ public class AnnotationConfigApplicationContextTests {
}
}
static class BeanA {
BeanB b;
BeanC c;
@Autowired public BeanA(BeanB b, BeanC c) {
this.b = b;
this.c = c;
}
}
static class BeanB {
@Autowired ApplicationContext applicationContext;
public BeanB() {
}
}
static class BeanC {}
static class UntypedFactoryBean implements FactoryBean<Object> {
@Override

Loading…
Cancel
Save