diff --git a/pom.xml b/pom.xml index f637fcad1..75f26f703 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,7 @@ - + 4.0.0 @@ -11,8 +13,11 @@ Core Spring concepts underpinning every Spring Data module. https://spring.io/projects/spring-data - scm:git:https://github.com/spring-projects/spring-data-commons.git - scm:git:git@github.com:spring-projects/spring-data-commons.git + scm:git:https://github.com/spring-projects/spring-data-commons.git + + + scm:git:git@github.com:spring-projects/spring-data-commons.git + https://github.com/spring-projects/spring-data-commons @@ -77,6 +82,7 @@ spring-webflux true + org.springframework spring-core-test @@ -121,11 +127,6 @@ reactor-test test - - org.springframework - spring-core-test - test - diff --git a/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java index 4bd4fe92e..48bc6dc22 100644 --- a/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java +++ b/src/main/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessor.java @@ -48,6 +48,15 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio private final Log logger = LogFactory.getLog(getClass()); private @Nullable String moduleIdentifier; + public void setModuleIdentifier(@Nullable String moduleIdentifier) { + this.moduleIdentifier = moduleIdentifier; + } + + @Nullable + public String getModuleIdentifier() { + return this.moduleIdentifier; + } + @Override public BeanRegistrationAotContribution processAheadOfTime(RegisteredBean registeredBean) { @@ -59,18 +68,20 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio return contribute(AotContext.from(beanFactory), resolveManagedTypes(registeredBean), registeredBean); } - ManagedTypes resolveManagedTypes(RegisteredBean registeredBean) { + private ManagedTypes resolveManagedTypes(RegisteredBean registeredBean) { RootBeanDefinition beanDefinition = registeredBean.getMergedBeanDefinition(); + if (beanDefinition.hasConstructorArgumentValues()) { + ValueHolder indexedArgumentValue = beanDefinition.getConstructorArgumentValues().getIndexedArgumentValue(0, null); Object value = indexedArgumentValue.getValue(); - if (value instanceof Collection values) { - if (values.stream().allMatch(it -> it instanceof Class)) { - return ManagedTypes.fromIterable((Collection>) values); - } + + if (value instanceof Collection values && values.stream().allMatch(it -> it instanceof Class)) { + return ManagedTypes.fromIterable((Collection>) values); } } + if (logger.isDebugEnabled()) { logger.debug( String.format("ManagedTypes BeanDefinition '%s' does serve arguments. Trying to resolve bean instance.", @@ -93,29 +104,18 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio return ManagedTypes.empty(); } - protected boolean isMatch(@Nullable Class beanType, @Nullable String beanName) { - return matchesByType(beanType) && matchesPrefix(beanName); - } - - protected boolean matchesByType(@Nullable Class beanType) { - return beanType != null && ClassUtils.isAssignable(ManagedTypes.class, beanType); - } - - protected boolean matchesPrefix(@Nullable String beanName) { - return StringUtils.startsWithIgnoreCase(beanName, getModuleIdentifier()); - } - /** * Hook to provide a customized flavor of {@link BeanRegistrationAotContribution}. By overriding this method calls to * {@link #contributeType(ResolvableType, GenerationContext)} might no longer be issued. * * @param aotContext never {@literal null}. * @param managedTypes never {@literal null}. - * @return new instance of {@link ManagedTypesBeanRegistrationAotProcessor} or {@literal null} if nothing to do. + * @return new instance of {@link BeanRegistrationAotContribution} or {@literal null} if nothing to do. */ @Nullable - protected BeanRegistrationAotContribution contribute(AotContext aotContext, ManagedTypes managedTypes, RegisteredBean registeredBean) { - return new ManagedTypesRegistrationAotContribution(aotContext, managedTypes, registeredBean, this::contributeType); + protected BeanRegistrationAotContribution contribute(AotContext aotContext, ManagedTypes managedTypes, + RegisteredBean registeredBean) { + return new ManagedTypesRegistrationAotContribution(managedTypes, registeredBean, this::contributeType); } /** @@ -138,12 +138,15 @@ public class ManagedTypesBeanRegistrationAotProcessor implements BeanRegistratio annotation -> TypeContributor.contribute(annotation.getType(), annotationNamespaces, generationContext)); } - public void setModuleIdentifier(@Nullable String moduleIdentifier) { - this.moduleIdentifier = moduleIdentifier; + protected boolean isMatch(@Nullable Class beanType, @Nullable String beanName) { + return matchesByType(beanType) && matchesPrefix(beanName); } - @Nullable - public String getModuleIdentifier() { - return this.moduleIdentifier; + protected boolean matchesByType(@Nullable Class beanType) { + return beanType != null && ClassUtils.isAssignable(ManagedTypes.class, beanType); + } + + protected boolean matchesPrefix(@Nullable String beanName) { + return StringUtils.startsWithIgnoreCase(beanName, getModuleIdentifier()); } } diff --git a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java b/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java index da455df23..5b784da99 100644 --- a/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java +++ b/src/main/java/org/springframework/data/aot/ManagedTypesRegistrationAotContribution.java @@ -50,51 +50,42 @@ import org.springframework.util.ReflectionUtils; * {@link BeanRegistrationAotContribution#customizeBeanRegistrationCodeFragments(GenerationContext, BeanRegistrationCodeFragments)}. * The generated code resolves potential factory methods accepting either a {@link ManagedTypes} instance, or a * {@link List} of either {@link Class} or {@link String} (classname) values. - * - *
- * 
+ *
+ * 
  * public static InstanceSupplier<ManagedTypes> instance() {
  *   return (registeredBean) -> {
  *     var types = List.of("com.example.A", "com.example.B");
  *     return ManagedTypes.ofStream(types.stream().map(it -> ClassUtils.forName(it, registeredBean.getBeanFactory().getBeanClassLoader())));
  *   }
  * }
