diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java b/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java index 350289ea247d..28d7a68c7d1a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragments.java @@ -72,23 +72,45 @@ class DefaultBeanRegistrationCodeFragments extends BeanRegistrationCodeFragments public Class getTarget(RegisteredBean registeredBean, Executable constructorOrFactoryMethod) { - Class target = extractDeclaringClass(constructorOrFactoryMethod); + Class target = extractDeclaringClass(registeredBean.getBeanType(), + constructorOrFactoryMethod); while (target.getName().startsWith("java.") && registeredBean.isInnerBean()) { target = registeredBean.getParent().getBeanClass(); } return target; } - private Class extractDeclaringClass(Executable executable) { + private Class extractDeclaringClass(ResolvableType beanType, Executable executable) { Class declaringClass = ClassUtils.getUserClass(executable.getDeclaringClass()); if (executable instanceof Constructor && AccessVisibility.forMember(executable) == AccessVisibility.PUBLIC && FactoryBean.class.isAssignableFrom(declaringClass)) { - return ResolvableType.forType(declaringClass).as(FactoryBean.class).getGeneric(0).toClass(); + return extractTargetClassFromFactoryBean(declaringClass, beanType); } return executable.getDeclaringClass(); } + /** + * Extract the target class of a public {@link FactoryBean} based on its + * constructor. If the implementation does not resolve the target class + * because it itself uses a generic, attempt to extract it from the + * bean type. + * @param factoryBeanType the factory bean type + * @param beanType the bean type + * @return the target class to use + */ + private Class extractTargetClassFromFactoryBean(Class factoryBeanType, ResolvableType beanType) { + ResolvableType target = ResolvableType.forType(factoryBeanType) + .as(FactoryBean.class).getGeneric(0); + if (target.getType().equals(Class.class)) { + return target.toClass(); + } + else if (factoryBeanType.isAssignableFrom(beanType.toClass())) { + return beanType.as(FactoryBean.class).getGeneric(0).toClass(); + } + return beanType.toClass(); + } + @Override public CodeBlock generateNewBeanDefinitionCode(GenerationContext generationContext, ResolvableType beanType, BeanRegistrationCode beanRegistrationCode) { diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragmentsTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragmentsTests.java index 35abd77c1e85..85ad9ed1b8b8 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragmentsTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/aot/DefaultBeanRegistrationCodeFragmentsTests.java @@ -27,6 +27,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.testfixture.beans.factory.DummyFactory; import org.springframework.beans.testfixture.beans.factory.aot.MockBeanRegistrationsCode; +import org.springframework.core.ResolvableType; import org.springframework.core.testfixture.aot.generate.TestGenerationContext; import org.springframework.util.ReflectionUtils; @@ -57,6 +58,29 @@ void getTargetOnConstructorToPublicFactoryBean() { SimpleBeanFactoryBean.class.getDeclaredConstructors()[0])).isEqualTo(SimpleBean.class); } + @Test + void getTargetOnConstructorToPublicGenericFactoryBeanExtractTargetFromFactoryBeanType() { + RegisteredBean registeredBean = registerTestBean(ResolvableType + .forClassWithGenerics(GenericFactoryBean.class, SimpleBean.class)); + assertThat(createInstance(registeredBean).getTarget(registeredBean, + GenericFactoryBean.class.getDeclaredConstructors()[0])).isEqualTo(SimpleBean.class); + } + + @Test + void getTargetOnConstructorToPublicGenericFactoryBeanWithBoundExtractTargetFromFactoryBeanType() { + RegisteredBean registeredBean = registerTestBean(ResolvableType + .forClassWithGenerics(NumberFactoryBean.class, Integer.class)); + assertThat(createInstance(registeredBean).getTarget(registeredBean, + NumberFactoryBean.class.getDeclaredConstructors()[0])).isEqualTo(Integer.class); + } + + @Test + void getTargetOnConstructorToPublicGenericFactoryBeanUseBeanTypeAsFallback() { + RegisteredBean registeredBean = registerTestBean(SimpleBean.class); + assertThat(createInstance(registeredBean).getTarget(registeredBean, + GenericFactoryBean.class.getDeclaredConstructors()[0])).isEqualTo(SimpleBean.class); + } + @Test void getTargetOnConstructorToProtectedFactoryBean() { RegisteredBean registeredBean = registerTestBean(SimpleBean.class); @@ -138,6 +162,12 @@ private RegisteredBean registerTestBean(Class beanType) { return RegisteredBean.of(this.beanFactory, "testBean"); } + private RegisteredBean registerTestBean(ResolvableType beanType) { + this.beanFactory.registerBeanDefinition("testBean", + new RootBeanDefinition(beanType)); + return RegisteredBean.of(this.beanFactory, "testBean"); + } + private BeanRegistrationCodeFragments createInstance(RegisteredBean registeredBean) { return new DefaultBeanRegistrationCodeFragments(this.beanRegistrationsCode, registeredBean, new BeanDefinitionMethodGeneratorFactory(this.beanFactory)); diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/GenericFactoryBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/GenericFactoryBean.java new file mode 100644 index 000000000000..439e41f8461c --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/GenericFactoryBean.java @@ -0,0 +1,47 @@ +/* + * 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. + * 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.beans.factory.aot; + +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.lang.Nullable; + +/** + * A public {@link FactoryBean} with a generic type. + * + * @author Stephane Nicoll + */ +public class GenericFactoryBean implements FactoryBean { + + private final Class beanType; + + public GenericFactoryBean(Class beanType) { + this.beanType = beanType; + } + + @Nullable + @Override + public T getObject() throws Exception { + return BeanUtils.instantiateClass(this.beanType); + } + + @Nullable + @Override + public Class getObjectType() { + return this.beanType; + } +} diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/NumberFactoryBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/NumberFactoryBean.java new file mode 100644 index 000000000000..5229f9ab86af --- /dev/null +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/NumberFactoryBean.java @@ -0,0 +1,30 @@ +/* + * 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. + * 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.beans.factory.aot; + +/** + * A {@link GenericFactoryBean} that has a bound for the target type. + * + * @author Stephane Nicoll + */ +public class NumberFactoryBean extends GenericFactoryBean { + + public NumberFactoryBean(Class beanType) { + super(beanType); + } + +} diff --git a/spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/SimpleBeanFactoryBean.java b/spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/SimpleBeanFactoryBean.java index b4bba18a6c3d..9af17cd70ca8 100644 --- a/spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/SimpleBeanFactoryBean.java +++ b/spring-beans/src/testFixtures/java/org/springframework/beans/factory/aot/SimpleBeanFactoryBean.java @@ -19,20 +19,14 @@ import org.springframework.beans.factory.FactoryBean; /** - * A public {@link FactoryBean}. + * A public {@link FactoryBean} with a resolved generic for {@link GenericFactoryBean}. * * @author Stephane Nicoll */ -public class SimpleBeanFactoryBean implements FactoryBean { +public class SimpleBeanFactoryBean extends GenericFactoryBean { - @Override - public SimpleBean getObject() throws Exception { - return new SimpleBean(); - } - - @Override - public Class getObjectType() { - return SimpleBean.class; + public SimpleBeanFactoryBean() { + super(SimpleBean.class); } }