From 048ad42b0ac642739ace0b507981f1570934f014 Mon Sep 17 00:00:00 2001
From: Uladzislau Seuruk <vladislavsevruk@gmail.com>
Date: Thu, 20 Jan 2022 13:21:45 +0200
Subject: [PATCH 1/6] Add ConditionalOnBean support for generic bean argument
 types

---
 .../condition/ConditionalOnBean.java          |  17 ++
 .../condition/ConditionalOnMissingBean.java   |  17 ++
 .../condition/OnBeanCondition.java            |  58 ++++-
 .../condition/ConditionalOnBeanTests.java     | 198 ++++++++++++++-
 .../ConditionalOnMissingBeanTests.java        | 225 ++++++++++++++++--
 5 files changed, 472 insertions(+), 43 deletions(-)

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java
index 7d5ca163a162..66f1e3820fc2 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java
@@ -56,6 +56,7 @@
  * another auto-configuration, make sure that the one using this condition runs after.
  *
  * @author Phillip Webb
+ * @author Uladzislau Seuruk
  * @since 1.0.0
  */
 @Target({ ElementType.TYPE, ElementType.METHOD })
@@ -110,4 +111,20 @@
 	 */
 	Class<?>[] parameterizedContainer() default {};
 
+	/**
+	 * The class types of generic bean type arguments that should be checked. The
+	 * condition matches when beans of all classes specified are contained in the
+	 * {@link BeanFactory}.
+	 * @return the class types of beans to check
+	 */
+	Class<?>[] typeArguments() default {};
+
+	/**
+	 * The class type names of generic bean type arguments that should be checked. The
+	 * condition matches when beans of all classes specified are contained in the
+	 * {@link BeanFactory}.
+	 * @return the class type names of beans to check
+	 */
+	String[] typeArgumentNames() default {};
+
 }
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java
index c08e6200c7bb..d9dfaa122553 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java
@@ -57,6 +57,7 @@
  *
  * @author Phillip Webb
  * @author Andy Wilkinson
+ * @author Uladzislau Seuruk
  * @since 1.0.0
  */
 @Target({ ElementType.TYPE, ElementType.METHOD })
@@ -126,4 +127,20 @@
 	 */
 	Class<?>[] parameterizedContainer() default {};
 
+	/**
+	 * The class types of generic bean type arguments that should be checked. The
+	 * condition matches when no bean of each class specified is contained in the
+	 * {@link BeanFactory}.
+	 * @return the class types of beans to check
+	 */
+	Class<?>[] typeArguments() default {};
+
+	/**
+	 * The class type names of generic bean type arguments that should be checked. The
+	 * condition matches when no bean of each class specified is contained in the
+	 * {@link BeanFactory}.
+	 * @return the class type names of beans to check
+	 */
+	String[] typeArgumentNames() default {};
+
 }
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
index 756068f5b13d..f9585f007990 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
@@ -69,6 +69,7 @@
  * @author Jakub Kubrynski
  * @author Stephane Nicoll
  * @author Andy Wilkinson
+ * @author Uladzislau Seuruk
  * @see ConditionalOnBean
  * @see ConditionalOnMissingBean
  * @see ConditionalOnSingleCandidate
@@ -156,6 +157,7 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s
 		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
 		boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
 		Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
+		Set<Class<?>> typeArguments = spec.getTypeArguments();
 		if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
 			BeanFactory parent = beanFactory.getParentBeanFactory();
 			Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
@@ -167,7 +169,7 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s
 				spec.getIgnoredTypes(), parameterizedContainers);
 		for (String type : spec.getTypes()) {
 			Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
-					parameterizedContainers);
+					parameterizedContainers, typeArguments);
 			Iterator<String> iterator = typeMatches.iterator();
 			while (iterator.hasNext()) {
 				String match = iterator.next();
@@ -209,17 +211,18 @@ private Set<String> getNamesOfBeansIgnoredByType(ClassLoader classLoader, Listab
 		Set<String> result = null;
 		for (String ignoredType : ignoredTypes) {
 			Collection<String> ignoredNames = getBeanNamesForType(classLoader, considerHierarchy, beanFactory,
-					ignoredType, parameterizedContainers);
+					ignoredType, parameterizedContainers, Collections.emptySet());
 			result = addAll(result, ignoredNames);
 		}
 		return (result != null) ? result : Collections.emptySet();
 	}
 
 	private Set<String> getBeanNamesForType(ClassLoader classLoader, boolean considerHierarchy,
-			ListableBeanFactory beanFactory, String type, Set<Class<?>> parameterizedContainers) throws LinkageError {
+			ListableBeanFactory beanFactory, String type, Set<Class<?>> parameterizedContainers,
+			Set<Class<?>> typeArguments) throws LinkageError {
 		try {
 			return getBeanNamesForType(beanFactory, considerHierarchy, resolve(type, classLoader),
-					parameterizedContainers);
+					parameterizedContainers, typeArguments);
 		}
 		catch (ClassNotFoundException | NoClassDefFoundError ex) {
 			return Collections.emptySet();
@@ -227,29 +230,53 @@ private Set<String> getBeanNamesForType(ClassLoader classLoader, boolean conside
 	}
 
 	private Set<String> getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class<?> type,
-			Set<Class<?>> parameterizedContainers) {
+			Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments) {
 		Set<String> result = collectBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers,
-				null);
+				typeArguments, null);
 		return (result != null) ? result : Collections.emptySet();
 	}
 
-	private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy,
-			Class<?> type, Set<Class<?>> parameterizedContainers, Set<String> result) {
+	private Set<String> collectBeanNamesWithoutTypeArguments(ListableBeanFactory beanFactory, Class<?> type,
+			Set<Class<?>> parameterizedContainers, Set<String> result) {
 		result = addAll(result, beanFactory.getBeanNamesForType(type, true, false));
 		for (Class<?> container : parameterizedContainers) {
 			ResolvableType generic = ResolvableType.forClassWithGenerics(container, type);
 			result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false));
 		}
+		return result;
+	}
+
+	private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy,
+			Class<?> type, Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments, Set<String> result) {
+		if (!typeArguments.isEmpty()) {
+			result = collectBeanNamesWithTypeArguments(beanFactory, type, parameterizedContainers, typeArguments,
+					result);
+		}
+		else {
+			result = collectBeanNamesWithoutTypeArguments(beanFactory, type, parameterizedContainers, result);
+		}
 		if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
 			BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory();
 			if (parent instanceof ListableBeanFactory) {
 				result = collectBeanNamesForType((ListableBeanFactory) parent, considerHierarchy, type,
-						parameterizedContainers, result);
+						parameterizedContainers, typeArguments, result);
 			}
 		}
 		return result;
 	}
 
+	private Set<String> collectBeanNamesWithTypeArguments(ListableBeanFactory beanFactory, Class<?> type,
+			Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments, Set<String> result) {
+		Class<?>[] typeArgumentArray = typeArguments.toArray(Class<?>[]::new);
+		ResolvableType genericType = ResolvableType.forClassWithGenerics(type, typeArgumentArray);
+		result = addAll(result, beanFactory.getBeanNamesForType(genericType, true, false));
+		for (Class<?> container : parameterizedContainers) {
+			ResolvableType generic = ResolvableType.forClassWithGenerics(container, genericType);
+			result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false));
+		}
+		return result;
+	}
+
 	private Set<String> getBeanNamesForAnnotation(ClassLoader classLoader, ConfigurableListableBeanFactory beanFactory,
 			String type, boolean considerHierarchy) throws LinkageError {
 		Set<String> result = null;
@@ -407,6 +434,8 @@ private static class Spec<A extends Annotation> {
 
 		private final Set<Class<?>> parameterizedContainers;
 
+		private final Set<Class<?>> typeArguments;
+
 		private final SearchStrategy strategy;
 
 		Spec(ConditionContext context, AnnotatedTypeMetadata metadata, MergedAnnotations annotations,
@@ -421,6 +450,7 @@ private static class Spec<A extends Annotation> {
 			this.annotations = extract(attributes, "annotation");
 			this.ignoredTypes = extract(attributes, "ignored", "ignoredType");
 			this.parameterizedContainers = resolveWhenPossible(extract(attributes, "parameterizedContainer"));
+			this.typeArguments = resolveWhenPossible(extract(attributes, "typeArguments", "typeArgumentNames"));
 			this.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null);
 			Set<String> types = extractTypes(attributes);
 			BeanTypeDeductionException deductionException = null;
@@ -584,6 +614,10 @@ Set<String> getIgnoredTypes() {
 			return this.ignoredTypes;
 		}
 
+		Set<Class<?>> getTypeArguments() {
+			return this.typeArguments;
+		}
+
 		Set<Class<?>> getParameterizedContainers() {
 			return this.parameterizedContainers;
 		}
@@ -601,6 +635,7 @@ public String toString() {
 			boolean hasNames = !this.names.isEmpty();
 			boolean hasTypes = !this.types.isEmpty();
 			boolean hasIgnoredTypes = !this.ignoredTypes.isEmpty();
+			boolean hasTypeArguments = !this.typeArguments.isEmpty();
 			StringBuilder string = new StringBuilder();
 			string.append("(");
 			if (hasNames) {
@@ -618,6 +653,11 @@ public String toString() {
 				string.append(StringUtils.collectionToCommaDelimitedString(this.ignoredTypes));
 				string.append("; ");
 			}
+			if (hasTypeArguments) {
+				string.append("argument types: ");
+				string.append(StringUtils.collectionToCommaDelimitedString(this.typeArguments));
+				string.append("; ");
+			}
 			string.append("SearchStrategy: ");
 			string.append(this.strategy.toString().toLowerCase(Locale.ENGLISH));
 			string.append(")");
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
index 5bfb79e00d00..453d3a94b467 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
@@ -51,6 +51,7 @@
  *
  * @author Dave Syer
  * @author Stephane Nicoll
+ * @author Uladzislau Seuruk
  */
 class ConditionalOnBeanTests {
 
@@ -226,6 +227,98 @@ void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistratio
 						.satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean")));
 	}
 
+	@Test
+	void genericWhenTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class,
+						GenericWithIntegerTypeArgumentsConfig.class)
+				.run((context) -> assertThat(context).satisfies(
+						exampleBeanRequirement("customExampleBean", "genericStringTypeArgumentsExampleBean")));
+	}
+
+	@Test
+	void genericWhenTypeArgumentNameNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithIntegerConfig.class, GenericWithStringTypeArgumentNamesConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean")));
+	}
+
+	@Test
+	void genericWhenTypeArgumentNameMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringConfig.class, GenericWithStringTypeArgumentNamesConfig.class)
+				.run((context) -> assertThat(context)
+						.satisfies(exampleBeanRequirement("genericStringExampleBean", "genericStringNameExampleBean")));
+	}
+
+	@Test
+	void genericWhenTypeArgumentWithValueMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringConfig.class, TypeArgumentsConditionWithValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(
+						exampleBeanRequirement("genericStringExampleBean", "genericStringWithValueExampleBean")));
+	}
+
+	@Test
+	void genericWithValueWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class)
+				.run((context) -> assertThat(context)
+						.satisfies(exampleBeanRequirement("customExampleBean", "genericStringWithValueExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenTypeArgumentNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithIntegerConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean",
+						"parameterizedContainerGenericExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(
+						exampleBeanRequirement("customExampleBean", "parameterizedContainerGenericExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWithValueWhenTypeArgumentNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithIntegerConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWithValueWhenTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean",
+						"parameterizedContainerGenericWithValueExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWithValueWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean",
+						"parameterizedContainerGenericWithValueExampleBean")));
+	}
+
 	private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
 		return (context) -> {
 			String[] beans = context.getBeanNamesForType(ExampleBean.class);
@@ -363,11 +456,11 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
 
 	}
 