- * 
  * 
* * @author John Blum * @author Christoph Strobl + * @author Mark Paluch * @see org.springframework.beans.factory.aot.BeanRegistrationAotContribution - * @since 3.0.0 + * @since 3.0 */ -public class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContribution { +class ManagedTypesRegistrationAotContribution implements RegisteredBeanAotContribution { - private final AotContext aotContext; private final ManagedTypes managedTypes; + private final Lazy>> sourceTypes; private final BiConsumer contributionAction; private final RegisteredBean source; - public ManagedTypesRegistrationAotContribution(AotContext aotContext, @Nullable ManagedTypes managedTypes, - RegisteredBean registeredBean, BiConsumer contributionAction) { + public ManagedTypesRegistrationAotContribution(ManagedTypes managedTypes, RegisteredBean registeredBean, + BiConsumer contributionAction) { - this.aotContext = aotContext; this.managedTypes = managedTypes; + this.sourceTypes = Lazy.of(managedTypes::toList); this.contributionAction = contributionAction; this.source = registeredBean; } - protected AotContext getAotContext() { - return this.aotContext; - } - - protected ManagedTypes getManagedTypes() { - return managedTypes == null ? ManagedTypes.empty() : managedTypes; - } - @Override public void applyTo(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode) { - List> types = getManagedTypes().toList(); + List> types = sourceTypes.get(); if (!types.isEmpty()) { TypeCollector.inspect(types).forEach(type -> contributionAction.accept(type, generationContext)); @@ -109,7 +100,7 @@ public class ManagedTypesRegistrationAotContribution implements RegisteredBeanAo return codeFragments; } - ManagedTypesInstanceCodeFragment fragment = new ManagedTypesInstanceCodeFragment(getManagedTypes(), source, + ManagedTypesInstanceCodeFragment fragment = new ManagedTypesInstanceCodeFragment(sourceTypes.get(), source, codeFragments); return fragment.canGenerateCode() ? fragment : codeFragments; } @@ -119,32 +110,27 @@ public class ManagedTypesRegistrationAotContribution implements RegisteredBeanAo return source; } + /** + * Class used to generate the fragment of code needed to define a {@link ManagedTypes} bean from previously discovered + * managed types. + */ static class ManagedTypesInstanceCodeFragment extends BeanRegistrationCodeFragments { - private ManagedTypes sourceTypes; - private RegisteredBean source; - private Lazy instanceMethod = Lazy.of(this::findInstanceFactory); + public static final ResolvableType LIST_TYPE = ResolvableType.forType(List.class); + public static final ResolvableType MANAGED_TYPES_TYPE = ResolvableType.forType(ManagedTypes.class); + private final List> sourceTypes; + private final RegisteredBean source; + private final Lazy instanceMethod = Lazy.of(this::findInstanceFactory); - protected ManagedTypesInstanceCodeFragment(ManagedTypes managedTypes, RegisteredBean source, + protected ManagedTypesInstanceCodeFragment(List> sourceTypes, RegisteredBean source, BeanRegistrationCodeFragments codeFragments) { super(codeFragments); - this.sourceTypes = managedTypes; + this.sourceTypes = sourceTypes; this.source = source; } - /** - * @return {@literal true} if the instance method code can be generated. {@literal false} otherwise. - */ - boolean canGenerateCode() { - - if (ObjectUtils.nullSafeEquals(source.getBeanClass(), ManagedTypes.class)) { - return true; - } - return instanceMethod.getNullable() != null; - } - @Override public CodeBlock generateInstanceSupplierCode(GenerationContext generationContext, BeanRegistrationCode beanRegistrationCode, Executable constructorOrFactoryMethod, @@ -156,33 +142,20 @@ public class ManagedTypesRegistrationAotContribution implements RegisteredBeanAo return CodeBlock.of("$T.$L()", beanRegistrationCode.getClassName(), generatedMethod.getName()); } - private CodeBlock toCodeBlock(List> values, boolean allPublic) { + /** + * @return {@literal true} if the instance method code can be generated. {@literal false} otherwise. + */ + boolean canGenerateCode() { - if (allPublic) { - return CodeBlock.join(values.stream().map(value -> CodeBlock.of("$T.class", value)).toList(), ", "); + if (ObjectUtils.nullSafeEquals(source.getBeanClass(), ManagedTypes.class)) { + return true; } - return CodeBlock.join(values.stream().map(value -> CodeBlock.of("$S", value.getName())).toList(), ", "); - } - private Method findInstanceFactory() { - - for (Method beanMethod : ReflectionUtils.getDeclaredMethods(source.getBeanClass())) { - - if (beanMethod.getParameterCount() == 1 && java.lang.reflect.Modifier.isPublic(beanMethod.getModifiers()) - && java.lang.reflect.Modifier.isStatic(beanMethod.getModifiers())) { - ResolvableType parameterType = ResolvableType.forMethodParameter(beanMethod, 0, source.getBeanClass()); - if (parameterType.isAssignableFrom(ResolvableType.forType(List.class)) - || parameterType.isAssignableFrom(ResolvableType.forType(ManagedTypes.class))) { - return beanMethod; - } - } - } - return null; + return instanceMethod.getNullable() != null; } void generateInstanceFactory(Builder method) { - List> sourceTypes = this.sourceTypes.toList(); boolean allSourceTypesVisible = sourceTypes.stream() .allMatch(it -> AccessVisibility.PUBLIC.equals(AccessVisibility.forClass(it))); @@ -207,6 +180,7 @@ public class ManagedTypesRegistrationAotContribution implements RegisteredBeanAo .addStatement("throw new $T($S, e)", IllegalArgumentException.class, "Cannot to load type").endControlFlow() .endControlFlow("))").build()); } + if (ObjectUtils.nullSafeEquals(source.getBeanClass(), ManagedTypes.class)) { builder.add("return managedTypes"); } else { @@ -221,8 +195,42 @@ public class ManagedTypesRegistrationAotContribution implements RegisteredBeanAo instanceFactoryMethod.getName(), "managedTypes"); } } + builder.endControlFlow(")"); method.addCode(builder.build()); } + + private CodeBlock toCodeBlock(List> values, boolean allPublic) { + + if (allPublic) { + return CodeBlock.join(values.stream().map(value -> CodeBlock.of("$T.class", value)).toList(), ", "); + } + return CodeBlock.join(values.stream().map(value -> CodeBlock.of("$S", value.getName())).toList(), ", "); + } + + @Nullable + private Method findInstanceFactory() { + + for (Method beanMethod : ReflectionUtils.getDeclaredMethods(source.getBeanClass())) { + + if (!isInstanceFactory(beanMethod)) { + continue; + } + + ResolvableType parameterType = ResolvableType.forMethodParameter(beanMethod, 0, source.getBeanClass()); + + if (parameterType.isAssignableFrom(LIST_TYPE) || parameterType.isAssignableFrom(MANAGED_TYPES_TYPE)) { + return beanMethod; + } + } + + return null; + } + + private static boolean isInstanceFactory(Method beanMethod) { + return beanMethod.getParameterCount() == 1 // + && java.lang.reflect.Modifier.isPublic(beanMethod.getModifiers()) // + && java.lang.reflect.Modifier.isStatic(beanMethod.getModifiers()); + } } } diff --git a/src/main/java/org/springframework/data/aot/RegisteredBeanAotContribution.java b/src/main/java/org/springframework/data/aot/RegisteredBeanAotContribution.java index b35567546..1cb3f3c31 100644 --- a/src/main/java/org/springframework/data/aot/RegisteredBeanAotContribution.java +++ b/src/main/java/org/springframework/data/aot/RegisteredBeanAotContribution.java @@ -19,9 +19,16 @@ import org.springframework.beans.factory.aot.BeanRegistrationAotContribution; import org.springframework.beans.factory.support.RegisteredBean; /** + * Extension to {@link BeanRegistrationAotContribution} that bases its contribution on a {@link RegisteredBean}. This + * interface exposes its {@link #getSource() source}. + * * @author Christoph Strobl + * @since 3.0 */ public interface RegisteredBeanAotContribution extends BeanRegistrationAotContribution { + /** + * @return the source {@link RegisteredBean}. + */ RegisteredBean getSource(); } diff --git a/src/main/java/org/springframework/data/aot/RepositoryBeanDefinitionReader.java b/src/main/java/org/springframework/data/aot/RepositoryBeanDefinitionReader.java index d6b1cf420..edd9aef78 100644 --- a/src/main/java/org/springframework/data/aot/RepositoryBeanDefinitionReader.java +++ b/src/main/java/org/springframework/data/aot/RepositoryBeanDefinitionReader.java @@ -36,7 +36,7 @@ import org.springframework.util.ClassUtils; * * @author Christoph Strobl * @author John Blum - * @since 3.0.0 + * @since 3.0 */ class RepositoryBeanDefinitionReader { diff --git a/src/main/java/org/springframework/data/aot/RepositoryRegistrationAotProcessor.java b/src/main/java/org/springframework/data/aot/RepositoryRegistrationAotProcessor.java index e549820a8..2ab354b31 100644 --- a/src/main/java/org/springframework/data/aot/RepositoryRegistrationAotProcessor.java +++ b/src/main/java/org/springframework/data/aot/RepositoryRegistrationAotProcessor.java @@ -61,7 +61,7 @@ import org.springframework.util.StringUtils; * * @author Christoph Strobl * @author John Blum - * @since 3.0.0 + * @since 3.0 */ @SuppressWarnings("unused") public class RepositoryRegistrationAotProcessor implements BeanRegistrationAotProcessor, BeanFactoryAware { diff --git a/src/test/java/org/springframework/data/aot/AotTestCodeContributionBuilder.java b/src/test/java/org/springframework/data/aot/AotTestCodeContributionBuilder.java index b924209cf..d26ba8879 100644 --- a/src/test/java/org/springframework/data/aot/AotTestCodeContributionBuilder.java +++ b/src/test/java/org/springframework/data/aot/AotTestCodeContributionBuilder.java @@ -31,6 +31,8 @@ import org.springframework.javapoet.MethodSpec; import org.springframework.javapoet.ParameterizedTypeName; /** + * Test utility to build and compile AOT code contributions. + * * @author Christoph Strobl */ public class AotTestCodeContributionBuilder { diff --git a/src/test/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessorUnitTests.java b/src/test/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessorUnitTests.java index 4fa54d870..bbb2efea8 100644 --- a/src/test/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessorUnitTests.java +++ b/src/test/java/org/springframework/data/aot/ManagedTypesBeanRegistrationAotProcessorUnitTests.java @@ -21,6 +21,7 @@ import static org.mockito.Mockito.*; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -47,21 +48,23 @@ import org.springframework.javapoet.MethodSpec.Builder; import org.springframework.test.util.ReflectionTestUtils; /** + * Unit tests for {@link ManagedTypesBeanRegistrationAotProcessor}. + * * @author Christoph Strobl */ class ManagedTypesBeanRegistrationAotProcessorUnitTests { - final RootBeanDefinition managedTypesDefinition = (RootBeanDefinition) BeanDefinitionBuilder + private final RootBeanDefinition managedTypesDefinition = (RootBeanDefinition) BeanDefinitionBuilder .rootBeanDefinition(ManagedTypes.class).setFactoryMethod("fromIterable") .addConstructorArgValue(Collections.singleton(A.class)).getBeanDefinition(); - final RootBeanDefinition myManagedTypesDefinition = (RootBeanDefinition) BeanDefinitionBuilder + private final RootBeanDefinition myManagedTypesDefinition = (RootBeanDefinition) BeanDefinitionBuilder .rootBeanDefinition(MyManagedTypes.class).getBeanDefinition(); - final RootBeanDefinition invocationCountingManagedTypesDefinition = (RootBeanDefinition) BeanDefinitionBuilder + private final RootBeanDefinition invocationCountingManagedTypesDefinition = (RootBeanDefinition) BeanDefinitionBuilder .rootBeanDefinition(InvocationRecordingManagedTypes.class).getBeanDefinition(); - DefaultListableBeanFactory beanFactory; + private DefaultListableBeanFactory beanFactory; @BeforeEach void beforeEach() { @@ -221,8 +224,8 @@ class ManagedTypesBeanRegistrationAotProcessorUnitTests { beanFactory.registerBeanDefinition("managed-types", managedTypesDefinition); RegisteredBean registeredBean = RegisteredBean.of(beanFactory, "managed-types"); - ManagedTypesInstanceCodeFragment fragment = new ManagedTypesInstanceCodeFragment( - ManagedTypes.from(A.class, B.class), registeredBean, Mockito.mock(BeanRegistrationCodeFragments.class)); + ManagedTypesInstanceCodeFragment fragment = new ManagedTypesInstanceCodeFragment(List.of(A.class, B.class), + registeredBean, Mockito.mock(BeanRegistrationCodeFragments.class)); Builder methodBuilder = MethodSpec.methodBuilder("instance"); fragment.generateInstanceFactory(methodBuilder); @@ -235,8 +238,8 @@ class ManagedTypesBeanRegistrationAotProcessorUnitTests { beanFactory.registerBeanDefinition("managed-types", myManagedTypesDefinition); RegisteredBean registeredBean = RegisteredBean.of(beanFactory, "managed-types"); - ManagedTypesInstanceCodeFragment fragment = new ManagedTypesInstanceCodeFragment( - ManagedTypes.from(A.class, B.class), registeredBean, Mockito.mock(BeanRegistrationCodeFragments.class)); + ManagedTypesInstanceCodeFragment fragment = new ManagedTypesInstanceCodeFragment(List.of(A.class, B.class), + registeredBean, Mockito.mock(BeanRegistrationCodeFragments.class)); assertThat(fragment.canGenerateCode()).isFalse(); } @@ -290,7 +293,7 @@ class ManagedTypesBeanRegistrationAotProcessorUnitTests { public static class InvocationRecordingManagedTypes implements ManagedTypes { - private AtomicInteger counter = new AtomicInteger(0); + private final AtomicInteger counter = new AtomicInteger(0); private ManagedTypes source = ManagedTypes.from(A.class, B.class); public static InvocationRecordingManagedTypes from(ManagedTypes source) {