diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java index 4c16bb0e64c..2501ed0dfa7 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotatedBeanDefinitionReader.java @@ -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 { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } + /** * Return the BeanDefinitionRegistry that this scanner operates on. */ @@ -117,28 +120,94 @@ public class AnnotatedBeanDefinitionReader { (scopeMetadataResolver != null ? scopeMetadataResolver : new AnnotationScopeMetadataResolver()); } + + /** + * Register one or more annotated classes to be processed. + *

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[]) null); + registerBean(annotatedClass, null, null, (Class[]) 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... 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... 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 AnnotatedBeanDefinition registerBean(Class annotatedClass, Supplier instanceSupplier) { + return registerBean(annotatedClass, instanceSupplier, null, (Class[]) 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 AnnotatedBeanDefinition registerBean(Class annotatedClass, Supplier instanceSupplier, + String name, Class... 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 { BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName); definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry); BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry); + return abd; } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java index 98ab303e294..3e553e257d6 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigApplicationContext.java @@ -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 @@ 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 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 } - @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). + *

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 void registerBean(Class annotatedClass, Supplier 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 void registerBean(String beanName, Class annotatedClass, Supplier 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. + *

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 void registerBean(Class 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 void registerBean(String beanName, Class annotatedClass, Object... constructorArguments) { + AnnotatedBeanDefinition abd = this.reader.registerBean(annotatedClass, null, beanName); + if (constructorArguments != null) { + for (Object arg : constructorArguments) { + abd.getConstructorArgumentValues().addGenericArgumentValue(arg); + } + } } } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index f485eeb3d53..3d18a9b1532 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -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 { 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 { 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 { // 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 { @Configuration static class Config { + @Bean public TestBean testBean() { TestBean testBean = new TestBean(); @@ -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 { } static class ConfigMissingAnnotation { + @Bean public TestBean testBean() { return new TestBean(); @@ -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 { } } + 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 { @Override