From 6b9bc012a54dd7a72130d2163b5d9a012eafb809 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 25 Apr 2023 22:07:31 +0100 Subject: [PATCH] Check that BPP and BFPP bean methods won't cause eager initialization Closes gh-35164 --- buildSrc/build.gradle | 1 + .../build/architecture/ArchitectureCheck.java | 186 ++++++++++++++++++ .../architecture/ArchitecturePlugin.java | 12 +- .../architecture/PackageTangleCheck.java | 102 ---------- .../boot/build/architecture/Rules.java | 62 ++++++ .../architecture/ArchitectureCheckTests.java | 148 ++++++++++++++ .../architecture/PackageTangleCheckTests.java | 93 --------- ...BeanFactoryPostProcessorConfiguration.java | 30 +++ ...BeanFactoryPostProcessorConfiguration.java | 40 ++++ ...BeanFactoryPostProcessorConfiguration.java | 40 ++++ ...nStaticBeanPostProcessorConfiguration.java | 31 +++ ...ametersBeanPostProcessorConfiguration.java | 41 ++++ ...ametersBeanPostProcessorConfiguration.java | 46 +++++ ...ametersBeanPostProcessorConfiguration.java | 43 ++++ ...ionPropertiesReportEndpointProxyTests.java | 2 +- .../web/annotation/BaseConfiguration.java | 2 +- .../RedisAutoConfigurationJedisTests.java | 2 +- .../ValidationAutoConfigurationTests.java | 2 +- ...ToolsDataSourceAutoConfigurationTests.java | 2 +- .../PropertySourcesDeducerTests.java | 6 +- 20 files changed, 682 insertions(+), 209 deletions(-) create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java delete mode 100644 buildSrc/src/main/java/org/springframework/boot/build/architecture/PackageTangleCheck.java create mode 100644 buildSrc/src/main/java/org/springframework/boot/build/architecture/Rules.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java delete mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/PackageTangleCheckTests.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/nonstatic/NonStaticBeanFactoryPostProcessorConfiguration.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/noparameters/NoParametersBeanFactoryPostProcessorConfiguration.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/parameters/ParametersBeanFactoryPostProcessorConfiguration.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/nonstatic/NonStaticBeanPostProcessorConfiguration.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/noparameters/NoParametersBeanPostProcessorConfiguration.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/safeparameters/SafeParametersBeanPostProcessorConfiguration.java create mode 100644 buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/unsafeparameters/UnsafeParametersBeanPostProcessorConfiguration.java diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle index f1ee4b8752..0fc303c70e 100644 --- a/buildSrc/build.gradle +++ b/buildSrc/build.gradle @@ -38,6 +38,7 @@ dependencies { testImplementation("org.assertj:assertj-core:3.11.1") testImplementation("org.apache.logging.log4j:log4j-core:2.17.1") testImplementation("org.junit.jupiter:junit-jupiter:5.6.0") + implementation("org.springframework:spring-context") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java new file mode 100644 index 0000000000..e6dba8e5cf --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java @@ -0,0 +1,186 @@ +/* + * Copyright 2022-2023 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.boot.build.architecture; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass; +import com.tngtech.archunit.core.domain.JavaClass.Predicates; +import com.tngtech.archunit.core.domain.JavaClasses; +import com.tngtech.archunit.core.domain.JavaMethod; +import com.tngtech.archunit.core.domain.JavaParameter; +import com.tngtech.archunit.core.domain.properties.CanBeAnnotated; +import com.tngtech.archunit.core.importer.ClassFileImporter; +import com.tngtech.archunit.lang.ArchCondition; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.ConditionEvents; +import com.tngtech.archunit.lang.EvaluationResult; +import com.tngtech.archunit.lang.SimpleConditionEvent; +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; +import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition; +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Task; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileTree; +import org.gradle.api.tasks.IgnoreEmptyDirectories; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.OutputDirectory; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.api.tasks.TaskAction; + +/** + * {@link Task} that checks for architecture problems. + * + * @author Andy Wilkinson + */ +public abstract class ArchitectureCheck extends DefaultTask { + + private FileCollection classes; + + public ArchitectureCheck() { + getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); + } + + @TaskAction + void checkArchitecture() throws IOException { + JavaClasses javaClasses = new ClassFileImporter() + .importPaths(this.classes.getFiles().stream().map(File::toPath).collect(Collectors.toList())); + List violations = Stream.of(allPackagesShouldBeFreeOfTangles(), + allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization(), + allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters()) + .map((rule) -> rule.evaluate(javaClasses)) + .filter(EvaluationResult::hasViolation) + .collect(Collectors.toList()); + File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); + outputFile.getParentFile().mkdirs(); + if (!violations.isEmpty()) { + StringBuilder report = new StringBuilder(); + for (EvaluationResult violation : violations) { + report.append(violation.getFailureReport().toString()); + report.append(String.format("%n")); + } + Files.write(outputFile.toPath(), report.toString().getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + throw new GradleException("Architecture check failed. See '" + outputFile + "' for details."); + } + else { + outputFile.createNewFile(); + } + } + + private ArchRule allPackagesShouldBeFreeOfTangles() { + return SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles(); + } + + private ArchRule allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization() { + return ArchRuleDefinition.methods() + .that() + .areAnnotatedWith("org.springframework.context.annotation.Bean") + .and() + .haveRawReturnType(Predicates.assignableTo("org.springframework.beans.factory.config.BeanPostProcessor")) + .should(onlyHaveParametersThatWillNotCauseEagerInitialization()) + .andShould() + .beStatic() + .allowEmptyShould(true); + } + + private ArchCondition onlyHaveParametersThatWillNotCauseEagerInitialization() { + DescribedPredicate notAnnotatedWithLazy = DescribedPredicate + .not(CanBeAnnotated.Predicates.annotatedWith("org.springframework.context.annotation.Lazy")); + DescribedPredicate notOfASafeType = DescribedPredicate + .not(Predicates.assignableTo("org.springframework.beans.factory.ObjectProvider") + .or(Predicates.assignableTo("org.springframework.context.ApplicationContext")) + .or(Predicates.assignableTo("org.springframework.core.env.Environment"))); + return new ArchCondition("not have parameters that will cause eager initialization") { + + @Override + public void check(JavaMethod item, ConditionEvents events) { + item.getParameters() + .stream() + .filter(notAnnotatedWithLazy) + .filter((parameter) -> notOfASafeType.test(parameter.getRawType())) + .forEach((parameter) -> events.add(SimpleConditionEvent.violated(parameter, + parameter.getDescription() + " will cause eager initialization as it is " + + notAnnotatedWithLazy.getDescription() + " and is " + + notOfASafeType.getDescription()))); + } + + }; + } + + private ArchRule allBeanFactoryPostProcessorBeanMethodsShouldBeStaticAndHaveNoParameters() { + return ArchRuleDefinition.methods() + .that() + .areAnnotatedWith("org.springframework.context.annotation.Bean") + .and() + .haveRawReturnType( + Predicates.assignableTo("org.springframework.beans.factory.config.BeanFactoryPostProcessor")) + .should(haveNoParameters()) + .andShould() + .beStatic() + .allowEmptyShould(true); + } + + private ArchCondition haveNoParameters() { + return new ArchCondition("have no parameters") { + + @Override + public void check(JavaMethod item, ConditionEvents events) { + List parameters = item.getParameters(); + if (!parameters.isEmpty()) { + events + .add(SimpleConditionEvent.violated(item, item.getDescription() + " should have no parameters")); + } + } + + }; + } + + public void setClasses(FileCollection classes) { + this.classes = classes; + } + + @Internal + public FileCollection getClasses() { + return this.classes; + } + + @InputFiles + @SkipWhenEmpty + @IgnoreEmptyDirectories + @PathSensitive(PathSensitivity.RELATIVE) + final FileTree getInputClasses() { + return this.classes.getAsFileTree(); + } + + @OutputDirectory + public abstract DirectoryProperty getOutputDirectory(); + +} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java index c95c433572..102ddf72e0 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitecturePlugin.java @@ -44,14 +44,14 @@ public class ArchitecturePlugin implements Plugin { private void registerTasks(Project project) { JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); - List> packageTangleChecks = new ArrayList<>(); + List> packageTangleChecks = new ArrayList<>(); for (SourceSet sourceSet : javaPluginExtension.getSourceSets()) { - TaskProvider checkPackageTangles = project.getTasks() - .register("checkForPackageTangles" + StringUtils.capitalize(sourceSet.getName()), - PackageTangleCheck.class, (task) -> { + TaskProvider checkPackageTangles = project.getTasks() + .register("checkArchitecture" + StringUtils.capitalize(sourceSet.getName()), ArchitectureCheck.class, + (task) -> { task.setClasses(sourceSet.getOutput().getClassesDirs()); - task.setDescription("Checks the classes of the " + sourceSet.getName() - + " source set for package tangles."); + task.setDescription("Checks the architecture of the classes of the " + sourceSet.getName() + + " source set."); task.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); }); packageTangleChecks.add(checkPackageTangles); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/PackageTangleCheck.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/PackageTangleCheck.java deleted file mode 100644 index 715ac5f686..0000000000 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/PackageTangleCheck.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright 2022-2023 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.boot.build.architecture; - -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.StandardOpenOption; -import java.util.stream.Collectors; - -import com.tngtech.archunit.core.domain.JavaClasses; -import com.tngtech.archunit.core.importer.ClassFileImporter; -import com.tngtech.archunit.lang.EvaluationResult; -import com.tngtech.archunit.library.dependencies.SliceRule; -import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition; -import org.gradle.api.DefaultTask; -import org.gradle.api.GradleException; -import org.gradle.api.Task; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.file.FileCollection; -import org.gradle.api.file.FileTree; -import org.gradle.api.tasks.IgnoreEmptyDirectories; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.Internal; -import org.gradle.api.tasks.OutputDirectory; -import org.gradle.api.tasks.PathSensitive; -import org.gradle.api.tasks.PathSensitivity; -import org.gradle.api.tasks.SkipWhenEmpty; -import org.gradle.api.tasks.TaskAction; - -import org.springframework.util.FileCopyUtils; - -/** - * {@link Task} that checks for package tangles. - * - * @author Andy Wilkinson - */ -public abstract class PackageTangleCheck extends DefaultTask { - - private FileCollection classes; - - public PackageTangleCheck() { - getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName())); - } - - @TaskAction - void checkForPackageTangles() throws IOException { - JavaClasses javaClasses = new ClassFileImporter() - .importPaths(this.classes.getFiles().stream().map(File::toPath).collect(Collectors.toList())); - SliceRule freeOfCycles = SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles(); - EvaluationResult result = freeOfCycles.evaluate(javaClasses); - File outputFile = getOutputDirectory().file("failure-report.txt").get().getAsFile(); - outputFile.getParentFile().mkdirs(); - if (result.hasViolation()) { - Files.write(outputFile.toPath(), result.getFailureReport().toString().getBytes(StandardCharsets.UTF_8), - StandardOpenOption.CREATE); - FileWriter writer = new FileWriter(outputFile); - FileCopyUtils.copy(result.getFailureReport().toString(), writer); - throw new GradleException("Package tangle check failed. See '" + outputFile + "' for details."); - } - else { - outputFile.createNewFile(); - } - } - - public void setClasses(FileCollection classes) { - this.classes = classes; - } - - @Internal - public FileCollection getClasses() { - return this.classes; - } - - @InputFiles - @SkipWhenEmpty - @IgnoreEmptyDirectories - @PathSensitive(PathSensitivity.RELATIVE) - final FileTree getInputClasses() { - return this.classes.getAsFileTree(); - } - - @OutputDirectory - public abstract DirectoryProperty getOutputDirectory(); - -} diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/Rules.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/Rules.java new file mode 100644 index 0000000000..d12c0a813b --- /dev/null +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/Rules.java @@ -0,0 +1,62 @@ +/* + * Copyright 2023 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.boot.build.architecture; + +import java.util.stream.Stream; + +import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.core.domain.JavaClass.Predicates; +import com.tngtech.archunit.lang.ArchRule; +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition; +import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition; + +/** + * Architecture rules evaluated by {@link ArchitectureCheck}. + * + * @author Andy Wilkinson + */ +final class Rules { + + private Rules() { + + } + + static Stream stream() { + return Stream.of(allPackagesShouldBeFreeOfTangles(), + allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization()); + } + + static ArchRule allPackagesShouldBeFreeOfTangles() { + return SlicesRuleDefinition.slices().matching("(**)").should().beFreeOfCycles(); + } + + static ArchRule allBeanPostProcessorBeanMethodsShouldBeStaticAndHaveParametersThatWillNotCausePrematureInitialization() { + return ArchRuleDefinition.methods() + .that() + .areAnnotatedWith("org.springframework.context.annotation.Bean") + .and() + .haveRawReturnType(Predicates.assignableTo("org.springframework.beans.factory.config.BeanPostProcessor")) + .should() + .beStatic() + .andShould() + .haveRawParameterTypes(DescribedPredicate + .allElements(Predicates.assignableTo("org.springframework.beans.factory.ObjectProvider") + .or(Predicates.assignableTo("org.springframework.context.ApplicationContext")))) + .allowEmptyShould(true); + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java new file mode 100644 index 0000000000..61001c7843 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java @@ -0,0 +1,148 @@ +/* + * Copyright 2012-2023 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.boot.build.architecture; + +import java.io.File; +import java.io.IOException; + +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.testfixtures.ProjectBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.util.FileSystemUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Tests for {@link ArchitectureCheck}. + * + * @author Andy Wilkinson + */ +class ArchitectureCheckTests { + + @TempDir + File temp; + + @Test + void whenPackagesAreTangledTaskFailsAndWritesAReport() throws Exception { + prepareTask("tangled", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture()); + assertThat(failureReport(architectureCheck).length()).isGreaterThan(0); + }); + } + + @Test + void whenPackagesAreNotTangledTaskSucceedsAndWritesAnEmptyReport() throws Exception { + prepareTask("untangled", (architectureCheck) -> { + architectureCheck.checkArchitecture(); + assertThat(failureReport(architectureCheck).length()).isZero(); + }); + } + + File failureReport(ArchitectureCheck architectureCheck) { + return new File(architectureCheck.getProject().getBuildDir(), "checkArchitecture/failure-report.txt"); + } + + @Test + void whenBeanPostProcessorBeanMethodIsNotStaticTaskFailsAndWritesAReport() throws Exception { + prepareTask("bpp/nonstatic", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture()); + assertThat(failureReport(architectureCheck).length()).isGreaterThan(0); + }); + } + + @Test + void whenBeanPostProcessorBeanMethodIsStaticAndHasUnsafeParametersTaskFailsAndWritesAReport() throws Exception { + prepareTask("bpp/unsafeparameters", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture()); + assertThat(failureReport(architectureCheck).length()).isGreaterThan(0); + }); + } + + @Test + void whenBeanPostProcessorBeanMethodIsStaticAndHasSafeParametersTaskSucceedsAndWritesAnEmptyReport() + throws Exception { + prepareTask("bpp/safeparameters", (architectureCheck) -> { + architectureCheck.checkArchitecture(); + assertThat(failureReport(architectureCheck).length()).isZero(); + }); + } + + @Test + void whenBeanPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceedsAndWritesAnEmptyReport() + throws Exception { + prepareTask("bpp/noparameters", (architectureCheck) -> { + architectureCheck.checkArchitecture(); + assertThat(failureReport(architectureCheck).length()).isZero(); + }); + } + + @Test + void whenBeanFactoryPostProcessorBeanMethodIsNotStaticTaskFailsAndWritesAReport() throws Exception { + prepareTask("bfpp/nonstatic", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture()); + assertThat(failureReport(architectureCheck).length()).isGreaterThan(0); + }); + } + + @Test + void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasParametersTaskFailsAndWritesAReport() throws Exception { + prepareTask("bfpp/parameters", (architectureCheck) -> { + assertThatExceptionOfType(GradleException.class).isThrownBy(() -> architectureCheck.checkArchitecture()); + assertThat(failureReport(architectureCheck).length()).isGreaterThan(0); + }); + } + + @Test + void whenBeanFactoryPostProcessorBeanMethodIsStaticAndHasNoParametersTaskSucceedsAndWritesAnEmptyReport() + throws Exception { + prepareTask("bfpp/noparameters", (architectureCheck) -> { + architectureCheck.checkArchitecture(); + assertThat(failureReport(architectureCheck).length()).isZero(); + }); + } + + private void prepareTask(String classes, Callback callback) throws Exception { + File projectDir = new File(this.temp, "project"); + projectDir.mkdirs(); + copyClasses(classes, projectDir); + Project project = ProjectBuilder.builder().withProjectDir(projectDir).build(); + ArchitectureCheck architectureCheck = project.getTasks() + .create("checkArchitecture", ArchitectureCheck.class, (task) -> task.setClasses(project.files("classes"))); + callback.accept(architectureCheck); + } + + private void copyClasses(String name, File projectDir) throws IOException { + PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); + Resource root = resolver.getResource("classpath:org/springframework/boot/build/architecture/" + name); + FileSystemUtils.copyRecursively(root.getFile(), + new File(projectDir, "classes/org/springframework/boot/build/architecture/" + name)); + + } + + private interface Callback { + + void accept(T item) throws Exception; + + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/PackageTangleCheckTests.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/PackageTangleCheckTests.java deleted file mode 100644 index a0909e6f8a..0000000000 --- a/buildSrc/src/test/java/org/springframework/boot/build/architecture/PackageTangleCheckTests.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2012-2023 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.boot.build.architecture; - -import java.io.File; -import java.io.IOException; - -import org.gradle.api.GradleException; -import org.gradle.api.Project; -import org.gradle.testfixtures.ProjectBuilder; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.util.FileSystemUtils; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; - -/** - * Tests for {@link PackageTangleCheck}. - * - * @author Andy Wilkinson - */ -class PackageTangleCheckTests { - - @TempDir - File temp; - - @Test - void whenPackagesAreTangledTaskFailsAndWritesAReport() throws Exception { - prepareTask("tangled", (packageTangleCheck) -> { - assertThatExceptionOfType(GradleException.class) - .isThrownBy(() -> packageTangleCheck.checkForPackageTangles()); - assertThat( - new File(packageTangleCheck.getProject().getBuildDir(), "checkForPackageTangles/failure-report.txt") - .length()) - .isGreaterThan(0); - }); - } - - @Test - void whenPackagesAreNotTangledTaskSucceedsAndWritesAnEmptyReport() throws Exception { - prepareTask("untangled", (packageTangleCheck) -> { - packageTangleCheck.checkForPackageTangles(); - assertThat( - new File(packageTangleCheck.getProject().getBuildDir(), "checkForPackageTangles/failure-report.txt") - .length()) - .isEqualTo(0); - }); - } - - private void prepareTask(String classes, Callback callback) throws Exception { - File projectDir = new File(this.temp, "project"); - projectDir.mkdirs(); - copyClasses(classes, projectDir); - Project project = ProjectBuilder.builder().withProjectDir(projectDir).build(); - PackageTangleCheck packageTangleCheck = project.getTasks() - .create("checkForPackageTangles", PackageTangleCheck.class, - (task) -> task.setClasses(project.files("classes"))); - callback.accept(packageTangleCheck); - } - - private void copyClasses(String name, File projectDir) throws IOException { - PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); - Resource root = resolver.getResource("classpath:org/springframework/boot/build/architecture/" + name); - FileSystemUtils.copyRecursively(root.getFile(), - new File(projectDir, "classes/org/springframework/boot/build/architecture/" + name)); - - } - - private interface Callback { - - void accept(T item) throws Exception; - - } - -} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/nonstatic/NonStaticBeanFactoryPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/nonstatic/NonStaticBeanFactoryPostProcessorConfiguration.java new file mode 100644 index 0000000000..13bf730aaa --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/nonstatic/NonStaticBeanFactoryPostProcessorConfiguration.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012-2023 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.boot.build.architecture.bfpp.nonstatic; + +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.context.annotation.Bean; + +class NonStaticBeanFactoryPostProcessorConfiguration { + + @Bean + BeanFactoryPostProcessor nonStaticBeanFactoryPostProcessor() { + return (beanFactory) -> { + }; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/noparameters/NoParametersBeanFactoryPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/noparameters/NoParametersBeanFactoryPostProcessorConfiguration.java new file mode 100644 index 0000000000..659c9c7960 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/noparameters/NoParametersBeanFactoryPostProcessorConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2023 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.boot.build.architecture.bfpp.noparameters; + +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.context.annotation.Bean; + +class NoParametersBeanFactoryPostProcessorConfiguration { + + @Bean + static BeanFactoryPostProcessor noParametersBeanFactoryPostProcessor() { + return (beanFactory) -> { + }; + } + + @Bean + Integer beanOne() { + return 1; + } + + @Bean + String beanTwo() { + return "test"; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/parameters/ParametersBeanFactoryPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/parameters/ParametersBeanFactoryPostProcessorConfiguration.java new file mode 100644 index 0000000000..e090a654d1 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bfpp/parameters/ParametersBeanFactoryPostProcessorConfiguration.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012-2023 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.boot.build.architecture.bfpp.parameters; + +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.context.annotation.Bean; + +class ParametersBeanFactoryPostProcessorConfiguration { + + @Bean + static BeanFactoryPostProcessor parametersBeanFactoryPostProcessor(Integer param) { + return (beanFactory) -> { + }; + } + + @Bean + Integer beanOne() { + return 1; + } + + @Bean + String beanTwo() { + return "test"; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/nonstatic/NonStaticBeanPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/nonstatic/NonStaticBeanPostProcessorConfiguration.java new file mode 100644 index 0000000000..fe2ab7c116 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/nonstatic/NonStaticBeanPostProcessorConfiguration.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2023 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.boot.build.architecture.bpp.nonstatic; + +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; + +class NonStaticBeanPostProcessorConfiguration { + + @Bean + BeanPostProcessor nonStaticBeanPostProcessor() { + return new BeanPostProcessor() { + + }; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/noparameters/NoParametersBeanPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/noparameters/NoParametersBeanPostProcessorConfiguration.java new file mode 100644 index 0000000000..39d30105ec --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/noparameters/NoParametersBeanPostProcessorConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012-2023 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.boot.build.architecture.bpp.noparameters; + +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.annotation.Bean; + +class NoParametersBeanPostProcessorConfiguration { + + @Bean + static BeanPostProcessor noParametersBeanPostProcessor() { + return new BeanPostProcessor() { + + }; + } + + @Bean + Integer beanOne() { + return 1; + } + + @Bean + String beanTwo() { + return "test"; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/safeparameters/SafeParametersBeanPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/safeparameters/SafeParametersBeanPostProcessorConfiguration.java new file mode 100644 index 0000000000..ae793225fe --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/safeparameters/SafeParametersBeanPostProcessorConfiguration.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012-2023 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.boot.build.architecture.bpp.safeparameters; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; +import org.springframework.core.env.Environment; + +class SafeParametersBeanPostProcessorConfiguration { + + @Bean + static BeanPostProcessor safeParametersBeanPostProcessor(ApplicationContext context, ObjectProvider beanOne, + ObjectProvider beanTwo, Environment environment, @Lazy String beanThree) { + return new BeanPostProcessor() { + + }; + } + + @Bean + Integer beanOne() { + return 1; + } + + @Bean + String beanTwo() { + return "test"; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/unsafeparameters/UnsafeParametersBeanPostProcessorConfiguration.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/unsafeparameters/UnsafeParametersBeanPostProcessorConfiguration.java new file mode 100644 index 0000000000..29d9be81c7 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/bpp/unsafeparameters/UnsafeParametersBeanPostProcessorConfiguration.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012-2023 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.boot.build.architecture.bpp.unsafeparameters; + +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; + +class UnsafeParametersBeanPostProcessorConfiguration { + + @Bean + static BeanPostProcessor unsafeParametersBeanPostProcessor(ApplicationContext context, Integer beanOne, + String beanTwo) { + return new BeanPostProcessor() { + + }; + } + + @Bean + Integer beanOne() { + return 1; + } + + @Bean + String beanTwo() { + return "test"; + } + +} diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java index f359af84e9..ef85155558 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/context/properties/ConfigurationPropertiesReportEndpointProxyTests.java @@ -108,7 +108,7 @@ class ConfigurationPropertiesReportEndpointProxyTests { } @Bean - MethodValidationPostProcessor testPostProcessor() { + static MethodValidationPostProcessor testPostProcessor() { return new MethodValidationPostProcessor(); } diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java index c9784db2c1..ae3768e7a8 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/endpoint/web/annotation/BaseConfiguration.java @@ -70,7 +70,7 @@ class BaseConfiguration { } @Bean - PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java index 1703575e2b..04435b273e 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisAutoConfigurationJedisTests.java @@ -239,7 +239,7 @@ class RedisAutoConfigurationJedisTests { static class JedisConnectionFactoryCaptorConfiguration { @Bean - JedisConnectionFactoryCaptor jedisConnectionFactoryCaptor() { + static JedisConnectionFactoryCaptor jedisConnectionFactoryCaptor() { return new JedisConnectionFactoryCaptor(); } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java index 59d4d7583d..ffc3278f6c 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/validation/ValidationAutoConfigurationTests.java @@ -368,7 +368,7 @@ class ValidationAutoConfigurationTests { static class SampleConfiguration { @Bean - MethodValidationPostProcessor testMethodValidationPostProcessor() { + static MethodValidationPostProcessor testMethodValidationPostProcessor() { return new MethodValidationPostProcessor(); } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java index 432298e17e..f2b531ba02 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/autoconfigure/AbstractDevToolsDataSourceAutoConfigurationTests.java @@ -151,7 +151,7 @@ abstract class AbstractDevToolsDataSourceAutoConfigurationTests { static class DataSourceSpyConfiguration { @Bean - DataSourceSpyBeanPostProcessor dataSourceSpyBeanPostProcessor() { + static DataSourceSpyBeanPostProcessor dataSourceSpyBeanPostProcessor() { return new DataSourceSpyBeanPostProcessor(); } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/PropertySourcesDeducerTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/PropertySourcesDeducerTests.java index 6cb182a36f..2573888628 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/PropertySourcesDeducerTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/PropertySourcesDeducerTests.java @@ -87,7 +87,7 @@ class PropertySourcesDeducerTests { static class PropertySourcesPlaceholderConfigurerConfiguration { @Bean - PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { + static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer(); MutablePropertySources propertySources = new MutablePropertySources(); propertySources.addFirst(new TestPropertySource()); @@ -106,12 +106,12 @@ class PropertySourcesDeducerTests { static class MultiplePropertySourcesPlaceholderConfigurerConfiguration { @Bean - PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer1() { + static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer1() { return new PropertySourcesPlaceholderConfigurer(); } @Bean - PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer2() { + static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer2() { return new PropertySourcesPlaceholderConfigurer(); }