From 03cd38f1b1393bda5f2490a95e12dd64580689d6 Mon Sep 17 00:00:00 2001 From: currenjin Date: Wed, 5 Mar 2025 10:24:23 +0900 Subject: [PATCH 1/4] Fix: type matching for request-scope generic beans Signed-off-by: currenjin --- .../factory/support/AbstractBeanFactory.java | 22 +++++++++ .../support/GenericTypeMatchingTests.java | 46 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index 32af62487c5e..ede0a1469ed3 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.function.UnaryOperator; import org.springframework.beans.BeanUtils; @@ -583,6 +584,27 @@ else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) { // Generics potentially only match on the target class, not on the proxy... RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName); Class targetType = mbd.getTargetType(); + + String scope = mbd.getScope(); + if (targetType == null && scope != null && !scope.isEmpty()) { + String targetBeanName = "scopedTarget." + beanName; + if (containsBeanDefinition(targetBeanName)) { + RootBeanDefinition targetMbd = getMergedLocalBeanDefinition(targetBeanName); + + ResolvableType targetResolvableType = targetMbd.targetType; + if (targetResolvableType == null) { + targetResolvableType = targetMbd.factoryMethodReturnType; + } + if (targetResolvableType == null) { + targetResolvableType = ResolvableType.forClass(targetMbd.getBeanClass()); + } + + if (typeToMatch.isAssignableFrom(targetResolvableType)) { + return true; + } + } + } + if (targetType != null && targetType != ClassUtils.getUserClass(beanInstance)) { // Check raw class match as well, making sure it's exposed on the proxy. Class classToMatch = typeToMatch.resolve(); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java new file mode 100644 index 000000000000..9678a25f597e --- /dev/null +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java @@ -0,0 +1,46 @@ +package org.springframework.beans.factory.support; + +import org.junit.jupiter.api.Test; +import org.springframework.core.ResolvableType; + +import java.util.function.Supplier; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AbstractBeanFactory#isTypeMatch} with scoped proxy beans that use generic types. + */ +class ScopedProxyGenericTypeMatchTests { + + @Test + void scopedProxyBeanTypeMatching() { + DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + + RootBeanDefinition targetDef = new RootBeanDefinition(SomeGenericSupplier.class); + targetDef.setScope("request"); + factory.registerBeanDefinition("scopedTarget.wordBean", targetDef); + + RootBeanDefinition proxyDef = new RootBeanDefinition(); + proxyDef.setScope("singleton"); + proxyDef.setTargetType(ResolvableType.forClassWithGenerics(Supplier.class, String.class)); + proxyDef.setAttribute("targetBeanName", "scopedTarget.wordBean"); + factory.registerBeanDefinition("wordBean", proxyDef); + + ResolvableType supplierType = ResolvableType.forClassWithGenerics(Supplier.class, String.class); + + boolean isMatch = factory.isTypeMatch("wordBean", supplierType); + + + assertThat(isMatch).isTrue(); + + String[] names = factory.getBeanNamesForType(supplierType); + assertThat(names).contains("wordBean"); + } + + static class SomeGenericSupplier implements Supplier { + @Override + public String get() { + return "value"; + } + } +} \ No newline at end of file From 8c06f4a9d62ad584ae742b33116ce42cb6001cfd Mon Sep 17 00:00:00 2001 From: currenjin Date: Thu, 13 Mar 2025 09:20:12 +0900 Subject: [PATCH 2/4] Improve: resolvable type extraction logic in isTypeMatch method Signed-off-by: currenjin --- .../beans/factory/support/AbstractBeanFactory.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java index ede0a1469ed3..c488bc61766b 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanFactory.java @@ -594,9 +594,9 @@ else if (typeToMatch.hasGenerics() && containsBeanDefinition(beanName)) { ResolvableType targetResolvableType = targetMbd.targetType; if (targetResolvableType == null) { targetResolvableType = targetMbd.factoryMethodReturnType; - } - if (targetResolvableType == null) { - targetResolvableType = ResolvableType.forClass(targetMbd.getBeanClass()); + if (targetResolvableType == null) { + targetResolvableType = ResolvableType.forClass(targetMbd.getBeanClass()); + } } if (typeToMatch.isAssignableFrom(targetResolvableType)) { From 7df2e55b562ac7383a83afd47e20531b522ecfac Mon Sep 17 00:00:00 2001 From: currenjin Date: Thu, 13 Mar 2025 09:23:10 +0900 Subject: [PATCH 3/4] Refactor: test to use variables for bean names Signed-off-by: currenjin --- .../factory/support/GenericTypeMatchingTests.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java index 9678a25f597e..9ab7ca294829 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java @@ -16,25 +16,26 @@ class ScopedProxyGenericTypeMatchTests { void scopedProxyBeanTypeMatching() { DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); + String targetBeanName = "scopedTarget.wordBean"; + String proxyBeanName = "wordBean"; + RootBeanDefinition targetDef = new RootBeanDefinition(SomeGenericSupplier.class); targetDef.setScope("request"); - factory.registerBeanDefinition("scopedTarget.wordBean", targetDef); + factory.registerBeanDefinition(targetBeanName, targetDef); RootBeanDefinition proxyDef = new RootBeanDefinition(); proxyDef.setScope("singleton"); proxyDef.setTargetType(ResolvableType.forClassWithGenerics(Supplier.class, String.class)); - proxyDef.setAttribute("targetBeanName", "scopedTarget.wordBean"); - factory.registerBeanDefinition("wordBean", proxyDef); + proxyDef.setAttribute("targetBeanName", targetBeanName); + factory.registerBeanDefinition(proxyBeanName, proxyDef); ResolvableType supplierType = ResolvableType.forClassWithGenerics(Supplier.class, String.class); - boolean isMatch = factory.isTypeMatch("wordBean", supplierType); - - + boolean isMatch = factory.isTypeMatch(proxyBeanName, supplierType); assertThat(isMatch).isTrue(); String[] names = factory.getBeanNamesForType(supplierType); - assertThat(names).contains("wordBean"); + assertThat(names).contains(proxyBeanName); } static class SomeGenericSupplier implements Supplier { From ad0ddb71d41efdf7527664df04edd1df43190246 Mon Sep 17 00:00:00 2001 From: currenjin Date: Thu, 13 Mar 2025 19:24:49 +0900 Subject: [PATCH 4/4] Improve: test with direct assertions and randomized bean names Signed-off-by: currenjin --- .../factory/support/GenericTypeMatchingTests.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java index 9ab7ca294829..7a7fb7fd380e 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/support/GenericTypeMatchingTests.java @@ -3,6 +3,7 @@ import org.junit.jupiter.api.Test; import org.springframework.core.ResolvableType; +import java.util.UUID; import java.util.function.Supplier; import static org.assertj.core.api.Assertions.assertThat; @@ -16,8 +17,8 @@ class ScopedProxyGenericTypeMatchTests { void scopedProxyBeanTypeMatching() { DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); - String targetBeanName = "scopedTarget.wordBean"; - String proxyBeanName = "wordBean"; + String proxyBeanName = "wordBean-" + UUID.randomUUID(); + String targetBeanName = "scopedTarget." + proxyBeanName; RootBeanDefinition targetDef = new RootBeanDefinition(SomeGenericSupplier.class); targetDef.setScope("request"); @@ -31,11 +32,9 @@ void scopedProxyBeanTypeMatching() { ResolvableType supplierType = ResolvableType.forClassWithGenerics(Supplier.class, String.class); - boolean isMatch = factory.isTypeMatch(proxyBeanName, supplierType); - assertThat(isMatch).isTrue(); + assertThat(factory.isTypeMatch(proxyBeanName, supplierType)).isTrue(); - String[] names = factory.getBeanNamesForType(supplierType); - assertThat(names).contains(proxyBeanName); + assertThat(factory.getBeanNamesForType(supplierType)).contains(proxyBeanName); } static class SomeGenericSupplier implements Supplier {