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 35bfa112b90..0ddc3f5f064 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2019 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. @@ -197,6 +197,43 @@ public class AnnotatedBeanDefinitionReader { doRegisterBean(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}) + * @param qualifiers specific qualifier annotations to consider, + * in addition to qualifiers at the bean class level + * @since 5.2 + */ + @SuppressWarnings("unchecked") + public void registerBean(Class annotatedClass, @Nullable Supplier instanceSupplier, + Class... qualifiers) { + + doRegisterBean(annotatedClass, instanceSupplier, null, 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 name an explicit name for the bean + * @param instanceSupplier a callback for creating an instance of the bean + * (may be {@code null}) + * @param qualifiers specific qualifier annotations to consider, + * in addition to qualifiers at the bean class level + * @since 5.2 + */ + @SuppressWarnings("unchecked") + public void registerBean(Class annotatedClass, String name, @Nullable Supplier instanceSupplier, + Class... qualifiers) { + + doRegisterBean(annotatedClass, instanceSupplier, name, qualifiers); + } + /** * Register a bean from the given bean class, deriving its metadata from * class-declared annotations. 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 e101b07d426..de9cd66bd26 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-2018 the original author or authors. + * Copyright 2002-2019 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,7 @@ package org.springframework.context.annotation; +import java.lang.annotation.Annotation; import java.util.function.Supplier; import org.springframework.beans.factory.config.BeanDefinitionCustomizer; @@ -152,7 +153,8 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex * @see #scan(String...) * @see #refresh() */ - public void register(Class... annotatedClasses) { + @Override + public final void register(Class... annotatedClasses) { Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified"); this.reader.register(annotatedClasses); } @@ -165,7 +167,8 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex * @see #register(Class...) * @see #refresh() */ - public void scan(String... basePackages) { + @Override + public final void scan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); this.scanner.scan(basePackages); } @@ -175,6 +178,65 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex // Convenient methods for registering individual beans //--------------------------------------------------------------------- + /** + * Register a bean from the given bean class. + * @param annotatedClass the class of the bean + * @since 5.2 + */ + public final void registerBean(Class annotatedClass) { + this.reader.doRegisterBean(annotatedClass, null, null, null); + } + + /** + * Register a bean from the given bean class, using the given supplier for + * obtaining a new instance (typically declared as a lambda expression or + * method reference). + * @param annotatedClass the class of the bean + * @param supplier a callback for creating an instance of the bean + * @since 5.2 + */ + public final void registerBean(Class annotatedClass, Supplier supplier) { + this.reader.doRegisterBean(annotatedClass, supplier, null, 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 (may be empty). + * These can be actual autowire qualifiers as well as {@link Primary} + * and {@link Lazy}. + * @since 5.2 + */ + @Override + @SafeVarargs + @SuppressWarnings("varargs") + public final void registerBean(Class annotatedClass, Class... qualifiers) { + this.reader.doRegisterBean(annotatedClass, null, null, qualifiers); + } + + /** + * Register a bean from the given bean class, using the given supplier for + * obtaining a new instance (typically declared as a lambda expression or + * method reference). + * @param annotatedClass the class of the bean + * @param supplier a callback for creating an instance of the bean + * @param qualifiers specific qualifier annotations to consider, + * in addition to qualifiers at the bean class level (may be empty). + * These can be actual autowire qualifiers as well as {@link Primary} + * and {@link Lazy}. + * @since 5.2 + */ + @Override + @SafeVarargs + @SuppressWarnings("varargs") + public final void registerBean( + Class annotatedClass, Supplier supplier, Class... qualifiers) { + + this.reader.doRegisterBean(annotatedClass, supplier, null, qualifiers); + } + /** * Register a bean from the given bean class, deriving its metadata from * class-declared annotations, and optionally providing explicit constructor @@ -187,7 +249,7 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex * (may be {@code null} or empty) * @since 5.0 */ - public void registerBean(Class annotatedClass, Object... constructorArguments) { + public final void registerBean(Class annotatedClass, Object... constructorArguments) { registerBean(null, annotatedClass, constructorArguments); } @@ -203,7 +265,9 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex * (may be {@code null} or empty) * @since 5.0 */ - public void registerBean(@Nullable String beanName, Class annotatedClass, Object... constructorArguments) { + public final void registerBean( + @Nullable String beanName, Class annotatedClass, Object... constructorArguments) { + this.reader.doRegisterBean(annotatedClass, null, beanName, null, bd -> { for (Object arg : constructorArguments) { @@ -213,8 +277,8 @@ public class AnnotationConfigApplicationContext extends GenericApplicationContex } @Override - public void registerBean(@Nullable String beanName, Class beanClass, @Nullable Supplier supplier, - BeanDefinitionCustomizer... customizers) { + public final void registerBean(@Nullable String beanName, Class beanClass, + @Nullable Supplier supplier, BeanDefinitionCustomizer... customizers) { this.reader.doRegisterBean(beanClass, supplier, beanName, null, customizers); } diff --git a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigRegistry.java b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigRegistry.java index d1fe2f1def8..62ba5eec5b2 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigRegistry.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2019 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.lang.annotation.Annotation; +import java.util.function.Supplier; + /** * Common interface for annotation config application contexts, * defining {@link #register} and {@link #scan} methods. @@ -40,4 +43,32 @@ public interface AnnotationConfigRegistry { */ void scan(String... basePackages); + /** + * 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 (may be empty). + * These can be actual autowire qualifiers as well as {@link Primary} + * and {@link Lazy}. + * @since 5.2 + */ + @SuppressWarnings("unchecked") + void registerBean(Class annotatedClass, Class... qualifiers); + + /** + * Register a bean from the given bean class, using the given supplier for + * obtaining a new instance (typically declared as a lambda expression or + * method reference). + * @param annotatedClass the class of the bean + * @param supplier a callback for creating an instance of the bean + * @param qualifiers specific qualifier annotations to consider, + * in addition to qualifiers at the bean class level (may be empty). + * These can be actual autowire qualifiers as well as {@link Primary} + * and {@link Lazy}. + * @since 5.2 + */ + @SuppressWarnings("unchecked") + void registerBean(Class annotatedClass, Supplier supplier, Class... qualifiers); + } diff --git a/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java index 55e2c8cfe5a..d04f3ac1acc 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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,9 +16,11 @@ package org.springframework.web.context.support; +import java.lang.annotation.Annotation; import java.util.Collections; import java.util.LinkedHashSet; import java.util.Set; +import java.util.function.Supplier; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.beans.factory.support.DefaultListableBeanFactory; @@ -94,6 +96,8 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe private final Set basePackages = new LinkedHashSet<>(); + private final Set registeredBeans = new LinkedHashSet<>(); + /** * Set a custom {@link BeanNameGenerator} for use with {@link AnnotatedBeanDefinitionReader} @@ -140,14 +144,14 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe * Register one or more annotated classes to be processed. *

Note that {@link #refresh()} must be called in order for the context * to fully process the new classes. - * @param annotatedClasses one or more annotated classes, - * e.g. {@link org.springframework.context.annotation.Configuration @Configuration} classes + * @param annotatedClasses one or more annotated classes, e.g. + * {@link org.springframework.context.annotation.Configuration @Configuration} classes * @see #scan(String...) * @see #loadBeanDefinitions(DefaultListableBeanFactory) - * @see #setConfigLocation(String) * @see #refresh() */ - public void register(Class... annotatedClasses) { + @Override + public final void register(Class... annotatedClasses) { Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified"); Collections.addAll(this.annotatedClasses, annotatedClasses); } @@ -157,16 +161,58 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe *

Note that {@link #refresh()} must be called in order for the context * to fully process the new classes. * @param basePackages the packages to check for annotated classes - * @see #loadBeanDefinitions(DefaultListableBeanFactory) * @see #register(Class...) - * @see #setConfigLocation(String) + * @see #loadBeanDefinitions(DefaultListableBeanFactory) * @see #refresh() */ - public void scan(String... basePackages) { + @Override + public final void scan(String... basePackages) { Assert.notEmpty(basePackages, "At least one base package must be specified"); Collections.addAll(this.basePackages, basePackages); } + /** + * Register a bean from the given bean class, deriving its metadata from + * class-declared annotations. + *

Note that {@link #refresh()} must be called in order for the context + * to fully process the new classes. + * @param annotatedClass the class of the bean + * @param qualifiers specific qualifier annotations to consider, + * in addition to qualifiers at the bean class level (may be empty) + * @since 5.2 + * @see #register(Class...) + * @see #loadBeanDefinitions(DefaultListableBeanFactory) + * @see #refresh() + */ + @Override + @SafeVarargs + @SuppressWarnings("varargs") + public final void registerBean(Class annotatedClass, Class... qualifiers) { + this.registeredBeans.add(new BeanRegistration(annotatedClass, null, qualifiers)); + } + + /** + * Register a bean from the given bean class, deriving its metadata from + * class-declared annotations. + *

Note that {@link #refresh()} must be called in order for the context + * to fully process the new classes. + * @param annotatedClass the class of the bean + * @param qualifiers specific qualifier annotations to consider, + * in addition to qualifiers at the bean class level (may be empty) + * @since 5.2 + * @see #register(Class...) + * @see #loadBeanDefinitions(DefaultListableBeanFactory) + * @see #refresh() + */ + @Override + @SafeVarargs + @SuppressWarnings("varargs") + public final void registerBean( + Class annotatedClass, Supplier supplier, Class... qualifiers) { + + this.registeredBeans.add(new BeanRegistration(annotatedClass, supplier, qualifiers)); + } + /** * Register a {@link org.springframework.beans.factory.config.BeanDefinition} for @@ -191,6 +237,7 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe * @see ClassPathBeanDefinitionScanner */ @Override + @SuppressWarnings("unchecked") protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) { AnnotatedBeanDefinitionReader reader = getAnnotatedBeanDefinitionReader(beanFactory); ClassPathBeanDefinitionScanner scanner = getClassPathBeanDefinitionScanner(beanFactory); @@ -224,6 +271,15 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe scanner.scan(StringUtils.toStringArray(this.basePackages)); } + if (!this.registeredBeans.isEmpty()) { + if (logger.isDebugEnabled()) { + logger.debug("Registering supplied beans: [" + + StringUtils.collectionToCommaDelimitedString(this.registeredBeans) + "]"); + } + this.registeredBeans.forEach(reg -> + reader.registerBean(reg.getAnnotatedClass(), reg.getSupplier(), reg.getQualifiers())); + } + String[] configLocations = getConfigLocations(); if (configLocations != null) { for (String configLocation : configLocations) { @@ -277,4 +333,46 @@ public class AnnotationConfigWebApplicationContext extends AbstractRefreshableWe return new ClassPathBeanDefinitionScanner(beanFactory, true, getEnvironment()); } + + /** + * Holder for a programmatic bean registration. + * @see #registerBean(Class, Class[]) + * @see #registerBean(Class, Supplier, Class[]) + */ + private static class BeanRegistration { + + private final Class annotatedClass; + + @Nullable + private final Supplier supplier; + + private final Class[] qualifiers; + + public BeanRegistration( + Class annotatedClass, @Nullable Supplier supplier, Class[] qualifiers) { + this.annotatedClass = annotatedClass; + this.supplier = supplier; + this.qualifiers = qualifiers; + } + + public Class getAnnotatedClass() { + return this.annotatedClass; + } + + @Nullable + @SuppressWarnings("rawtypes") + public Supplier getSupplier() { + return this.supplier; + } + + public Class[] getQualifiers() { + return this.qualifiers; + } + + @Override + public String toString() { + return this.annotatedClass.getName(); + } + } + } diff --git a/spring-web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java b/spring-web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java index be74d092542..ac61dbd7aac 100644 --- a/spring-web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java +++ b/spring-web/src/test/java/org/springframework/web/context/support/AnnotationConfigWebApplicationContextTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2019 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. @@ -23,6 +23,7 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.context.annotation.AnnotationBeanNameGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import static org.hamcrest.CoreMatchers.*; import static org.junit.Assert.*; @@ -82,6 +83,54 @@ public class AnnotationConfigWebApplicationContextTests { assertThat(ctx.containsBean("custom-myConfig"), is(true)); } + @Test + @SuppressWarnings("resource") + public void registerBean() { + AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); + ctx.registerBean(TestBean.class); + ctx.refresh(); + + assertTrue(ctx.getBeanFactory().containsSingleton("annotationConfigWebApplicationContextTests.TestBean")); + TestBean bean = ctx.getBean(TestBean.class); + assertNotNull(bean); + } + + @Test + @SuppressWarnings("resource") + public void registerBeanWithLazy() { + AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); + ctx.registerBean(TestBean.class, Lazy.class); + ctx.refresh(); + + assertFalse(ctx.getBeanFactory().containsSingleton("annotationConfigWebApplicationContextTests.TestBean")); + TestBean bean = ctx.getBean(TestBean.class); + assertNotNull(bean); + } + + @Test + @SuppressWarnings("resource") + public void registerBeanWithSupplier() { + AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); + ctx.registerBean(TestBean.class, TestBean::new); + ctx.refresh(); + + assertTrue(ctx.getBeanFactory().containsSingleton("annotationConfigWebApplicationContextTests.TestBean")); + TestBean bean = ctx.getBean(TestBean.class); + assertNotNull(bean); + } + + @Test + @SuppressWarnings("resource") + public void registerBeanWithSupplierAndLazy() { + AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext(); + ctx.registerBean(TestBean.class, TestBean::new, Lazy.class); + ctx.refresh(); + + assertFalse(ctx.getBeanFactory().containsSingleton("annotationConfigWebApplicationContextTests.TestBean")); + TestBean bean = ctx.getBean(TestBean.class); + assertNotNull(bean); + } + @Configuration("myConfig") static class Config {