-	static class ExampleFactoryBean implements FactoryBean<ExampleBean> {
+	static class ExampleFactoryBean implements FactoryBean<ExampleBean<String>> {
 
 		@Override
-		public ExampleBean getObject() {
-			return new ExampleBean("fromFactory");
+		public ExampleBean<String> getObject() {
+			return new ExampleBean<>("fromFactory");
 		}
 
 		@Override
@@ -485,23 +578,110 @@ TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() {
 
 	}
 
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithStringConfig {
+
+		@Bean
+		ExampleBean<String> genericStringExampleBean() {
+			return new ExampleBean<>("genericStringExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithStringTypeArgumentsConfig {
+
+		@Bean
+		@ConditionalOnBean(typeArguments = String.class)
+		ExampleBean<String> genericStringTypeArgumentsExampleBean() {
+			return new ExampleBean<>("genericStringTypeArgumentsExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithStringTypeArgumentNamesConfig {
+
+		@Bean
+		@ConditionalOnBean(typeArgumentNames = "java.lang.String")
+		ExampleBean<String> genericStringNameExampleBean() {
+			return new ExampleBean<>("genericStringNameExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithIntegerConfig {
+
+		@Bean
+		ExampleBean<Integer> genericIntegerExampleBean() {
+			return new ExampleBean<>(1_000);
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithIntegerTypeArgumentsConfig {
+
+		@Bean
+		@ConditionalOnBean(typeArguments = Integer.class)
+		ExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() {
+			return new ExampleBean<>(1_000);
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class TypeArgumentsConditionWithValueConfig {
+
+		@Bean
+		@ConditionalOnBean(value = ExampleBean.class, typeArguments = String.class)
+		ExampleBean<String> genericStringWithValueExampleBean() {
+			return new ExampleBean<>("genericStringWithValueExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class TypeArgumentsConditionWithParameterizedContainerConfig {
+
+		@Bean
+		@ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class, typeArguments = String.class)
+		TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() {
+			return new TestParameterizedContainer<>();
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class TypeArgumentsConditionWithParameterizedContainerAndValueConfig {
+
+		@Bean
+		@ConditionalOnBean(value = ExampleBean.class, parameterizedContainer = TestParameterizedContainer.class,
+				typeArguments = String.class)
+		TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericWithValueExampleBean() {
+			return new TestParameterizedContainer<>();
+		}
+
+	}
+
 	@TestAnnotation
-	static class ExampleBean {
+	static class ExampleBean<T> {
 
-		private String value;
+		private final T value;
 
-		ExampleBean(String value) {
+		ExampleBean(T value) {
 			this.value = value;
 		}
 
 		@Override
 		public String toString() {
-			return this.value;
+			return String.valueOf(this.value);
 		}
 
 	}
 
-	static class CustomExampleBean extends ExampleBean {
+	static class CustomExampleBean extends ExampleBean<String> {
 
 		CustomExampleBean() {
 			super("custom subclass");
@@ -509,7 +689,7 @@ static class CustomExampleBean extends ExampleBean {
 
 	}
 
-	static class OtherExampleBean extends ExampleBean {
+	static class OtherExampleBean extends ExampleBean<String> {
 
 		OtherExampleBean() {
 			super("other subclass");
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
index f7087dd2331c..d1052e08d5bf 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
@@ -59,6 +59,7 @@
  * @author Phillip Webb
  * @author Jakub Kubrynski
  * @author Andy Wilkinson
+ * @author Uladzislau Seuruk
  */
 @SuppressWarnings("resource")
 class ConditionalOnMissingBeanTests {
@@ -340,6 +341,112 @@ void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistratio
 				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
 	}
 
+	@Test
+	void genericWhenTypeArgumentNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
+						GenericWithIntegerTypeArgumentsConfig.class)
+				.run((context) -> assertThat(context)
+						.satisfies(exampleBeanRequirement("genericStringExampleBean", "genericIntegerExampleBean")));
+	}
+
+	@Test
+	void genericWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
+	}
+
+	@Test
+	void genericWhenSubclassTypeArgumentNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithIntegerTypeArgumentsConfig.class)
+				.run((context) -> assertThat(context)
+						.satisfies(exampleBeanRequirement("customExampleBean", "genericIntegerExampleBean")));
+	}
+
+	@Test
+	void genericWhenTypeArgumentNameNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class,
+						GenericWithStringTypeArgumentNamesConfig.class)
+				.run((context) -> assertThat(context).satisfies(
+						exampleBeanRequirement("genericIntegerExampleBean", "genericStringNameExampleBean")));
+	}
+
+	@Test
+	void genericWhenTypeArgumentNameMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
+						GenericWithStringTypeArgumentNamesConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean")));
+	}
+
+	@Test
+	void genericWhenTypeArgumentWithValueMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
+						TypeArgumentsConditionWithValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean")));
+	}
+
+	@Test
+	void genericWithValueWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenTypeArgumentNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean",
+						"parameterizedContainerGenericExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWithValueWhenTypeArgumentNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean",
+						"parameterizedContainerGenericWithValueExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWithValueWhenTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWithValueWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
+	}
+
 	private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
 		return (context) -> {
 			String[] beans = context.getBeanNamesForType(ExampleBean.class);
@@ -353,8 +460,8 @@ static class OnBeanInAncestorsConfiguration {
 
 		@Bean
 		@ConditionalOnMissingBean(search = SearchStrategy.ANCESTORS)
-		ExampleBean exampleBean2() {
-			return new ExampleBean("test");
+		ExampleBean<String> exampleBean2() {
+			return new ExampleBean<String>("test");
 		}
 
 	}
@@ -386,7 +493,7 @@ String bar() {
 	static class FactoryBeanConfiguration {
 
 		@Bean
-		FactoryBean<ExampleBean> exampleBeanFactoryBean() {
+		FactoryBean<ExampleBean<String>> exampleBeanFactoryBean() {
 			return new ExampleFactoryBean("foo");
 		}
 
@@ -412,7 +519,7 @@ static class ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration {
 	static class FactoryBeanWithBeanMethodArgumentsConfiguration {
 
 		@Bean
-		FactoryBean<ExampleBean> exampleBeanFactoryBean(@Value("${theValue}") String value) {
+		FactoryBean<ExampleBean<String>> exampleBeanFactoryBean(@Value("${theValue}") String value) {
 			return new ExampleFactoryBean(value);
 		}
 
@@ -503,8 +610,8 @@ static class ConditionalOnFactoryBean {
 
 		@Bean
 		@ConditionalOnMissingBean(ExampleBean.class)
-		ExampleBean createExampleBean() {
-			return new ExampleBean("direct");
+		ExampleBean<String> createExampleBean() {
+			return new ExampleBean<>("direct");
 		}
 
 	}
@@ -514,8 +621,8 @@ static class ConditionalOnIgnoredSubclass {
 
 		@Bean
 		@ConditionalOnMissingBean(value = ExampleBean.class, ignored = CustomExampleBean.class)
-		ExampleBean exampleBean() {
-			return new ExampleBean("test");
+		ExampleBean<String> exampleBean() {
+			return new ExampleBean<>("test");
 		}
 
 	}
@@ -526,8 +633,8 @@ static class ConditionalOnIgnoredSubclassByName {
 		@Bean
 		@ConditionalOnMissingBean(value = ExampleBean.class,
 				ignoredType = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests$CustomExampleBean")
-		ExampleBean exampleBean() {
-			return new ExampleBean("test");
+		ExampleBean<String> exampleBean() {
+			return new ExampleBean<>("test");
 		}
 
 	}
@@ -601,8 +708,8 @@ String bar() {
 	static class ExampleBeanConfiguration {
 
 		@Bean
-		ExampleBean exampleBean() {
-			return new ExampleBean("test");
+		ExampleBean<String> exampleBean() {
+			return new ExampleBean<>("test");
 		}
 
 	}
@@ -612,21 +719,21 @@ static class ImpliedOnBeanMethod {
 
 		@Bean
 		@ConditionalOnMissingBean
-		ExampleBean exampleBean2() {
-			return new ExampleBean("test");
+		ExampleBean<String> exampleBean2() {
+			return new ExampleBean<>("test");
 		}
 
 	}
 
-	static class ExampleFactoryBean implements FactoryBean<ExampleBean> {
+	static class ExampleFactoryBean implements FactoryBean<ExampleBean<String>> {
 
 		ExampleFactoryBean(String value) {
 			Assert.state(!value.contains("$"), "value should not contain '$'");
 		}
 
 		@Override
-		public ExampleBean getObject() {
-			return new ExampleBean("fromFactory");
+		public ExampleBean<String> getObject() {
+			return new ExampleBean<>("fromFactory");
 		}
 
 		@Override
@@ -648,8 +755,8 @@ static class NonspecificFactoryBean implements FactoryBean<Object> {
 		}
 
 		@Override
-		public ExampleBean getObject() {
-			return new ExampleBean("fromFactory");
+		public ExampleBean<String> getObject() {
+			return new ExampleBean<>("fromFactory");
 		}
 
 		@Override
@@ -738,23 +845,91 @@ TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() {
 
 	}
 
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithStringTypeArgumentsConfig {
+
+		@Bean
+		@ConditionalOnMissingBean(typeArguments = String.class)
+		ExampleBean<String> genericStringExampleBean() {
+			return new ExampleBean<>("genericStringExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithStringTypeArgumentNamesConfig {
+
+		@Bean
+		@ConditionalOnMissingBean(typeArgumentNames = "java.lang.String")
+		ExampleBean<String> genericStringNameExampleBean() {
+			return new ExampleBean<>("genericStringNameExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithIntegerTypeArgumentsConfig {
+
+		@Bean
+		@ConditionalOnMissingBean(typeArguments = Integer.class)
+		ExampleBean<Integer> genericIntegerExampleBean() {
+			return new ExampleBean<>(1_000);
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class TypeArgumentsConditionWithValueConfig {
+
+		@Bean
+		@ConditionalOnMissingBean(value = ExampleBean.class, typeArguments = String.class)
+		ExampleBean<String> genericStringWithValueExampleBean() {
+			return new ExampleBean<>("genericStringWithValueExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class TypeArgumentsConditionWithParameterizedContainerConfig {
+
+		@Bean
+		@ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class,
+				typeArguments = String.class)
+		TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() {
+			return new TestParameterizedContainer<>();
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class TypeArgumentsConditionWithParameterizedContainerAndValueConfig {
+
+		@Bean
+		@ConditionalOnMissingBean(value = ExampleBean.class, parameterizedContainer = TestParameterizedContainer.class,
+				typeArguments = String.class)
+		TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericWithValueExampleBean() {
+			return new TestParameterizedContainer<>();
+		}
+
+	}
+
 	@TestAnnotation
-	static class ExampleBean {
+	static class ExampleBean<T> {
 
-		private String value;
+		private final T value;
 
-		ExampleBean(String value) {
+		ExampleBean(T value) {
 			this.value = value;
 		}
 
 		@Override
 		public String toString() {
-			return this.value;
+			return String.valueOf(this.value);
 		}
 
 	}
 
-	static class CustomExampleBean extends ExampleBean {
+	static class CustomExampleBean extends ExampleBean<String> {
 
 		CustomExampleBean() {
 			super("custom subclass");
@@ -762,7 +937,7 @@ static class CustomExampleBean extends ExampleBean {
 
 	}
 
-	static class OtherExampleBean extends ExampleBean {
+	static class OtherExampleBean extends ExampleBean<String> {
 
 		OtherExampleBean() {
 			super("other subclass");

From 6f9af2a6894fc4bd338e176724670d915b5ec0e7 Mon Sep 17 00:00:00 2001
From: Uladzislau Seuruk <vladislavsevruk@gmail.com>
Date: Thu, 3 Feb 2022 19:19:02 +0200
Subject: [PATCH 2/6] Update OnBeanCondition to auto-detect generic type
 arguments

---
 .../condition/OnBeanCondition.java            | 147 +++++++++---------
 .../condition/ConditionalOnBeanTests.java     |  28 ++--
 .../ConditionalOnMissingBeanTests.java        |  29 ++--
 3 files changed, 102 insertions(+), 102 deletions(-)

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
index f9585f007990..843c1c600f86 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
@@ -157,7 +157,6 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s
 		ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
 		boolean considerHierarchy = spec.getStrategy() != SearchStrategy.CURRENT;
 		Set<Class<?>> parameterizedContainers = spec.getParameterizedContainers();
-		Set<Class<?>> typeArguments = spec.getTypeArguments();
 		if (spec.getStrategy() == SearchStrategy.ANCESTORS) {
 			BeanFactory parent = beanFactory.getParentBeanFactory();
 			Assert.isInstanceOf(ConfigurableListableBeanFactory.class, parent,
@@ -165,11 +164,11 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s
 			beanFactory = (ConfigurableListableBeanFactory) parent;
 		}
 		MatchResult result = new MatchResult();
-		Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(classLoader, beanFactory, considerHierarchy,
+		Set<String> beansIgnoredByType = getNamesOfBeansIgnoredByType(beanFactory, considerHierarchy,
 				spec.getIgnoredTypes(), parameterizedContainers);
-		for (String type : spec.getTypes()) {
-			Collection<String> typeMatches = getBeanNamesForType(classLoader, considerHierarchy, beanFactory, type,
-					parameterizedContainers, typeArguments);
+		for (ResolvableType type : spec.getTypes()) {
+			Collection<String> typeMatches = getBeanNamesForType(considerHierarchy, beanFactory, type,
+					parameterizedContainers);
 			Iterator<String> iterator = typeMatches.iterator();
 			while (iterator.hasNext()) {
 				String match = iterator.next();
@@ -178,10 +177,10 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s
 				}
 			}
 			if (typeMatches.isEmpty()) {
-				result.recordUnmatchedType(type);
+				result.recordUnmatchedType(type.toString());
 			}
 			else {
-				result.recordMatchedType(type, typeMatches);
+				result.recordMatchedType(type.toString(), typeMatches);
 			}
 		}
 		for (String annotation : spec.getAnnotations()) {
@@ -206,37 +205,37 @@ protected final MatchResult getMatchingBeans(ConditionContext context, Spec<?> s
 		return result;
 	}
 
-	private Set<String> getNamesOfBeansIgnoredByType(ClassLoader classLoader, ListableBeanFactory beanFactory,
-			boolean considerHierarchy, Set<String> ignoredTypes, Set<Class<?>> parameterizedContainers) {
+	private Set<String> getNamesOfBeansIgnoredByType(ListableBeanFactory beanFactory, boolean considerHierarchy,
+			Set<Class<?>> ignoredTypes, Set<Class<?>> parameterizedContainers) {
 		Set<String> result = null;
-		for (String ignoredType : ignoredTypes) {
-			Collection<String> ignoredNames = getBeanNamesForType(classLoader, considerHierarchy, beanFactory,
-					ignoredType, parameterizedContainers, Collections.emptySet());
+		for (Class<?> ignoredType : ignoredTypes) {
+			ResolvableType ignoredResolvableType = ResolvableType.forClass(ignoredType);
+			Collection<String> ignoredNames = getBeanNamesForType(considerHierarchy, beanFactory,
+					ignoredResolvableType, parameterizedContainers);
 			result = addAll(result, ignoredNames);
 		}
 		return (result != null) ? result : Collections.emptySet();
 	}
 
-	private Set<String> getBeanNamesForType(ClassLoader classLoader, boolean considerHierarchy,
-			ListableBeanFactory beanFactory, String type, Set<Class<?>> parameterizedContainers,
-			Set<Class<?>> typeArguments) throws LinkageError {
+	private Set<String> getBeanNamesForType(boolean considerHierarchy,
+			ListableBeanFactory beanFactory, ResolvableType type, Set<Class<?>> parameterizedContainers)
+			throws LinkageError {
 		try {
-			return getBeanNamesForType(beanFactory, considerHierarchy, resolve(type, classLoader),
-					parameterizedContainers, typeArguments);
+			return getBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers);
 		}
-		catch (ClassNotFoundException | NoClassDefFoundError ex) {
+		catch (NoClassDefFoundError ex) {
 			return Collections.emptySet();
 		}
 	}
 
-	private Set<String> getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy, Class<?> type,
-			Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments) {
+	private Set<String> getBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy,
+			ResolvableType type, Set<Class<?>> parameterizedContainers) {
 		Set<String> result = collectBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers,
-				typeArguments, null);
+				null);
 		return (result != null) ? result : Collections.emptySet();
 	}
 
-	private Set<String> collectBeanNamesWithoutTypeArguments(ListableBeanFactory beanFactory, Class<?> type,
+	private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, ResolvableType type,
 			Set<Class<?>> parameterizedContainers, Set<String> result) {
 		result = addAll(result, beanFactory.getBeanNamesForType(type, true, false));
 		for (Class<?> container : parameterizedContainers) {
@@ -247,36 +246,18 @@ private Set<String> collectBeanNamesWithoutTypeArguments(ListableBeanFactory bea
 	}
 
 	private Set<String> collectBeanNamesForType(ListableBeanFactory beanFactory, boolean considerHierarchy,
-			Class<?> type, Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments, Set<String> result) {
-		if (!typeArguments.isEmpty()) {
-			result = collectBeanNamesWithTypeArguments(beanFactory, type, parameterizedContainers, typeArguments,
-					result);
-		}
-		else {
-			result = collectBeanNamesWithoutTypeArguments(beanFactory, type, parameterizedContainers, result);
-		}
+			ResolvableType type, Set<Class<?>> parameterizedContainers, Set<String> result) {
+		result = collectBeanNamesForType(beanFactory, type, parameterizedContainers, result);
 		if (considerHierarchy && beanFactory instanceof HierarchicalBeanFactory) {
 			BeanFactory parent = ((HierarchicalBeanFactory) beanFactory).getParentBeanFactory();
 			if (parent instanceof ListableBeanFactory) {
 				result = collectBeanNamesForType((ListableBeanFactory) parent, considerHierarchy, type,
-						parameterizedContainers, typeArguments, result);
+						parameterizedContainers, result);
 			}
 		}
 		return result;
 	}
 
-	private Set<String> collectBeanNamesWithTypeArguments(ListableBeanFactory beanFactory, Class<?> type,
-			Set<Class<?>> parameterizedContainers, Set<Class<?>> typeArguments, Set<String> result) {
-		Class<?>[] typeArgumentArray = typeArguments.toArray(Class<?>[]::new);
-		ResolvableType genericType = ResolvableType.forClassWithGenerics(type, typeArgumentArray);
-		result = addAll(result, beanFactory.getBeanNamesForType(genericType, true, false));
-		for (Class<?> container : parameterizedContainers) {
-			ResolvableType generic = ResolvableType.forClassWithGenerics(container, genericType);
-			result = addAll(result, beanFactory.getBeanNamesForType(generic, true, false));
-		}
-		return result;
-	}
-
 	private Set<String> getBeanNamesForAnnotation(ClassLoader classLoader, ConfigurableListableBeanFactory beanFactory,
 			String type, boolean considerHierarchy) throws LinkageError {
 		Set<String> result = null;
@@ -426,16 +407,14 @@ private static class Spec<A extends Annotation> {
 
 		private final Set<String> names;
 
-		private final Set<String> types;
+		private final Set<ResolvableType> types;
 
 		private final Set<String> annotations;
 
-		private final Set<String> ignoredTypes;
+		private final Set<Class<?>> ignoredTypes;
 
 		private final Set<Class<?>> parameterizedContainers;
 
-		private final Set<Class<?>> typeArguments;
-
 		private final SearchStrategy strategy;
 
 		Spec(ConditionContext context, AnnotatedTypeMetadata metadata, MergedAnnotations annotations,
@@ -448,11 +427,10 @@ private static class Spec<A extends Annotation> {
 			this.annotationType = annotationType;
 			this.names = extract(attributes, "name");
 			this.annotations = extract(attributes, "annotation");
-			this.ignoredTypes = extract(attributes, "ignored", "ignoredType");
+			this.ignoredTypes = resolveWhenPossible(extract(attributes, "ignored", "ignoredType"));
 			this.parameterizedContainers = resolveWhenPossible(extract(attributes, "parameterizedContainer"));
-			this.typeArguments = resolveWhenPossible(extract(attributes, "typeArguments", "typeArgumentNames"));
 			this.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null);
-			Set<String> types = extractTypes(attributes);
+			Set<ResolvableType> types = resolveTypes(extractTypes(attributes), extractTypeArguments(attributes));
 			BeanTypeDeductionException deductionException = null;
 			if (types.isEmpty() && this.names.isEmpty()) {
 				try {
@@ -470,6 +448,10 @@ protected Set<String> extractTypes(MultiValueMap<String, Object> attributes) {
 			return extract(attributes, "value", "type");
 		}
 
+		protected Set<String> extractTypeArguments(MultiValueMap<String, Object> attributes) {
+			return extract(attributes, "typeArguments", "typeArgumentNames");
+		}
+
 		private Set<String> extract(MultiValueMap<String, Object> attributes, String... attributeNames) {
 			if (attributes.isEmpty()) {
 				return Collections.emptySet();
@@ -489,6 +471,31 @@ else if (value instanceof String) {
 			return result.isEmpty() ? Collections.emptySet() : result;
 		}
 
+		private Set<ResolvableType> resolveTypes(Set<String> types, Set<String> typeAttributes) {
+			if (types.isEmpty()) {
+				return Collections.emptySet();
+			}
+			Set<ResolvableType> resolved = new LinkedHashSet<>(types.size());
+			Set<ResolvableType> resolvedTypeAttributes = resolveTypes(typeAttributes, Collections.emptySet());
+			for (String type : types) {
+				try {
+					Class<?> typeClass = resolve(type, this.classLoader);
+					resolved.add(resolveType(typeClass, resolvedTypeAttributes));
+				}
+				catch (ClassNotFoundException | NoClassDefFoundError ex) {
+					resolved.add(ResolvableType.NONE);
+				}
+			}
+			return resolved;
+		}
+
+		private ResolvableType resolveType(Class<?> typeClass, Set<ResolvableType> typeAttributes) {
+			if (typeAttributes.isEmpty()) {
+				return ResolvableType.forClass(typeClass);
+			}
+			return ResolvableType.forClassWithGenerics(typeClass, typeAttributes.toArray(new ResolvableType[0]));
+		}
+
 		private void merge(Set<String> result, String... additional) {
 			Collections.addAll(result, additional);
 		}
@@ -531,48 +538,52 @@ protected final String getAnnotationName() {
 			return "@" + ClassUtils.getShortName(this.annotationType);
 		}
 
-		private Set<String> deducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata) {
+		private Set<ResolvableType> deducedBeanType(ConditionContext context, AnnotatedTypeMetadata metadata) {
 			if (metadata instanceof MethodMetadata && metadata.isAnnotated(Bean.class.getName())) {
 				return deducedBeanTypeForBeanMethod(context, (MethodMetadata) metadata);
 			}
 			return Collections.emptySet();
 		}
 
-		private Set<String> deducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata) {
+		private Set<ResolvableType> deducedBeanTypeForBeanMethod(ConditionContext context, MethodMetadata metadata) {
 			try {
-				Class<?> returnType = getReturnType(context, metadata);
-				return Collections.singleton(returnType.getName());
+				ResolvableType returnType = getReturnType(context, metadata);
+				return Collections.singleton(returnType);
 			}
 			catch (Throwable ex) {
 				throw new BeanTypeDeductionException(metadata.getDeclaringClassName(), metadata.getMethodName(), ex);
 			}
 		}
 
-		private Class<?> getReturnType(ConditionContext context, MethodMetadata metadata)
+		private ResolvableType getReturnType(ConditionContext context, MethodMetadata metadata)
 				throws ClassNotFoundException, LinkageError {
 			// Safe to load at this point since we are in the REGISTER_BEAN phase
 			ClassLoader classLoader = context.getClassLoader();
-			Class<?> returnType = resolve(metadata.getReturnTypeName(), classLoader);
+			ResolvableType returnType = getMethodReturnType(metadata, classLoader);
 			if (isParameterizedContainer(returnType)) {
-				returnType = getReturnTypeGeneric(metadata, classLoader);
+				returnType = returnType.getGeneric();
 			}
 			return returnType;
 		}
 
-		private boolean isParameterizedContainer(Class<?> type) {
+		private boolean isParameterizedContainer(ResolvableType type) {
+			Class<?> rawType = type.getRawClass();
+			if (rawType == null) {
+				return false;
+			}
 			for (Class<?> parameterizedContainer : this.parameterizedContainers) {
-				if (parameterizedContainer.isAssignableFrom(type)) {
+				if (parameterizedContainer.isAssignableFrom(rawType)) {
 					return true;
 				}
 			}
 			return false;
 		}
 
-		private Class<?> getReturnTypeGeneric(MethodMetadata metadata, ClassLoader classLoader)
+		private ResolvableType getMethodReturnType(MethodMetadata metadata, ClassLoader classLoader)
 				throws ClassNotFoundException, LinkageError {
 			Class<?> declaringClass = resolve(metadata.getDeclaringClassName(), classLoader);
 			Method beanMethod = findBeanMethod(declaringClass, metadata.getMethodName());
-			return ResolvableType.forMethodReturnType(beanMethod).resolveGeneric();
+			return ResolvableType.forMethodReturnType(beanMethod);
 		}
 
 		private Method findBeanMethod(Class<?> declaringClass, String methodName) {
@@ -602,7 +613,7 @@ Set<String> getNames() {
 			return this.names;
 		}
 
-		Set<String> getTypes() {
+		Set<ResolvableType> getTypes() {
 			return this.types;
 		}
 
@@ -610,14 +621,10 @@ Set<String> getAnnotations() {
 			return this.annotations;
 		}
 
-		Set<String> getIgnoredTypes() {
+		Set<Class<?>> getIgnoredTypes() {
 			return this.ignoredTypes;
 		}
 
-		Set<Class<?>> getTypeArguments() {
-			return this.typeArguments;
-		}
-
 		Set<Class<?>> getParameterizedContainers() {
 			return this.parameterizedContainers;
 		}
@@ -635,7 +642,6 @@ public String toString() {
 			boolean hasNames = !this.names.isEmpty();
 			boolean hasTypes = !this.types.isEmpty();
 			boolean hasIgnoredTypes = !this.ignoredTypes.isEmpty();
-			boolean hasTypeArguments = !this.typeArguments.isEmpty();
 			StringBuilder string = new StringBuilder();
 			string.append("(");
 			if (hasNames) {
@@ -653,11 +659,6 @@ public String toString() {
 				string.append(StringUtils.collectionToCommaDelimitedString(this.ignoredTypes));
 				string.append("; ");
 			}
-			if (hasTypeArguments) {
-				string.append("argument types: ");
-				string.append(StringUtils.collectionToCommaDelimitedString(this.typeArguments));
-				string.append("; ");
-			}
 			string.append("SearchStrategy: ");
 			string.append(this.strategy.toString().toLowerCase(Locale.ENGLISH));
 			string.append(")");
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
index 453d3a94b467..af7fca1a0d9e 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
@@ -592,24 +592,13 @@ ExampleBean<String> genericStringExampleBean() {
 	static class GenericWithStringTypeArgumentsConfig {
 
 		@Bean
-		@ConditionalOnBean(typeArguments = String.class)
+		@ConditionalOnBean
 		ExampleBean<String> genericStringTypeArgumentsExampleBean() {
 			return new ExampleBean<>("genericStringTypeArgumentsExampleBean");
 		}
 
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class GenericWithStringTypeArgumentNamesConfig {
-
-		@Bean
-		@ConditionalOnBean(typeArgumentNames = "java.lang.String")
-		ExampleBean<String> genericStringNameExampleBean() {
-			return new ExampleBean<>("genericStringNameExampleBean");
-		}
-
-	}
-
 	@Configuration(proxyBeanMethods = false)
 	static class GenericWithIntegerConfig {
 
@@ -624,7 +613,7 @@ ExampleBean<Integer> genericIntegerExampleBean() {
 	static class GenericWithIntegerTypeArgumentsConfig {
 
 		@Bean
-		@ConditionalOnBean(typeArguments = Integer.class)
+		@ConditionalOnBean
 		ExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() {
 			return new ExampleBean<>(1_000);
 		}
@@ -642,11 +631,22 @@ ExampleBean<String> genericStringWithValueExampleBean() {
 
 	}
 
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithStringTypeArgumentNamesConfig {
+
+		@Bean
+		@ConditionalOnBean(value = ExampleBean.class, typeArgumentNames = "java.lang.String")
+		ExampleBean<String> genericStringNameExampleBean() {
+			return new ExampleBean<>("genericStringNameExampleBean");
+		}
+
+	}
+
 	@Configuration(proxyBeanMethods = false)
 	static class TypeArgumentsConditionWithParameterizedContainerConfig {
 
 		@Bean
-		@ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class, typeArguments = String.class)
+		@ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class)
 		TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() {
 			return new TestParameterizedContainer<>();
 		}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
index d1052e08d5bf..eff4d7c4d445 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
@@ -849,29 +849,18 @@ TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() {
 	static class GenericWithStringTypeArgumentsConfig {
 
 		@Bean
-		@ConditionalOnMissingBean(typeArguments = String.class)
+		@ConditionalOnMissingBean
 		ExampleBean<String> genericStringExampleBean() {
 			return new ExampleBean<>("genericStringExampleBean");
 		}
 
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class GenericWithStringTypeArgumentNamesConfig {
-
-		@Bean
-		@ConditionalOnMissingBean(typeArgumentNames = "java.lang.String")
-		ExampleBean<String> genericStringNameExampleBean() {
-			return new ExampleBean<>("genericStringNameExampleBean");
-		}
-
-	}
-
 	@Configuration(proxyBeanMethods = false)
 	static class GenericWithIntegerTypeArgumentsConfig {
 
 		@Bean
-		@ConditionalOnMissingBean(typeArguments = Integer.class)
+		@ConditionalOnMissingBean
 		ExampleBean<Integer> genericIntegerExampleBean() {
 			return new ExampleBean<>(1_000);
 		}
@@ -889,12 +878,22 @@ ExampleBean<String> genericStringWithValueExampleBean() {
 
 	}
 
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithStringTypeArgumentNamesConfig {
+
+		@Bean
+		@ConditionalOnMissingBean(value = ExampleBean.class, typeArgumentNames = "java.lang.String")
+		ExampleBean<String> genericStringNameExampleBean() {
+			return new ExampleBean<>("genericStringNameExampleBean");
+		}
+
+	}
+
 	@Configuration(proxyBeanMethods = false)
 	static class TypeArgumentsConditionWithParameterizedContainerConfig {
 
 		@Bean
-		@ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class,
-				typeArguments = String.class)
+		@ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class)
 		TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() {
 			return new TestParameterizedContainer<>();
 		}

From 4ee1fd341c26ae5c330db5b7947a65d1733279e6 Mon Sep 17 00:00:00 2001
From: Uladzislau Seuruk <vladislavsevruk@gmail.com>
Date: Fri, 4 Feb 2022 01:14:06 +0200
Subject: [PATCH 3/6] Removed added annotation methods

---
 .../condition/ConditionalOnBean.java          | 17 -----
 .../condition/ConditionalOnMissingBean.java   | 17 -----
 .../condition/OnBeanCondition.java            | 20 ++----
 .../condition/ConditionalOnBeanTests.java     | 66 +-----------------
 .../ConditionalOnMissingBeanTests.java        | 67 +------------------
 5 files changed, 7 insertions(+), 180 deletions(-)

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java
index 66f1e3820fc2..7d5ca163a162 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBean.java
@@ -56,7 +56,6 @@
  * another auto-configuration, make sure that the one using this condition runs after.
  *
  * @author Phillip Webb
- * @author Uladzislau Seuruk
  * @since 1.0.0
  */
 @Target({ ElementType.TYPE, ElementType.METHOD })
@@ -111,20 +110,4 @@
 	 */
 	Class<?>[] parameterizedContainer() default {};
 
-	/**
-	 * The class types of generic bean type arguments that should be checked. The
-	 * condition matches when beans of all classes specified are contained in the
-	 * {@link BeanFactory}.
-	 * @return the class types of beans to check
-	 */
-	Class<?>[] typeArguments() default {};
-
-	/**
-	 * The class type names of generic bean type arguments that should be checked. The
-	 * condition matches when beans of all classes specified are contained in the
-	 * {@link BeanFactory}.
-	 * @return the class type names of beans to check
-	 */
-	String[] typeArgumentNames() default {};
-
 }
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java
index d9dfaa122553..c08e6200c7bb 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBean.java
@@ -57,7 +57,6 @@
  *
  * @author Phillip Webb
  * @author Andy Wilkinson
- * @author Uladzislau Seuruk
  * @since 1.0.0
  */
 @Target({ ElementType.TYPE, ElementType.METHOD })
@@ -127,20 +126,4 @@
 	 */
 	Class<?>[] parameterizedContainer() default {};
 
-	/**
-	 * The class types of generic bean type arguments that should be checked. The
-	 * condition matches when no bean of each class specified is contained in the
-	 * {@link BeanFactory}.
-	 * @return the class types of beans to check
-	 */
-	Class<?>[] typeArguments() default {};
-
-	/**
-	 * The class type names of generic bean type arguments that should be checked. The
-	 * condition matches when no bean of each class specified is contained in the
-	 * {@link BeanFactory}.
-	 * @return the class type names of beans to check
-	 */
-	String[] typeArgumentNames() default {};
-
 }
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
index 843c1c600f86..84e7d053ecef 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
@@ -430,7 +430,7 @@ private static class Spec<A extends Annotation> {
 			this.ignoredTypes = resolveWhenPossible(extract(attributes, "ignored", "ignoredType"));
 			this.parameterizedContainers = resolveWhenPossible(extract(attributes, "parameterizedContainer"));
 			this.strategy = annotation.getValue("search", SearchStrategy.class).orElse(null);
-			Set<ResolvableType> types = resolveTypes(extractTypes(attributes), extractTypeArguments(attributes));
+			Set<ResolvableType> types = resolveTypes(extractTypes(attributes));
 			BeanTypeDeductionException deductionException = null;
 			if (types.isEmpty() && this.names.isEmpty()) {
 				try {
@@ -448,10 +448,6 @@ protected Set<String> extractTypes(MultiValueMap<String, Object> attributes) {
 			return extract(attributes, "value", "type");
 		}
 
-		protected Set<String> extractTypeArguments(MultiValueMap<String, Object> attributes) {
-			return extract(attributes, "typeArguments", "typeArgumentNames");
-		}
-
 		private Set<String> extract(MultiValueMap<String, Object> attributes, String... attributeNames) {
 			if (attributes.isEmpty()) {
 				return Collections.emptySet();
@@ -471,16 +467,17 @@ else if (value instanceof String) {
 			return result.isEmpty() ? Collections.emptySet() : result;
 		}
 
-		private Set<ResolvableType> resolveTypes(Set<String> types, Set<String> typeAttributes) {
+		private Set<ResolvableType> resolveTypes(Set<String> types) {
 			if (types.isEmpty()) {
 				return Collections.emptySet();
 			}
 			Set<ResolvableType> resolved = new LinkedHashSet<>(types.size());
-			Set<ResolvableType> resolvedTypeAttributes = resolveTypes(typeAttributes, Collections.emptySet());
 			for (String type : types) {
 				try {
 					Class<?> typeClass = resolve(type, this.classLoader);
-					resolved.add(resolveType(typeClass, resolvedTypeAttributes));
+					ResolvableType resolvableType = typeClass.getTypeParameters().length != 0
+							? ResolvableType.forRawClass(typeClass) : ResolvableType.forClass(typeClass);
+					resolved.add(resolvableType);
 				}
 				catch (ClassNotFoundException | NoClassDefFoundError ex) {
 					resolved.add(ResolvableType.NONE);
@@ -489,13 +486,6 @@ private Set<ResolvableType> resolveTypes(Set<String> types, Set<String> typeAttr
 			return resolved;
 		}
 
-		private ResolvableType resolveType(Class<?> typeClass, Set<ResolvableType> typeAttributes) {
-			if (typeAttributes.isEmpty()) {
-				return ResolvableType.forClass(typeClass);
-			}
-			return ResolvableType.forClassWithGenerics(typeClass, typeAttributes.toArray(new ResolvableType[0]));
-		}
-
 		private void merge(Set<String> result, String... additional) {
 			Collections.addAll(result, additional);
 		}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
index af7fca1a0d9e..4aba7aea5590 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
@@ -236,21 +236,6 @@ void genericWhenTypeArgumentMatches() {
 						exampleBeanRequirement("customExampleBean", "genericStringTypeArgumentsExampleBean")));
 	}
 
-	@Test
-	void genericWhenTypeArgumentNameNotMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithIntegerConfig.class, GenericWithStringTypeArgumentNamesConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean")));
-	}
-
-	@Test
-	void genericWhenTypeArgumentNameMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithStringConfig.class, GenericWithStringTypeArgumentNamesConfig.class)
-				.run((context) -> assertThat(context)
-						.satisfies(exampleBeanRequirement("genericStringExampleBean", "genericStringNameExampleBean")));
-	}
-
 	@Test
 	void genericWhenTypeArgumentWithValueMatches() {
 		this.contextRunner
@@ -293,32 +278,6 @@ void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() {
 						exampleBeanRequirement("customExampleBean", "parameterizedContainerGenericExampleBean")));
 	}
 
-	@Test
-	void parameterizedContainerGenericWithValueWhenTypeArgumentNotMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithIntegerConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean")));
-	}
-
-	@Test
-	void parameterizedContainerGenericWithValueWhenTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithStringConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean",
-						"parameterizedContainerGenericWithValueExampleBean")));
-	}
-
-	@Test
-	void parameterizedContainerGenericWithValueWhenSubclassTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(ParameterizedWithCustomConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean",
-						"parameterizedContainerGenericWithValueExampleBean")));
-	}
-
 	private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
 		return (context) -> {
 			String[] beans = context.getBeanNamesForType(ExampleBean.class);
@@ -624,24 +583,13 @@ ExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() {
 	static class TypeArgumentsConditionWithValueConfig {
 
 		@Bean
-		@ConditionalOnBean(value = ExampleBean.class, typeArguments = String.class)
+		@ConditionalOnBean(value = ExampleBean.class)
 		ExampleBean<String> genericStringWithValueExampleBean() {
 			return new ExampleBean<>("genericStringWithValueExampleBean");
 		}
 
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class GenericWithStringTypeArgumentNamesConfig {
-
-		@Bean
-		@ConditionalOnBean(value = ExampleBean.class, typeArgumentNames = "java.lang.String")
-		ExampleBean<String> genericStringNameExampleBean() {
-			return new ExampleBean<>("genericStringNameExampleBean");
-		}
-
-	}
-
 	@Configuration(proxyBeanMethods = false)
 	static class TypeArgumentsConditionWithParameterizedContainerConfig {
 
@@ -653,18 +601,6 @@ TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExa
 
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class TypeArgumentsConditionWithParameterizedContainerAndValueConfig {
-
-		@Bean
-		@ConditionalOnBean(value = ExampleBean.class, parameterizedContainer = TestParameterizedContainer.class,
-				typeArguments = String.class)
-		TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericWithValueExampleBean() {
-			return new TestParameterizedContainer<>();
-		}
-
-	}
-
 	@TestAnnotation
 	static class ExampleBean<T> {
 
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
index eff4d7c4d445..ba7043e01d7b 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
@@ -365,23 +365,6 @@ void genericWhenSubclassTypeArgumentNotMatches() {
 						.satisfies(exampleBeanRequirement("customExampleBean", "genericIntegerExampleBean")));
 	}
 
-	@Test
-	void genericWhenTypeArgumentNameNotMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class,
-						GenericWithStringTypeArgumentNamesConfig.class)
-				.run((context) -> assertThat(context).satisfies(
-						exampleBeanRequirement("genericIntegerExampleBean", "genericStringNameExampleBean")));
-	}
-
-	@Test
-	void genericWhenTypeArgumentNameMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
-						GenericWithStringTypeArgumentNamesConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean")));
-	}
-
 	@Test
 	void genericWhenTypeArgumentWithValueMatches() {
 		this.contextRunner
@@ -422,31 +405,6 @@ void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() {
 				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
 	}
 
-	@Test
-	void parameterizedContainerGenericWithValueWhenTypeArgumentNotMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean",
-						"parameterizedContainerGenericWithValueExampleBean")));
-	}
-
-	@Test
-	void parameterizedContainerGenericWithValueWhenTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean")));
-	}
-
-	@Test
-	void parameterizedContainerGenericWithValueWhenSubclassTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(ParameterizedWithCustomConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerAndValueConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
-	}
-
 	private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
 		return (context) -> {
 			String[] beans = context.getBeanNamesForType(ExampleBean.class);
@@ -871,24 +829,13 @@ ExampleBean<Integer> genericIntegerExampleBean() {
 	static class TypeArgumentsConditionWithValueConfig {
 
 		@Bean
-		@ConditionalOnMissingBean(value = ExampleBean.class, typeArguments = String.class)
+		@ConditionalOnMissingBean(value = ExampleBean.class)
 		ExampleBean<String> genericStringWithValueExampleBean() {
 			return new ExampleBean<>("genericStringWithValueExampleBean");
 		}
 
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class GenericWithStringTypeArgumentNamesConfig {
-
-		@Bean
-		@ConditionalOnMissingBean(value = ExampleBean.class, typeArgumentNames = "java.lang.String")
-		ExampleBean<String> genericStringNameExampleBean() {
-			return new ExampleBean<>("genericStringNameExampleBean");
-		}
-
-	}
-
 	@Configuration(proxyBeanMethods = false)
 	static class TypeArgumentsConditionWithParameterizedContainerConfig {
 
@@ -900,18 +847,6 @@ TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExa
 
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class TypeArgumentsConditionWithParameterizedContainerAndValueConfig {
-
-		@Bean
-		@ConditionalOnMissingBean(value = ExampleBean.class, parameterizedContainer = TestParameterizedContainer.class,
-				typeArguments = String.class)
-		TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericWithValueExampleBean() {
-			return new TestParameterizedContainer<>();
-		}
-
-	}
-
 	@TestAnnotation
 	static class ExampleBean<T> {
 

From c1c5b2ef2bd7d838d0a4e4fe19bf96e262885650 Mon Sep 17 00:00:00 2001
From: Uladzislau Seuruk <vladislavsevruk@gmail.com>
Date: Fri, 4 Feb 2022 01:49:09 +0200
Subject: [PATCH 4/6] Adjust styles

---
 .../boot/autoconfigure/condition/OnBeanCondition.java | 11 +++++------
 .../condition/ConditionalOnBeanTests.java             |  2 +-
 .../condition/ConditionalOnMissingBeanTests.java      |  2 +-
 3 files changed, 7 insertions(+), 8 deletions(-)

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
index 84e7d053ecef..1bf3d313ddfa 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/condition/OnBeanCondition.java
@@ -210,16 +210,15 @@ private Set<String> getNamesOfBeansIgnoredByType(ListableBeanFactory beanFactory
 		Set<String> result = null;
 		for (Class<?> ignoredType : ignoredTypes) {
 			ResolvableType ignoredResolvableType = ResolvableType.forClass(ignoredType);
-			Collection<String> ignoredNames = getBeanNamesForType(considerHierarchy, beanFactory,
-					ignoredResolvableType, parameterizedContainers);
+			Collection<String> ignoredNames = getBeanNamesForType(considerHierarchy, beanFactory, ignoredResolvableType,
+					parameterizedContainers);
 			result = addAll(result, ignoredNames);
 		}
 		return (result != null) ? result : Collections.emptySet();
 	}
 
-	private Set<String> getBeanNamesForType(boolean considerHierarchy,
-			ListableBeanFactory beanFactory, ResolvableType type, Set<Class<?>> parameterizedContainers)
-			throws LinkageError {
+	private Set<String> getBeanNamesForType(boolean considerHierarchy, ListableBeanFactory beanFactory,
+			ResolvableType type, Set<Class<?>> parameterizedContainers) throws LinkageError {
 		try {
 			return getBeanNamesForType(beanFactory, considerHierarchy, type, parameterizedContainers);
 		}
@@ -475,7 +474,7 @@ private Set<ResolvableType> resolveTypes(Set<String> types) {
 			for (String type : types) {
 				try {
 					Class<?> typeClass = resolve(type, this.classLoader);
-					ResolvableType resolvableType = typeClass.getTypeParameters().length != 0
+					ResolvableType resolvableType = (typeClass.getTypeParameters().length != 0)
 							? ResolvableType.forRawClass(typeClass) : ResolvableType.forClass(typeClass);
 					resolved.add(resolvableType);
 				}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
index 4aba7aea5590..f9a80b18e176 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
@@ -583,7 +583,7 @@ ExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() {
 	static class TypeArgumentsConditionWithValueConfig {
 
 		@Bean
-		@ConditionalOnBean(value = ExampleBean.class)
+		@ConditionalOnBean(ExampleBean.class)
 		ExampleBean<String> genericStringWithValueExampleBean() {
 			return new ExampleBean<>("genericStringWithValueExampleBean");
 		}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
index ba7043e01d7b..9317faacf905 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
@@ -829,7 +829,7 @@ ExampleBean<Integer> genericIntegerExampleBean() {
 	static class TypeArgumentsConditionWithValueConfig {
 
 		@Bean
-		@ConditionalOnMissingBean(value = ExampleBean.class)
+		@ConditionalOnMissingBean(ExampleBean.class)
 		ExampleBean<String> genericStringWithValueExampleBean() {
 			return new ExampleBean<>("genericStringWithValueExampleBean");
 		}

From 1298a434dabae01338253896a90540895f3a4419 Mon Sep 17 00:00:00 2001
From: Uladzislau Seuruk <vladislavsevruk@gmail.com>
Date: Fri, 4 Feb 2022 11:41:28 +0200
Subject: [PATCH 5/6] Extract tests for generic return type to separate classes

---
 ...nditionalOnBeanGenericReturnTypeTests.java | 209 ++++++++++++++++++
 .../condition/ConditionalOnBeanTests.java     | 134 +----------
 ...alOnMissingBeanGenericReturnTypeTests.java | 203 +++++++++++++++++
 .../ConditionalOnMissingBeanTests.java        | 159 +++----------
 4 files changed, 446 insertions(+), 259 deletions(-)
 create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java
 create mode 100644 spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java
new file mode 100644
index 000000000000..e57101e5f4cc
--- /dev/null
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanGenericReturnTypeTests.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright 2012-2020 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
+ *
+ *      https://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.boot.autoconfigure.condition;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.function.Consumer;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.StringUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link ConditionalOnBean @ConditionalOnBean} with generic bean return type.
+ *
+ * @author Uladzislau Seuruk
+ */
+class ConditionalOnBeanGenericReturnTypeTests {
+
+	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
+
+	@Test
+	void genericWhenTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class,
+						GenericWithIntegerTypeArgumentsConfig.class)
+				.run((context) -> assertThat(context).satisfies(
+						exampleBeanRequirement("customExampleBean", "genericStringTypeArgumentsExampleBean")));
+	}
+
+	@Test
+	void genericWhenTypeArgumentWithValueMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringConfig.class, TypeArgumentsConditionWithValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(
+						exampleBeanRequirement("genericStringExampleBean", "genericStringWithValueExampleBean")));
+	}
+
+	@Test
+	void genericWithValueWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class)
+				.run((context) -> assertThat(context)
+						.satisfies(exampleBeanRequirement("customExampleBean", "genericStringWithValueExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenTypeArgumentNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithIntegerConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean",
+						"parameterizedContainerGenericExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(
+						exampleBeanRequirement("customExampleBean", "parameterizedContainerGenericExampleBean")));
+	}
+
+	private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
+		return (context) -> {
+			String[] beans = context.getBeanNamesForType(GenericExampleBean.class);
+			String[] containers = context.getBeanNamesForType(TestParameterizedContainer.class);
+			assertThat(StringUtils.concatenateStringArrays(beans, containers)).containsOnly(names);
+		};
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class ParameterizedWithCustomConfig {
+
+		@Bean
+		CustomExampleBean customExampleBean() {
+			return new CustomExampleBean();
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithStringConfig {
+
+		@Bean
+		GenericExampleBean<String> genericStringExampleBean() {
+			return new GenericExampleBean<>("genericStringExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithStringTypeArgumentsConfig {
+
+		@Bean
+		@ConditionalOnBean
+		GenericExampleBean<String> genericStringTypeArgumentsExampleBean() {
+			return new GenericExampleBean<>("genericStringTypeArgumentsExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithIntegerConfig {
+
+		@Bean
+		GenericExampleBean<Integer> genericIntegerExampleBean() {
+			return new GenericExampleBean<>(1_000);
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithIntegerTypeArgumentsConfig {
+
+		@Bean
+		@ConditionalOnBean
+		GenericExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() {
+			return new GenericExampleBean<>(1_000);
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class TypeArgumentsConditionWithValueConfig {
+
+		@Bean
+		@ConditionalOnBean(GenericExampleBean.class)
+		GenericExampleBean<String> genericStringWithValueExampleBean() {
+			return new GenericExampleBean<>("genericStringWithValueExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class TypeArgumentsConditionWithParameterizedContainerConfig {
+
+		@Bean
+		@ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class)
+		TestParameterizedContainer<GenericExampleBean<String>> parameterizedContainerGenericExampleBean() {
+			return new TestParameterizedContainer<>();
+		}
+
+	}
+
+	@TestAnnotation
+	static class GenericExampleBean<T> {
+
+		private final T value;
+
+		GenericExampleBean(T value) {
+			this.value = value;
+		}
+
+		@Override
+		public String toString() {
+			return String.valueOf(this.value);
+		}
+
+	}
+
+	static class CustomExampleBean extends GenericExampleBean<String> {
+
+		CustomExampleBean() {
+			super("custom subclass");
+		}
+
+	}
+
+	@Target(ElementType.TYPE)
+	@Retention(RetentionPolicy.RUNTIME)
+	@Documented
+	@interface TestAnnotation {
+
+	}
+
+}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
index f9a80b18e176..5bfb79e00d00 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnBeanTests.java
@@ -51,7 +51,6 @@
  *
  * @author Dave Syer
  * @author Stephane Nicoll
- * @author Uladzislau Seuruk
  */
 class ConditionalOnBeanTests {
 
@@ -227,57 +226,6 @@ void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistratio
 						.satisfies(exampleBeanRequirement("customExampleBean", "conditionalCustomExampleBean")));
 	}
 
-	@Test
-	void genericWhenTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class,
-						GenericWithIntegerTypeArgumentsConfig.class)
-				.run((context) -> assertThat(context).satisfies(
-						exampleBeanRequirement("customExampleBean", "genericStringTypeArgumentsExampleBean")));
-	}
-
-	@Test
-	void genericWhenTypeArgumentWithValueMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithStringConfig.class, TypeArgumentsConditionWithValueConfig.class)
-				.run((context) -> assertThat(context).satisfies(
-						exampleBeanRequirement("genericStringExampleBean", "genericStringWithValueExampleBean")));
-	}
-
-	@Test
-	void genericWithValueWhenSubclassTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class)
-				.run((context) -> assertThat(context)
-						.satisfies(exampleBeanRequirement("customExampleBean", "genericStringWithValueExampleBean")));
-	}
-
-	@Test
-	void parameterizedContainerGenericWhenTypeArgumentNotMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithIntegerConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean")));
-	}
-
-	@Test
-	void parameterizedContainerGenericWhenTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithStringConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean",
-						"parameterizedContainerGenericExampleBean")));
-	}
-
-	@Test
-	void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(ParameterizedWithCustomConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerConfig.class)
-				.run((context) -> assertThat(context).satisfies(
-						exampleBeanRequirement("customExampleBean", "parameterizedContainerGenericExampleBean")));
-	}
-
 	private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
 		return (context) -> {
 			String[] beans = context.getBeanNamesForType(ExampleBean.class);
@@ -415,11 +363,11 @@ public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
 
 	}
 
-	static class ExampleFactoryBean implements FactoryBean<ExampleBean<String>> {
+	static class ExampleFactoryBean implements FactoryBean<ExampleBean> {
 
 		@Override
-		public ExampleBean<String> getObject() {
-			return new ExampleBean<>("fromFactory");
+		public ExampleBean getObject() {
+			return new ExampleBean("fromFactory");
 		}
 
 		@Override
@@ -537,87 +485,23 @@ TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() {
 
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class GenericWithStringConfig {
-
-		@Bean
-		ExampleBean<String> genericStringExampleBean() {
-			return new ExampleBean<>("genericStringExampleBean");
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	static class GenericWithStringTypeArgumentsConfig {
-
-		@Bean
-		@ConditionalOnBean
-		ExampleBean<String> genericStringTypeArgumentsExampleBean() {
-			return new ExampleBean<>("genericStringTypeArgumentsExampleBean");
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	static class GenericWithIntegerConfig {
-
-		@Bean
-		ExampleBean<Integer> genericIntegerExampleBean() {
-			return new ExampleBean<>(1_000);
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	static class GenericWithIntegerTypeArgumentsConfig {
-
-		@Bean
-		@ConditionalOnBean
-		ExampleBean<Integer> genericIntegerTypeArgumentsExampleBean() {
-			return new ExampleBean<>(1_000);
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	static class TypeArgumentsConditionWithValueConfig {
-
-		@Bean
-		@ConditionalOnBean(ExampleBean.class)
-		ExampleBean<String> genericStringWithValueExampleBean() {
-			return new ExampleBean<>("genericStringWithValueExampleBean");
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	static class TypeArgumentsConditionWithParameterizedContainerConfig {
-
-		@Bean
-		@ConditionalOnBean(parameterizedContainer = TestParameterizedContainer.class)
-		TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() {
-			return new TestParameterizedContainer<>();
-		}
-
-	}
-
 	@TestAnnotation
-	static class ExampleBean<T> {
+	static class ExampleBean {
 
-		private final T value;
+		private String value;
 
-		ExampleBean(T value) {
+		ExampleBean(String value) {
 			this.value = value;
 		}
 
 		@Override
 		public String toString() {
-			return String.valueOf(this.value);
+			return this.value;
 		}
 
 	}
 
-	static class CustomExampleBean extends ExampleBean<String> {
+	static class CustomExampleBean extends ExampleBean {
 
 		CustomExampleBean() {
 			super("custom subclass");
@@ -625,7 +509,7 @@ static class CustomExampleBean extends ExampleBean<String> {
 
 	}
 
-	static class OtherExampleBean extends ExampleBean<String> {
+	static class OtherExampleBean extends ExampleBean {
 
 		OtherExampleBean() {
 			super("other subclass");
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java
new file mode 100644
index 000000000000..31298c3b1e4d
--- /dev/null
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2012-2021 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
+ *
+ *      https://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.boot.autoconfigure.condition;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.function.Consumer;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.util.StringUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Tests for {@link ConditionalOnMissingBean @ConditionalOnMissingBean} with generic bean return type.
+ *
+ * @author Uladzislau Seuruk
+ */
+@SuppressWarnings("resource")
+class ConditionalOnMissingBeanGenericReturnTypeTests {
+
+	private final ApplicationContextRunner contextRunner = new ApplicationContextRunner();
+
+	@Test
+	void genericWhenTypeArgumentNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
+						GenericWithIntegerTypeArgumentsConfig.class)
+				.run((context) -> assertThat(context)
+						.satisfies(exampleBeanRequirement("genericStringExampleBean", "genericIntegerExampleBean")));
+	}
+
+	@Test
+	void genericWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
+	}
+
+	@Test
+	void genericWhenSubclassTypeArgumentNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithIntegerTypeArgumentsConfig.class)
+				.run((context) -> assertThat(context)
+						.satisfies(exampleBeanRequirement("customExampleBean", "genericIntegerExampleBean")));
+	}
+
+	@Test
+	void genericWhenTypeArgumentWithValueMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
+						TypeArgumentsConditionWithValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean")));
+	}
+
+	@Test
+	void genericWithValueWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenTypeArgumentNotMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean",
+						"parameterizedContainerGenericExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean")));
+	}
+
+	@Test
+	void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() {
+		this.contextRunner
+				.withUserConfiguration(ParameterizedWithCustomConfig.class,
+						TypeArgumentsConditionWithParameterizedContainerConfig.class)
+				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
+	}
+
+	private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
+		return (context) -> {
+			String[] beans = context.getBeanNamesForType(GenericExampleBean.class);
+			String[] containers = context.getBeanNamesForType(TestParameterizedContainer.class);
+			assertThat(StringUtils.concatenateStringArrays(beans, containers)).containsOnly(names);
+		};
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class ParameterizedWithCustomConfig {
+
+		@Bean
+		CustomExampleBean customExampleBean() {
+			return new CustomExampleBean();
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithStringTypeArgumentsConfig {
+
+		@Bean
+		@ConditionalOnMissingBean
+		GenericExampleBean<String> genericStringExampleBean() {
+			return new GenericExampleBean<>("genericStringExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class GenericWithIntegerTypeArgumentsConfig {
+
+		@Bean
+		@ConditionalOnMissingBean
+		GenericExampleBean<Integer> genericIntegerExampleBean() {
+			return new GenericExampleBean<>(1_000);
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class TypeArgumentsConditionWithValueConfig {
+
+		@Bean
+		@ConditionalOnMissingBean(GenericExampleBean.class)
+		GenericExampleBean<String> genericStringWithValueExampleBean() {
+			return new GenericExampleBean<>("genericStringWithValueExampleBean");
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class TypeArgumentsConditionWithParameterizedContainerConfig {
+
+		@Bean
+		@ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class)
+		TestParameterizedContainer<GenericExampleBean<String>> parameterizedContainerGenericExampleBean() {
+			return new TestParameterizedContainer<>();
+		}
+
+	}
+
+	@TestAnnotation
+	static class GenericExampleBean<T> {
+
+		private final T value;
+
+		GenericExampleBean(T value) {
+			this.value = value;
+		}
+
+		@Override
+		public String toString() {
+			return String.valueOf(this.value);
+		}
+
+	}
+
+	static class CustomExampleBean extends GenericExampleBean<String> {
+
+		CustomExampleBean() {
+			super("custom subclass");
+		}
+
+	}
+
+	@Target(ElementType.TYPE)
+	@Retention(RetentionPolicy.RUNTIME)
+	@Documented
+	@interface TestAnnotation {
+
+	}
+
+}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
index 9317faacf905..f7087dd2331c 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanTests.java
@@ -59,7 +59,6 @@
  * @author Phillip Webb
  * @author Jakub Kubrynski
  * @author Andy Wilkinson
- * @author Uladzislau Seuruk
  */
 @SuppressWarnings("resource")
 class ConditionalOnMissingBeanTests {
@@ -341,70 +340,6 @@ void parameterizedContainerWhenReturnRegistrationTypeIsOfExistingBeanRegistratio
 				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
 	}
 
-	@Test
-	void genericWhenTypeArgumentNotMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
-						GenericWithIntegerTypeArgumentsConfig.class)
-				.run((context) -> assertThat(context)
-						.satisfies(exampleBeanRequirement("genericStringExampleBean", "genericIntegerExampleBean")));
-	}
-
-	@Test
-	void genericWhenSubclassTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithStringTypeArgumentsConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
-	}
-
-	@Test
-	void genericWhenSubclassTypeArgumentNotMatches() {
-		this.contextRunner
-				.withUserConfiguration(ParameterizedWithCustomConfig.class, GenericWithIntegerTypeArgumentsConfig.class)
-				.run((context) -> assertThat(context)
-						.satisfies(exampleBeanRequirement("customExampleBean", "genericIntegerExampleBean")));
-	}
-
-	@Test
-	void genericWhenTypeArgumentWithValueMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
-						TypeArgumentsConditionWithValueConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean")));
-	}
-
-	@Test
-	void genericWithValueWhenSubclassTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(ParameterizedWithCustomConfig.class, TypeArgumentsConditionWithValueConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
-	}
-
-	@Test
-	void parameterizedContainerGenericWhenTypeArgumentNotMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithIntegerTypeArgumentsConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericIntegerExampleBean",
-						"parameterizedContainerGenericExampleBean")));
-	}
-
-	@Test
-	void parameterizedContainerGenericWhenTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(GenericWithStringTypeArgumentsConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("genericStringExampleBean")));
-	}
-
-	@Test
-	void parameterizedContainerGenericWhenSubclassTypeArgumentMatches() {
-		this.contextRunner
-				.withUserConfiguration(ParameterizedWithCustomConfig.class,
-						TypeArgumentsConditionWithParameterizedContainerConfig.class)
-				.run((context) -> assertThat(context).satisfies(exampleBeanRequirement("customExampleBean")));
-	}
-
 	private Consumer<ConfigurableApplicationContext> exampleBeanRequirement(String... names) {
 		return (context) -> {
 			String[] beans = context.getBeanNamesForType(ExampleBean.class);
@@ -418,8 +353,8 @@ static class OnBeanInAncestorsConfiguration {
 
 		@Bean
 		@ConditionalOnMissingBean(search = SearchStrategy.ANCESTORS)
-		ExampleBean<String> exampleBean2() {
-			return new ExampleBean<String>("test");
+		ExampleBean exampleBean2() {
+			return new ExampleBean("test");
 		}
 
 	}
@@ -451,7 +386,7 @@ String bar() {
 	static class FactoryBeanConfiguration {
 
 		@Bean
-		FactoryBean<ExampleBean<String>> exampleBeanFactoryBean() {
+		FactoryBean<ExampleBean> exampleBeanFactoryBean() {
 			return new ExampleFactoryBean("foo");
 		}
 
@@ -477,7 +412,7 @@ static class ComponentScannedFactoryBeanBeanMethodWithArgumentsConfiguration {
 	static class FactoryBeanWithBeanMethodArgumentsConfiguration {
 
 		@Bean
-		FactoryBean<ExampleBean<String>> exampleBeanFactoryBean(@Value("${theValue}") String value) {
+		FactoryBean<ExampleBean> exampleBeanFactoryBean(@Value("${theValue}") String value) {
 			return new ExampleFactoryBean(value);
 		}
 
@@ -568,8 +503,8 @@ static class ConditionalOnFactoryBean {
 
 		@Bean
 		@ConditionalOnMissingBean(ExampleBean.class)
-		ExampleBean<String> createExampleBean() {
-			return new ExampleBean<>("direct");
+		ExampleBean createExampleBean() {
+			return new ExampleBean("direct");
 		}
 
 	}
@@ -579,8 +514,8 @@ static class ConditionalOnIgnoredSubclass {
 
 		@Bean
 		@ConditionalOnMissingBean(value = ExampleBean.class, ignored = CustomExampleBean.class)
-		ExampleBean<String> exampleBean() {
-			return new ExampleBean<>("test");
+		ExampleBean exampleBean() {
+			return new ExampleBean("test");
 		}
 
 	}
@@ -591,8 +526,8 @@ static class ConditionalOnIgnoredSubclassByName {
 		@Bean
 		@ConditionalOnMissingBean(value = ExampleBean.class,
 				ignoredType = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBeanTests$CustomExampleBean")
-		ExampleBean<String> exampleBean() {
-			return new ExampleBean<>("test");
+		ExampleBean exampleBean() {
+			return new ExampleBean("test");
 		}
 
 	}
@@ -666,8 +601,8 @@ String bar() {
 	static class ExampleBeanConfiguration {
 
 		@Bean
-		ExampleBean<String> exampleBean() {
-			return new ExampleBean<>("test");
+		ExampleBean exampleBean() {
+			return new ExampleBean("test");
 		}
 
 	}
@@ -677,21 +612,21 @@ static class ImpliedOnBeanMethod {
 
 		@Bean
 		@ConditionalOnMissingBean
-		ExampleBean<String> exampleBean2() {
-			return new ExampleBean<>("test");
+		ExampleBean exampleBean2() {
+			return new ExampleBean("test");
 		}
 
 	}
 
-	static class ExampleFactoryBean implements FactoryBean<ExampleBean<String>> {
+	static class ExampleFactoryBean implements FactoryBean<ExampleBean> {
 
 		ExampleFactoryBean(String value) {
 			Assert.state(!value.contains("$"), "value should not contain '$'");
 		}
 
 		@Override
-		public ExampleBean<String> getObject() {
-			return new ExampleBean<>("fromFactory");
+		public ExampleBean getObject() {
+			return new ExampleBean("fromFactory");
 		}
 
 		@Override
@@ -713,8 +648,8 @@ static class NonspecificFactoryBean implements FactoryBean<Object> {
 		}
 
 		@Override
-		public ExampleBean<String> getObject() {
-			return new ExampleBean<>("fromFactory");
+		public ExampleBean getObject() {
+			return new ExampleBean("fromFactory");
 		}
 
 		@Override
@@ -803,67 +738,23 @@ TestParameterizedContainer<CustomExampleBean> conditionalCustomExampleBean() {
 
 	}
 
-	@Configuration(proxyBeanMethods = false)
-	static class GenericWithStringTypeArgumentsConfig {
-
-		@Bean
-		@ConditionalOnMissingBean
-		ExampleBean<String> genericStringExampleBean() {
-			return new ExampleBean<>("genericStringExampleBean");
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	static class GenericWithIntegerTypeArgumentsConfig {
-
-		@Bean
-		@ConditionalOnMissingBean
-		ExampleBean<Integer> genericIntegerExampleBean() {
-			return new ExampleBean<>(1_000);
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	static class TypeArgumentsConditionWithValueConfig {
-
-		@Bean
-		@ConditionalOnMissingBean(ExampleBean.class)
-		ExampleBean<String> genericStringWithValueExampleBean() {
-			return new ExampleBean<>("genericStringWithValueExampleBean");
-		}
-
-	}
-
-	@Configuration(proxyBeanMethods = false)
-	static class TypeArgumentsConditionWithParameterizedContainerConfig {
-
-		@Bean
-		@ConditionalOnMissingBean(parameterizedContainer = TestParameterizedContainer.class)
-		TestParameterizedContainer<ExampleBean<String>> parameterizedContainerGenericExampleBean() {
-			return new TestParameterizedContainer<>();
-		}
-
-	}
-
 	@TestAnnotation
-	static class ExampleBean<T> {
+	static class ExampleBean {
 
-		private final T value;
+		private String value;
 
-		ExampleBean(T value) {
+		ExampleBean(String value) {
 			this.value = value;
 		}
 
 		@Override
 		public String toString() {
-			return String.valueOf(this.value);
+			return this.value;
 		}
 
 	}
 
-	static class CustomExampleBean extends ExampleBean<String> {
+	static class CustomExampleBean extends ExampleBean {
 
 		CustomExampleBean() {
 			super("custom subclass");
@@ -871,7 +762,7 @@ static class CustomExampleBean extends ExampleBean<String> {
 
 	}
 
-	static class OtherExampleBean extends ExampleBean<String> {
+	static class OtherExampleBean extends ExampleBean {
 
 		OtherExampleBean() {
 			super("other subclass");

From bc2db920a97acbef1b7f17e7314ee93c82fbd03a Mon Sep 17 00:00:00 2001
From: Uladzislau Seuruk <vladislavsevruk@gmail.com>
Date: Fri, 4 Feb 2022 12:09:00 +0200
Subject: [PATCH 6/6] Adjust styles

---
 .../ConditionalOnMissingBeanGenericReturnTypeTests.java        | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java
index 31298c3b1e4d..d9b5178b7879 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/condition/ConditionalOnMissingBeanGenericReturnTypeTests.java
@@ -34,7 +34,8 @@
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
- * Tests for {@link ConditionalOnMissingBean @ConditionalOnMissingBean} with generic bean return type.
+ * Tests for {@link ConditionalOnMissingBean @ConditionalOnMissingBean} with generic bean
+ * return type.
  *
  * @author Uladzislau Seuruk
  */