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 index 8fd11ee1286..2995bdff992 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java @@ -87,6 +87,8 @@ public abstract class ArchitectureCheck extends DefaultTask { .classFor(getAnnotationClasses().get(), ArchitectureCheckAnnotation.CONDITIONAL_ON_MISSING_BEAN)))); getRules().addAll(whenMainSources(() -> ArchitectureRules.configurationProperties(ArchitectureCheckAnnotation .classFor(getAnnotationClasses().get(), ArchitectureCheckAnnotation.CONFIGURATION_PROPERTIES)))); + getRules().addAll(whenMainSources(() -> ArchitectureRules.configurationProperties(ArchitectureCheckAnnotation + .classFor(getAnnotationClasses().get(), ArchitectureCheckAnnotation.CONFIGURATION_PROPERTIES_SOURCE)))); getRules().addAll(whenMainSources( () -> ArchitectureRules.configurationPropertiesBinding(ArchitectureCheckAnnotation.classFor( getAnnotationClasses().get(), ArchitectureCheckAnnotation.CONFIGURATION_PROPERTIES_BINDING)))); diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheckAnnotation.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheckAnnotation.java index 991bdaa7c27..bd7ff6d6ea7 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheckAnnotation.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheckAnnotation.java @@ -42,6 +42,11 @@ public enum ArchitectureCheckAnnotation { */ CONFIGURATION_PROPERTIES, + /** + * Configuration properties source. + */ + CONFIGURATION_PROPERTIES_SOURCE, + /** * Deprecated configuration property. */ @@ -56,6 +61,8 @@ public enum ArchitectureCheckAnnotation { "org.springframework.boot.autoconfigure.condition.ConditionalOnClass", CONDITIONAL_ON_MISSING_BEAN.name(), "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean", CONFIGURATION_PROPERTIES.name(), "org.springframework.boot.context.properties.ConfigurationProperties", + CONFIGURATION_PROPERTIES_SOURCE.name(), + "org.springframework.boot.context.properties.ConfigurationPropertiesSource", DEPRECATED_CONFIGURATION_PROPERTY.name(), "org.springframework.boot.context.properties.DeprecatedConfigurationProperty", CONFIGURATION_PROPERTIES_BINDING.name(), diff --git a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java index 2a4bb2d92c8..b6c2440c4e9 100644 --- a/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java +++ b/buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java @@ -131,10 +131,10 @@ final class ArchitectureRules { static List configurationProperties(String annotationClass) { return List.of(classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute(annotationClass), methodLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute(annotationClass), - initializedConfigurationPropertiesShouldUse("java.util.Set", "java.util.LinkedHashSet", - annotationClass), - initializedConfigurationPropertiesShouldUse("java.util.Map", - List.of("java.util.LinkedHashMap", "java.util.EnumMap"), annotationClass)); + configurationPropertyOfTypeShouldUseImplementation(annotationClass, "java.util.Set", + List.of("java.util.LinkedHashSet")), + configurationPropertyOfTypeShouldUseImplementation(annotationClass, "java.util.Map", + List.of("java.util.LinkedHashMap", "java.util.EnumMap"))); } static List configurationPropertiesBinding(String annotationClass) { @@ -436,42 +436,36 @@ final class ArchitectureRules { .allowEmptyShould(true); } - private static ArchRule initializedConfigurationPropertiesShouldUse(String propertyType, String implementationType, - String annotationClass) { - return initializedConfigurationPropertiesShouldUse(propertyType, List.of(implementationType), annotationClass); - } - - private static ArchRule initializedConfigurationPropertiesShouldUse(String propertyType, - List implementationTypes, String annotationClass) { + private static ArchRule configurationPropertyOfTypeShouldUseImplementation(String annotationClass, + String propertyType, List allowedImplementations) { return ArchRuleDefinition.classes() .that() .areAnnotatedWith(annotationClass) - .or(areNestedInConfigurationPropertiesClasses(annotationClass)) - .should(useImplementationForInitializedProperties(propertyType, implementationTypes)) - .because("@ConfigurationProperties classes should preserve the property type's expected implementation") + .or(areNestedInClassAnnotatedWith(annotationClass)) + .should(useAllowedImplementationForProperties(propertyType, allowedImplementations)) .allowEmptyShould(true); } - private static ArchCondition useImplementationForInitializedProperties(String propertyType, - List implementationTypes) { - String description = implementationTypes.stream().collect(Collectors.joining(" or ")); - return check("use %s for initialized %s properties".formatted(description, propertyType), + private static ArchCondition useAllowedImplementationForProperties(String propertyType, + List allowedImplementations) { + return check( + "use %s for properties of type %s".formatted(String.join(" or ", allowedImplementations), propertyType), (javaClass, events) -> javaClass.getFields() .stream() .filter((field) -> propertyType.equals(field.getRawType().getName())) - .forEach((field) -> checkPropertyInitializer(field, implementationTypes, events))); + .forEach((field) -> checkPropertyImplementation(field, allowedImplementations, events))); } - private static void checkPropertyInitializer(JavaField field, List implementationTypes, + private static void checkPropertyImplementation(JavaField field, List allowedImplementations, ConditionEvents events) { - String description = implementationTypes.stream().collect(Collectors.joining(" or ")); field.getAccessesToSelf() .stream() .filter((access) -> access.getAccessType() == AccessType.SET) .flatMap((access) -> initializerConstructorCalls(access).stream()) - .filter((call) -> !implementationTypes.contains(call.getTargetOwner().getName())) - .forEach((call) -> addViolation(events, field, "%s should be initialized with %s instead of %s" - .formatted(field.getDescription(), description, call.getTargetOwner().getName()))); + .filter((call) -> !allowedImplementations.contains(call.getTargetOwner().getName())) + .forEach((call) -> addViolation(events, field, + "%s should be initialized with %s instead of %s".formatted(field.getDescription(), + String.join(" or ", allowedImplementations), call.getTargetOwner().getName()))); } private static List initializerConstructorCalls(JavaFieldAccess fieldAccess) { @@ -482,11 +476,11 @@ final class ArchitectureRules { .toList(); } - private static DescribedPredicate areNestedInConfigurationPropertiesClasses(String annotationClass) { - return DescribedPredicate.describe("are nested in @ConfigurationProperties", + private static DescribedPredicate areNestedInClassAnnotatedWith(String annotationClass) { + return DescribedPredicate.describe("one of its inner class", (javaClass) -> javaClass.getEnclosingClass() .map((enclosing) -> enclosing.isAnnotatedWith(annotationClass) - || areNestedInConfigurationPropertiesClasses(annotationClass).test(enclosing)) + || areNestedInClassAnnotatedWith(annotationClass).test(enclosing)) .orElse(false)); } 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 index aaddc9feda3..62224bcefa9 100644 --- a/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java @@ -47,6 +47,7 @@ import org.springframework.boot.build.architecture.annotations.TestConditionalOn import org.springframework.boot.build.architecture.annotations.TestConditionalOnMissingBean; import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties; import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesBinding; +import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesSource; import org.springframework.boot.build.architecture.annotations.TestDeprecatedConfigurationProperty; import org.springframework.util.ClassUtils; import org.springframework.util.FileSystemUtils; @@ -339,6 +340,13 @@ class ArchitectureCheckTests { Task.CHECK_ARCHITECTURE_MAIN, "should be initialized with java.util.LinkedHashMap"); } + @Test + void whenConfigurationPropertiesSourceUsesHashMapShouldFailAndWriteReport() throws IOException { + prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationpropertiessource/hashmap", "annotations"); + buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesSourceAnnotation(), + Task.CHECK_ARCHITECTURE_MAIN, "should be initialized with java.util.LinkedHashMap"); + } + @Test void whenConfigurationPropertiesUsesHashSetShouldFailAndWriteReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/hashset", "annotations"); @@ -346,6 +354,13 @@ class ArchitectureCheckTests { Task.CHECK_ARCHITECTURE_MAIN, "should be initialized with java.util.LinkedHashSet"); } + @Test + void whenConfigurationPropertiesSourceUsesHashSetShouldFailAndWriteReport() throws IOException { + prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationpropertiessource/hashset", "annotations"); + buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesSourceAnnotation(), + Task.CHECK_ARCHITECTURE_MAIN, "should be initialized with java.util.LinkedHashSet"); + } + @Test void whenConfigurationPropertiesUsesLinkedHashMapShouldSucceedAndWriteEmptyReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/linkedhashmap", "annotations"); @@ -353,6 +368,13 @@ class ArchitectureCheckTests { Task.CHECK_ARCHITECTURE_MAIN); } + @Test + void whenConfigurationPropertiesSourceUsesLinkedHashMapShouldSucceedAndWriteEmptyReport() throws IOException { + prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationpropertiessource/linkedhashmap", "annotations"); + build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesSourceAnnotation(), + Task.CHECK_ARCHITECTURE_MAIN); + } + @Test void whenConfigurationPropertiesUsesEnumMapShouldSucceedAndWriteEmptyReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/enummap", "annotations"); @@ -360,6 +382,13 @@ class ArchitectureCheckTests { Task.CHECK_ARCHITECTURE_MAIN); } + @Test + void whenConfigurationPropertiesSourceUsesEnumMapShouldSucceedAndWriteEmptyReport() throws IOException { + prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationpropertiessource/enummap", "annotations"); + build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesSourceAnnotation(), + Task.CHECK_ARCHITECTURE_MAIN); + } + @Test void whenConfigurationPropertiesUsesLinkedHashSetShouldSucceedAndWriteEmptyReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/linkedhashset", "annotations"); @@ -367,6 +396,13 @@ class ArchitectureCheckTests { Task.CHECK_ARCHITECTURE_MAIN); } + @Test + void whenConfigurationPropertiesSourceUsesLinkedHashSetShouldSucceedAndWriteEmptyReport() throws IOException { + prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationpropertiessource/linkedhashset", "annotations"); + build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesSourceAnnotation(), + Task.CHECK_ARCHITECTURE_MAIN); + } + @Test void whenConfigurationPropertiesBindingBeanMethodIsNotStaticShouldFailAndWriteReport() throws IOException { prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/bindingnonstatic", "annotations"); @@ -604,6 +640,12 @@ class ArchitectureCheckTests { return this; } + GradleBuild withConfigurationPropertiesSourceAnnotation() { + configureTasks(ArchitectureCheckAnnotation.CONFIGURATION_PROPERTIES_SOURCE.name(), + TestConfigurationPropertiesSource.class.getName()); + return this; + } + GradleBuild withConfigurationPropertiesBindingAnnotation() { configureTasks(ArchitectureCheckAnnotation.CONFIGURATION_PROPERTIES_BINDING.name(), TestConfigurationPropertiesBinding.class.getName()); diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConfigurationPropertiesSource.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConfigurationPropertiesSource.java new file mode 100644 index 00000000000..d4529eceef4 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConfigurationPropertiesSource.java @@ -0,0 +1,28 @@ +/* + * Copyright 2012-present 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.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface TestConfigurationPropertiesSource { + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/enummap/ConfigurationPropertiesWithEnumMap.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/enummap/ConfigurationPropertiesWithEnumMap.java new file mode 100644 index 00000000000..1c5534177a2 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/enummap/ConfigurationPropertiesWithEnumMap.java @@ -0,0 +1,48 @@ +/* + * Copyright 2012-present 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.configurationpropertiessource.enummap; + +import java.util.EnumMap; +import java.util.Map; + +import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesSource; + +/** + * Test {@link TestConfigurationPropertiesSource} using {@link EnumMap}. + * + * @author Stephane Nicoll + */ +@TestConfigurationPropertiesSource +public class ConfigurationPropertiesWithEnumMap { + + private Map properties = new EnumMap<>(Example.class); + + public Map getProperties() { + return this.properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + + enum Example { + + ONE + + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/hashmap/ConfigurationPropertiesWithHashMap.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/hashmap/ConfigurationPropertiesWithHashMap.java new file mode 100644 index 00000000000..98d6b4e26b4 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/hashmap/ConfigurationPropertiesWithHashMap.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-present 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.configurationpropertiessource.hashmap; + +import java.util.HashMap; +import java.util.Map; + +import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesSource; + +/** + * Test {@link TestConfigurationPropertiesSource} using {@link HashMap}. + * + * @author Stephane Nicoll + */ +@TestConfigurationPropertiesSource +public class ConfigurationPropertiesWithHashMap { + + private Map properties = new HashMap<>(); + + public Map getProperties() { + return this.properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/hashset/ConfigurationPropertiesWithHashSet.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/hashset/ConfigurationPropertiesWithHashSet.java new file mode 100644 index 00000000000..c7b4011f9d9 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/hashset/ConfigurationPropertiesWithHashSet.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-present 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.configurationpropertiessource.hashset; + +import java.util.HashSet; +import java.util.Set; + +import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesSource; + +/** + * Test {@link TestConfigurationPropertiesSource} using {@link HashSet}. + * + * @author Stephane Nicoll + */ +@TestConfigurationPropertiesSource +public class ConfigurationPropertiesWithHashSet { + + private Set items = new HashSet<>(); + + public Set getItems() { + return this.items; + } + + public void setItems(Set items) { + this.items = items; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/linkedhashmap/ConfigurationPropertiesWithLinkedHashMap.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/linkedhashmap/ConfigurationPropertiesWithLinkedHashMap.java new file mode 100644 index 00000000000..d300433a042 --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/linkedhashmap/ConfigurationPropertiesWithLinkedHashMap.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-present 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.configurationpropertiessource.linkedhashmap; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesSource; + +/** + * Test {@link TestConfigurationPropertiesSource} using {@link LinkedHashMap}. + * + * @author Stephane Nicoll + */ +@TestConfigurationPropertiesSource +public class ConfigurationPropertiesWithLinkedHashMap { + + private Map properties = new LinkedHashMap<>(); + + public Map getProperties() { + return this.properties; + } + + public void setProperties(Map properties) { + this.properties = properties; + } + +} diff --git a/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/linkedhashset/ConfigurationPropertiesWithLinkedHashSet.java b/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/linkedhashset/ConfigurationPropertiesWithLinkedHashSet.java new file mode 100644 index 00000000000..085fea8b58d --- /dev/null +++ b/buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationpropertiessource/linkedhashset/ConfigurationPropertiesWithLinkedHashSet.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-present 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.configurationpropertiessource.linkedhashset; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesSource; + +/** + * Test {@link TestConfigurationPropertiesSource} using {@link LinkedHashSet}. + * + * @author Stephane Nicoll + */ +@TestConfigurationPropertiesSource +public class ConfigurationPropertiesWithLinkedHashSet { + + private Set items = new LinkedHashSet<>(); + + public Set getItems() { + return this.items; + } + + public void setItems(Set items) { + this.items = items; + } + +} diff --git a/module/spring-boot-kafka/src/main/java/org/springframework/boot/kafka/autoconfigure/KafkaProperties.java b/module/spring-boot-kafka/src/main/java/org/springframework/boot/kafka/autoconfigure/KafkaProperties.java index 70d4bc6bef3..d622810b5fe 100644 --- a/module/spring-boot-kafka/src/main/java/org/springframework/boot/kafka/autoconfigure/KafkaProperties.java +++ b/module/spring-boot-kafka/src/main/java/org/springframework/boot/kafka/autoconfigure/KafkaProperties.java @@ -59,7 +59,6 @@ import org.springframework.util.unit.DataSize; * @author Andy Wilkinson * @author Scott Frederick * @author Yanming Zhou - * @author Venkata Naga Sai Srikanth Gollapudi * @since 4.0.0 */ @ConfigurationProperties("spring.kafka")