diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java index 7c3691ae042..084e61f2bc0 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/PropertyDescriptorResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -197,7 +197,8 @@ class PropertyDescriptorResolver { } private static boolean isConstructorBoundType(TypeElement type, MetadataGenerationEnvironment env) { - if (env.hasConstructorBindingAnnotation(type)) { + if (env.hasConstructorBindingAnnotation(type) + || "java.lang.Record".equals(type.getSuperclass().toString())) { return true; } if (type.getNestingKind() == NestingKind.MEMBER) { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/AbstractMetadataGenerationTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/AbstractMetadataGenerationTests.java index 1af2311b2a1..55567a17a87 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/AbstractMetadataGenerationTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/AbstractMetadataGenerationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -18,6 +18,7 @@ package org.springframework.boot.configurationprocessor; import java.io.File; import java.io.IOException; +import java.util.Arrays; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.io.TempDir; @@ -54,4 +55,11 @@ public abstract class AbstractMetadataGenerationTests { return processor.getMetadata(); } + protected ConfigurationMetadata compile(File... sources) { + TestConfigurationMetadataAnnotationProcessor processor = new TestConfigurationMetadataAnnotationProcessor( + this.compiler.getOutputLocation()); + this.compiler.getTask(Arrays.asList(sources)).call(processor); + return processor.getMetadata(); + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index b942ee93c0c..756d4b9a4ca 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -16,7 +16,15 @@ package org.springframework.boot.configurationprocessor; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; + import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledForJreRange; +import org.junit.jupiter.api.condition.JRE; +import org.junit.jupiter.api.io.TempDir; import org.springframework.boot.configurationprocessor.metadata.ConfigurationMetadata; import org.springframework.boot.configurationprocessor.metadata.ItemMetadata; @@ -402,4 +410,54 @@ class ConfigurationMetadataAnnotationProcessorTests extends AbstractMetadataGene compile(RecursiveProperties.class); } + @Test + @EnabledForJreRange(min = JRE.JAVA_16) + void explicityBoundRecordProperties(@TempDir File temp) throws IOException { + File exampleRecord = new File(temp, "ExampleRecord.java"); + try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) { + writer.println("@org.springframework.boot.configurationsample.ConstructorBinding"); + writer.println("@org.springframework.boot.configurationsample.ConfigurationProperties(\"explicit\")"); + writer.println("public record ExampleRecord(String someString, Integer someInteger) {"); + writer.println("}"); + } + ConfigurationMetadata metadata = compile(exampleRecord); + assertThat(metadata).has(Metadata.withProperty("explicit.some-string")); + assertThat(metadata).has(Metadata.withProperty("explicit.some-integer")); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_16) + void implicitlyBoundRecordProperties(@TempDir File temp) throws IOException { + File exampleRecord = new File(temp, "ExampleRecord.java"); + try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) { + writer.println("@org.springframework.boot.configurationsample.ConfigurationProperties(\"implicit\")"); + writer.println("public record ExampleRecord(String someString, Integer someInteger) {"); + writer.println("}"); + } + ConfigurationMetadata metadata = compile(exampleRecord); + assertThat(metadata).has(Metadata.withProperty("implicit.some-string")); + assertThat(metadata).has(Metadata.withProperty("implicit.some-integer")); + } + + @Test + @EnabledForJreRange(min = JRE.JAVA_16) + void multiConstructorRecordProperties(@TempDir File temp) throws IOException { + File exampleRecord = new File(temp, "ExampleRecord.java"); + try (PrintWriter writer = new PrintWriter(new FileWriter(exampleRecord))) { + writer.println("@org.springframework.boot.configurationsample.ConfigurationProperties(\"multi\")"); + writer.println("public record ExampleRecord(String someString, Integer someInteger) {"); + writer.println(" @org.springframework.boot.configurationsample.ConstructorBinding"); + writer.println(" public ExampleRecord(String someString) {"); + writer.println(" this(someString, 42);"); + writer.println(" }"); + writer.println(" public ExampleRecord(Integer someInteger) {"); + writer.println(" this(\"someString\", someInteger);"); + writer.println(" }"); + writer.println("}"); + } + ConfigurationMetadata metadata = compile(exampleRecord); + assertThat(metadata).has(Metadata.withProperty("multi.some-string")); + assertThat(metadata).doesNotHave(Metadata.withProperty("multi.some-integer")); + } + }