From 0e9eab55ceff1a7b4f8720f2c3725aaac2bedb48 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 8 Nov 2022 20:22:12 +0100 Subject: [PATCH] Unwrap nested generic type within FactoryBean target type if necessary Closes gh-29385 --- ...ricTypeAwareAutowireCandidateResolver.java | 13 ++- ...notationConfigApplicationContextTests.java | 82 ++++++++++++++++--- 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java index c291046df364..e8e14fd81c8c 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/GenericTypeAwareAutowireCandidateResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 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. @@ -102,6 +102,17 @@ protected boolean checkGenericTypeMatch(BeanDefinitionHolder bdHolder, Dependenc } } } + else { + // Pre-existing target type: In case of a generic FactoryBean type, + // unwrap nested generic type when matching a non-FactoryBean type. + Class resolvedClass = targetType.resolve(); + if (resolvedClass != null && FactoryBean.class.isAssignableFrom(resolvedClass)) { + Class typeToBeMatched = dependencyType.resolve(); + if (typeToBeMatched != null && !FactoryBean.class.isAssignableFrom(typeToBeMatched)) { + targetType = targetType.getGeneric(); + } + } + } } if (targetType == null) { diff --git a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java index d5ba2a49b8a8..d11db0c30672 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java @@ -16,7 +16,9 @@ package org.springframework.context.annotation; +import java.util.Collections; import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; import org.junit.jupiter.api.Test; @@ -372,8 +374,8 @@ void individualBeanWithFactoryBeanSupplier() { context.registerBean("fb", NonInstantiatedFactoryBean.class, NonInstantiatedFactoryBean::new, bd -> bd.setLazyInit(true)); context.refresh(); - assertThat(context.getType("fb")).isEqualTo(String.class); assertThat(context.getType("&fb")).isEqualTo(NonInstantiatedFactoryBean.class); + assertThat(context.getType("fb")).isEqualTo(String.class); assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(1); assertThat(context.getBeanNamesForType(NonInstantiatedFactoryBean.class)).hasSize(1); } @@ -388,25 +390,55 @@ void individualBeanWithFactoryBeanSupplierAndTargetType() { context.registerBeanDefinition("fb", bd); context.refresh(); - assertThat(context.getType("fb")).isEqualTo(String.class); assertThat(context.getType("&fb")).isEqualTo(FactoryBean.class); + assertThat(context.getType("fb")).isEqualTo(String.class); assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(1); assertThat(context.getBeanNamesForType(NonInstantiatedFactoryBean.class)).isEmpty(); } + @Test + void individualBeanWithFactoryBeanTypeAsTargetType() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + RootBeanDefinition bd1 = new RootBeanDefinition(); + bd1.setBeanClass(SetFactoryBean.class); + bd1.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class, ResolvableType.forClassWithGenerics(Set.class, String.class))); + bd1.setLazyInit(true); + context.registerBeanDefinition("fb1", bd1); + RootBeanDefinition bd2 = new RootBeanDefinition(); + bd2.setBeanClass(UntypedFactoryBean.class); + bd2.setTargetType(ResolvableType.forClassWithGenerics(FactoryBean.class, ResolvableType.forClassWithGenerics(Set.class, Integer.class))); + bd2.setLazyInit(true); + context.registerBeanDefinition("fb2", bd2); + context.registerBeanDefinition("ip", new RootBeanDefinition(FactoryBeanInjectionPoints.class)); + context.refresh(); + + assertThat(context.getType("&fb1")).isEqualTo(SetFactoryBean.class); + assertThat(context.getType("fb1")).isEqualTo(Set.class); + assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(2); + assertThat(context.getBeanNamesForType(SetFactoryBean.class)).hasSize(1); + assertThat(context.getBean("ip", FactoryBeanInjectionPoints.class).factoryBean).isSameAs(context.getBean("&fb1")); + assertThat(context.getBean("ip", FactoryBeanInjectionPoints.class).factoryResult).isSameAs(context.getBean("fb1")); + } + @Test void individualBeanWithFactoryBeanObjectTypeAsTargetType() { AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); - RootBeanDefinition bd = new RootBeanDefinition(); - bd.setBeanClass(TypedFactoryBean.class); - bd.setTargetType(String.class); - context.registerBeanDefinition("fb", bd); + RootBeanDefinition bd1 = new RootBeanDefinition(); + bd1.setBeanClass(SetFactoryBean.class); + bd1.setTargetType(ResolvableType.forClassWithGenerics(Set.class, String.class)); + context.registerBeanDefinition("fb1", bd1); + RootBeanDefinition bd2 = new RootBeanDefinition(); + bd2.setBeanClass(UntypedFactoryBean.class); + bd2.setTargetType(ResolvableType.forClassWithGenerics(Set.class, Integer.class)); + context.registerBeanDefinition("fb2", bd2); + context.registerBeanDefinition("ip", new RootBeanDefinition(FactoryResultInjectionPoint.class)); context.refresh(); - assertThat(context.getType("&fb")).isEqualTo(TypedFactoryBean.class); - assertThat(context.getType("fb")).isEqualTo(String.class); - assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(1); - assertThat(context.getBeanNamesForType(TypedFactoryBean.class)).hasSize(1); + assertThat(context.getType("&fb1")).isEqualTo(SetFactoryBean.class); + assertThat(context.getType("fb1")).isEqualTo(Set.class); + assertThat(context.getBeanNamesForType(FactoryBean.class)).hasSize(2); + assertThat(context.getBeanNamesForType(SetFactoryBean.class)).hasSize(1); + assertThat(context.getBean("ip", FactoryResultInjectionPoint.class).factoryResult).isSameAs(context.getBean("fb1")); } @Test @@ -630,6 +662,36 @@ public boolean isSingleton() { return false; } } + + static class SetFactoryBean implements FactoryBean> { + + @Override + public Set getObject() { + return Collections.emptySet(); + } + + @Override + public Class getObjectType() { + return Set.class; + } + + @Override + public boolean isSingleton() { + return true; + } + } + + static class FactoryResultInjectionPoint { + + @Autowired + Set factoryResult; + } + + static class FactoryBeanInjectionPoints extends FactoryResultInjectionPoint { + + @Autowired + FactoryBean> factoryBean; + } } class TestBean {