Browse Source

Reduce API surface, expose interfaces only.

See #3270
Original pull request: #3271
pull/3304/head
Mark Paluch 7 months ago
parent
commit
a8c779c26b
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 48
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
  2. 49
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryClassBuilder.java
  3. 71
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
  4. 124
      src/main/java/org/springframework/data/repository/aot/generate/RepositoryConstructorBuilder.java
  5. 9
      src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
  6. 9
      src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilderUnitTests.java
  7. 7
      src/test/java/org/springframework/data/repository/aot/generate/MethodCapturingRepositoryContributor.java
  8. 6
      src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java

48
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java

@ -22,6 +22,7 @@ import java.util.Comparator; @@ -22,6 +22,7 @@ import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import javax.lang.model.element.Modifier;
@ -43,6 +44,7 @@ import org.springframework.javapoet.JavaFile; @@ -43,6 +44,7 @@ import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeName;
import org.springframework.javapoet.TypeSpec;
import org.springframework.util.Assert;
/**
* Builder for AOT repository fragments.
@ -59,9 +61,9 @@ class AotRepositoryBuilder { @@ -59,9 +61,9 @@ class AotRepositoryBuilder {
private final ProjectionFactory projectionFactory;
private final AotRepositoryFragmentMetadata generationMetadata;
private @Nullable ConstructorCustomizer constructorCustomizer;
private @Nullable Consumer<AotRepositoryConstructorBuilder> constructorCustomizer;
private @Nullable MethodContributorFactory methodContributorFactory;
private ClassCustomizer customizer;
private Consumer<AotRepositoryClassBuilder> classCustomizer;
private AotRepositoryBuilder(RepositoryInformation repositoryInformation, String moduleName,
ProjectionFactory projectionFactory) {
@ -76,7 +78,7 @@ class AotRepositoryBuilder { @@ -76,7 +78,7 @@ class AotRepositoryBuilder {
.initializer("$T.getLog($T.class)", TypeName.get(LogFactory.class), this.generationMetadata.getTargetTypeName())
.build());
this.customizer = (info, builder) -> {};
this.classCustomizer = (builder) -> {};
}
/**
@ -93,14 +95,14 @@ class AotRepositoryBuilder { @@ -93,14 +95,14 @@ class AotRepositoryBuilder {
}
/**
* Configure a {@link ClassCustomizer} customizer.
* Configure a {@link AotRepositoryConstructorBuilder} customizer.
*
* @param classCustomizer must not be {@literal null}.
* @return {@code this}.
*/
public AotRepositoryBuilder withClassCustomizer(ClassCustomizer classCustomizer) {
public AotRepositoryBuilder withClassCustomizer(Consumer<AotRepositoryClassBuilder> classCustomizer) {
this.customizer = classCustomizer;
this.classCustomizer = classCustomizer;
return this;
}
@ -110,7 +112,8 @@ class AotRepositoryBuilder { @@ -110,7 +112,8 @@ class AotRepositoryBuilder {
* @param constructorCustomizer must not be {@literal null}.
* @return {@code this}.
*/
public AotRepositoryBuilder withConstructorCustomizer(ConstructorCustomizer constructorCustomizer) {
public AotRepositoryBuilder withConstructorCustomizer(
Consumer<AotRepositoryConstructorBuilder> constructorCustomizer) {
this.constructorCustomizer = constructorCustomizer;
return this;
@ -162,7 +165,12 @@ class AotRepositoryBuilder { @@ -162,7 +165,12 @@ class AotRepositoryBuilder {
generationMetadata.getFields().values().forEach(builder::addField);
// finally customize the file itself
this.customizer.customize(repositoryInformation, builder);
this.classCustomizer.accept(customizer -> {
Assert.notNull(customizer, "ClassCustomizer must not be null");
customizer.customize(builder);
});
JavaFile javaFile = JavaFile.builder(packageName(), builder.build()).build();
AotRepositoryMetadata metadata = getAotRepositoryMetadata(methodMetadata);
@ -171,11 +179,11 @@ class AotRepositoryBuilder { @@ -171,11 +179,11 @@ class AotRepositoryBuilder {
private MethodSpec buildConstructor() {
AotRepositoryConstructorBuilder constructorBuilder = new AotRepositoryConstructorBuilder(repositoryInformation,
RepositoryConstructorBuilder constructorBuilder = new RepositoryConstructorBuilder(
generationMetadata);
if (constructorCustomizer != null) {
constructorCustomizer.customize(constructorBuilder);
constructorCustomizer.accept(constructorBuilder);
}
return constructorBuilder.buildConstructor();
@ -213,8 +221,7 @@ class AotRepositoryBuilder { @@ -213,8 +221,7 @@ class AotRepositoryBuilder {
if (repositoryInformation.isQueryMethod(method) && methodContributorFactory != null) {
MethodContributor<? extends QueryMethod> contributor = methodContributorFactory.create(method,
repositoryInformation);
MethodContributor<? extends QueryMethod> contributor = methodContributorFactory.create(method);
if (contributor != null) {
@ -273,20 +280,6 @@ class AotRepositoryBuilder { @@ -273,20 +280,6 @@ class AotRepositoryBuilder {
return projectionFactory;
}
/**
* Customizer interface to customize the AOT repository fragment class after it has been defined.
*/
public interface ClassCustomizer {
/**
* Apply customization ot the AOT repository fragment class after it has been defined.
*
* @param information the repository information that is used for the AOT fragment.
* @param builder the class builder to be customized.
*/
void customize(RepositoryInformation information, TypeSpec.Builder builder);
}
/**
* Customizer interface to customize the AOT repository fragment constructor through
@ -313,12 +306,11 @@ class AotRepositoryBuilder { @@ -313,12 +306,11 @@ class AotRepositoryBuilder {
* Apply customization ot the AOT repository fragment constructor.
*
* @param method the method to be contributed.
* @param information repository information.
* @return the {@link MethodContributor} to be used. Can be {@literal null} if the method and method metadata should
* not be contributed.
*/
@Nullable
MethodContributor<? extends QueryMethod> create(Method method, RepositoryInformation information);
MethodContributor<? extends QueryMethod> create(Method method);
}

49
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryClassBuilder.java

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
/*
* Copyright 2025 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.data.repository.aot.generate;
import org.springframework.javapoet.TypeSpec;
/**
* Builder for AOT repository fragment classes.
*
* @author Mark Paluch
* @since 4.0
*/
public interface AotRepositoryClassBuilder {
/**
* Add a class customizer. Customizer is invoked after building the class.
*
* @param customizer the customizer with direct access to the {@link TypeSpec.Builder type builder}.
*/
void customize(ClassCustomizer customizer);
/**
* Customizer interface to customize the AOT repository fragment class after it has been defined.
*/
interface ClassCustomizer {
/**
* Apply customization ot the AOT repository fragment class after it has been defined.
*
* @param builder the class builder to be customized.
*/
void customize(TypeSpec.Builder builder);
}
}

71
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java

@ -15,15 +15,7 @@ @@ -15,15 +15,7 @@
*/
package org.springframework.data.repository.aot.generate;
import java.util.Map.Entry;
import javax.lang.model.element.Modifier;
import org.springframework.core.ResolvableType;
import org.springframework.data.repository.aot.generate.AotRepositoryFragmentMetadata.ConstructorArgument;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.javapoet.TypeName;
/**
@ -33,18 +25,7 @@ import org.springframework.javapoet.TypeName; @@ -33,18 +25,7 @@ import org.springframework.javapoet.TypeName;
* @author Mark Paluch
* @since 4.0
*/
public class AotRepositoryConstructorBuilder {
private final RepositoryInformation repositoryInformation;
private final AotRepositoryFragmentMetadata metadata;
private ConstructorCustomizer customizer = (info, builder) -> {};
AotRepositoryConstructorBuilder(RepositoryInformation repositoryInformation, AotRepositoryFragmentMetadata metadata) {
this.repositoryInformation = repositoryInformation;
this.metadata = metadata;
}
public interface AotRepositoryConstructorBuilder {
/**
* Add constructor parameter and create a field storing its value.
@ -52,16 +33,7 @@ public class AotRepositoryConstructorBuilder { @@ -52,16 +33,7 @@ public class AotRepositoryConstructorBuilder {
* @param parameterName name of the parameter.
* @param type parameter type.
*/
public void addParameter(String parameterName, Class<?> type) {
ResolvableType resolvableType = ResolvableType.forClass(type);
if (!resolvableType.hasGenerics() || !resolvableType.hasResolvableGenerics()) {
addParameter(parameterName, TypeName.get(type));
return;
}
addParameter(parameterName, ParameterizedTypeName.get(type, resolvableType.resolveGenerics()));
}
void addParameter(String parameterName, Class<?> type);
/**
* Add constructor parameter and create a field storing its value.
@ -69,7 +41,7 @@ public class AotRepositoryConstructorBuilder { @@ -69,7 +41,7 @@ public class AotRepositoryConstructorBuilder {
* @param parameterName name of the parameter.
* @param type parameter type.
*/
public void addParameter(String parameterName, TypeName type) {
default void addParameter(String parameterName, TypeName type) {
addParameter(parameterName, type, true);
}
@ -80,14 +52,7 @@ public class AotRepositoryConstructorBuilder { @@ -80,14 +52,7 @@ public class AotRepositoryConstructorBuilder {
* @param type parameter type.
* @param createField whether to create a field for the parameter and assign its value to the field.
*/
public void addParameter(String parameterName, TypeName type, boolean createField) {
this.metadata.addConstructorArgument(parameterName, type, createField ? parameterName : null);
if (createField) {
this.metadata.addField(parameterName, type, Modifier.PRIVATE, Modifier.FINAL);
}
}
void addParameter(String parameterName, TypeName type, boolean createField);
/**
* Add constructor customizer. Customizer is invoked after adding constructor arguments and before assigning
@ -95,41 +60,19 @@ public class AotRepositoryConstructorBuilder { @@ -95,41 +60,19 @@ public class AotRepositoryConstructorBuilder {
*
* @param customizer the customizer with direct access to the {@link MethodSpec.Builder constructor builder}.
*/
public void customize(ConstructorCustomizer customizer) {
this.customizer = customizer;
}
MethodSpec buildConstructor() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC);
for (Entry<String, ConstructorArgument> parameter : this.metadata.getConstructorArguments().entrySet()) {
builder.addParameter(parameter.getValue().typeName(), parameter.getKey());
}
customizer.customize(repositoryInformation, builder);
for (Entry<String, ConstructorArgument> parameter : this.metadata.getConstructorArguments().entrySet()) {
if (parameter.getValue().isForLocalField()) {
builder.addStatement("this.$N = $N", parameter.getKey(), parameter.getKey());
}
}
return builder.build();
}
void customize(ConstructorCustomizer customizer);
/**
* Customizer for the AOT repository constructor.
*/
public interface ConstructorCustomizer {
interface ConstructorCustomizer {
/**
* Customize the constructor.
*
* @param information the repository information that is used for the AOT fragment.
* @param builder the constructor builder to be customized.
*/
void customize(RepositoryInformation information, MethodSpec.Builder builder);
void customize(MethodSpec.Builder builder);
}

124
src/main/java/org/springframework/data/repository/aot/generate/RepositoryConstructorBuilder.java

@ -0,0 +1,124 @@ @@ -0,0 +1,124 @@
/*
* Copyright 2024 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.data.repository.aot.generate;
import java.util.Map.Entry;
import javax.lang.model.element.Modifier;
import org.springframework.core.ResolvableType;
import org.springframework.data.repository.aot.generate.AotRepositoryFragmentMetadata.ConstructorArgument;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.javapoet.TypeName;
import org.springframework.util.Assert;
/**
* Builder for AOT Repository Constructors.
*
* @author Christoph Strobl
* @author Mark Paluch
* @since 4.0
*/
class RepositoryConstructorBuilder implements AotRepositoryConstructorBuilder {
private final AotRepositoryFragmentMetadata metadata;
private ConstructorCustomizer customizer = (builder) -> {};
RepositoryConstructorBuilder(AotRepositoryFragmentMetadata metadata) {
this.metadata = metadata;
}
/**
* Add constructor parameter and create a field storing its value.
*
* @param parameterName name of the parameter.
* @param type parameter type.
*/
@Override
public void addParameter(String parameterName, Class<?> type) {
ResolvableType resolvableType = ResolvableType.forClass(type);
if (!resolvableType.hasGenerics() || !resolvableType.hasResolvableGenerics()) {
addParameter(parameterName, TypeName.get(type));
return;
}
addParameter(parameterName, ParameterizedTypeName.get(type, resolvableType.resolveGenerics()));
}
/**
* Add constructor parameter and create a field storing its value.
*
* @param parameterName name of the parameter.
* @param type parameter type.
*/
@Override
public void addParameter(String parameterName, TypeName type) {
addParameter(parameterName, type, true);
}
/**
* Add constructor parameter.
*
* @param parameterName name of the parameter.
* @param type parameter type.
* @param createField whether to create a field for the parameter and assign its value to the field.
*/
@Override
public void addParameter(String parameterName, TypeName type, boolean createField) {
this.metadata.addConstructorArgument(parameterName, type, createField ? parameterName : null);
if (createField) {
this.metadata.addField(parameterName, type, Modifier.PRIVATE, Modifier.FINAL);
}
}
/**
* Add constructor customizer. Customizer is invoked after adding constructor arguments and before assigning
* constructor arguments to fields.
*
* @param customizer the customizer with direct access to the {@link MethodSpec.Builder constructor builder}.
*/
@Override
public void customize(ConstructorCustomizer customizer) {
Assert.notNull(customizer, "ConstructorCustomizer must not be null");
this.customizer = customizer;
}
public MethodSpec buildConstructor() {
MethodSpec.Builder builder = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC);
for (Entry<String, ConstructorArgument> parameter : this.metadata.getConstructorArguments().entrySet()) {
builder.addParameter(parameter.getValue().typeName(), parameter.getKey());
}
customizer.customize(builder);
for (Entry<String, ConstructorArgument> parameter : this.metadata.getConstructorArguments().entrySet()) {
if (parameter.getValue().isForLocalField()) {
builder.addStatement("this.$N = $N", parameter.getKey(), parameter.getKey());
}
}
return builder.build();
}
}

9
src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java

@ -31,6 +31,7 @@ import org.springframework.data.repository.core.RepositoryInformation; @@ -31,6 +31,7 @@ import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.TypeName;
import org.springframework.util.StringUtils;
import org.springframework.javapoet.TypeSpec;
/**
@ -136,23 +137,21 @@ public class RepositoryContributor { @@ -136,23 +137,21 @@ public class RepositoryContributor {
/**
* Customization hook for store implementations to customize class after building the entire class.
*/
protected void customizeClass(RepositoryInformation information,
TypeSpec.Builder builder) {
protected void customizeClass(AotRepositoryClassBuilder builder) {
}
/**
* Customization hook for store implementations to customize the fragment constructor after building the constructor.
*/
protected void customizeConstructor(AotRepositoryConstructorBuilder constructorBuilder) {
protected void customizeConstructor(AotRepositoryConstructorBuilder builder) {
}
/**
* Customization hook for store implementations to contribute a query method.
*/
protected @Nullable MethodContributor<? extends QueryMethod> contributeQueryMethod(Method method,
RepositoryInformation repositoryInformation) {
protected @Nullable MethodContributor<? extends QueryMethod> contributeQueryMethod(Method method) {
return null;
}

9
src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilderUnitTests.java

@ -96,7 +96,7 @@ class AotRepositoryBuilderUnitTests { @@ -96,7 +96,7 @@ class AotRepositoryBuilderUnitTests {
AotRepositoryBuilder repoBuilder = AotRepositoryBuilder.forRepository(repositoryInformation, "Commons",
new SpelAwareProxyProjectionFactory());
repoBuilder.withConstructorCustomizer(ctor -> {
ctor.customize((info, code) -> {
ctor.customize((code) -> {
code.addStatement("throw new $T($S)", IllegalStateException.class, "initialization error");
});
});
@ -110,7 +110,9 @@ class AotRepositoryBuilderUnitTests { @@ -110,7 +110,9 @@ class AotRepositoryBuilderUnitTests {
AotRepositoryBuilder repoBuilder = AotRepositoryBuilder.forRepository(repositoryInformation, "Commons",
new SpelAwareProxyProjectionFactory());
repoBuilder.withClassCustomizer((info, metadata, clazz) -> {
repoBuilder.withClassCustomizer((builder) -> {
builder.customize(clazz -> {
clazz.addField(Float.class, "f", Modifier.PRIVATE, Modifier.STATIC);
clazz.addField(Double.class, "d", Modifier.PUBLIC);
@ -120,6 +122,7 @@ class AotRepositoryBuilderUnitTests { @@ -120,6 +122,7 @@ class AotRepositoryBuilderUnitTests {
clazz.addMethod(MethodSpec.methodBuilder("oops").build());
});
});
assertThat(repoBuilder.build().javaFile().toString()) //
.contains("@Repository") //
@ -138,7 +141,7 @@ class AotRepositoryBuilderUnitTests { @@ -138,7 +141,7 @@ class AotRepositoryBuilderUnitTests {
AotRepositoryBuilder repoBuilder = AotRepositoryBuilder.forRepository(repositoryInformation, "Commons",
new SpelAwareProxyProjectionFactory());
repoBuilder.withQueryMethodContributor((method, info) -> {
repoBuilder.withQueryMethodContributor((method) -> {
return new MethodContributor<>(mock(QueryMethod.class), null) {

7
src/test/java/org/springframework/data/repository/aot/generate/MethodCapturingRepositoryContributor.java

@ -15,15 +15,15 @@ @@ -15,15 +15,15 @@
*/
package org.springframework.data.repository.aot.generate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.*;
import java.lang.reflect.Method;
import java.util.List;
import org.assertj.core.api.MapAssert;
import org.jspecify.annotations.Nullable;
import org.springframework.data.repository.config.AotRepositoryContext;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
@ -41,8 +41,7 @@ public class MethodCapturingRepositoryContributor extends RepositoryContributor @@ -41,8 +41,7 @@ public class MethodCapturingRepositoryContributor extends RepositoryContributor
}
@Override
protected @Nullable MethodContributor<? extends QueryMethod> contributeQueryMethod(Method method,
RepositoryInformation repositoryInformation) {
protected @Nullable MethodContributor<? extends QueryMethod> contributeQueryMethod(Method method) {
capturedInvocations.add(method.getName(), method);
return null;
}

6
src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java

@ -58,10 +58,10 @@ class RepositoryContributorUnitTests { @@ -58,10 +58,10 @@ class RepositoryContributorUnitTests {
RepositoryContributor repositoryContributor = new RepositoryContributor(aotContext) {
@Override
protected @Nullable MethodContributor<? extends QueryMethod> contributeQueryMethod(Method method,
RepositoryInformation repositoryInformation) {
protected @Nullable MethodContributor<? extends QueryMethod> contributeQueryMethod(Method method) {
return MethodContributor.forQueryMethod(new QueryMethod(method, repositoryInformation, getProjectionFactory()))
return MethodContributor
.forQueryMethod(new QueryMethod(method, getRepositoryInformation(), getProjectionFactory()))
.withMetadata(new QueryMetadata() {
@Override

Loading…
Cancel
Save