From 2eeffe79319d7d8dbad3910e6782769c85754efc Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Mon, 6 Jul 2020 17:37:56 -0700 Subject: [PATCH] Add @Name support for value object binding Update value object binder support so that parameters can be annotated with `@Name` if a specific property name should be used. Prior to this commit is was not possible to use Java reserved words as property names. Closes gh-22492 --- .../boot/context/properties/bind/Name.java | 44 +++++++++++++++++++ .../properties/bind/ValueObjectBinder.java | 5 ++- .../bind/ValueObjectBinderTests.java | 26 +++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Name.java diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Name.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Name.java new file mode 100644 index 00000000000..0ae0c236ab5 --- /dev/null +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Name.java @@ -0,0 +1,44 @@ +/* + * Copyright 2012-2020 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.context.properties.bind; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation that can be used to specify the name when binding to an immutable property. + * This annotation may be required when binding to names that clash with reserved language + * keywords. + * + * @author Phillip Webb + * @since 2.4.0 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.PARAMETER }) +@Documented +public @interface Name { + + /** + * The name of the property to use for binding. + * @return the property name + */ + String value(); + +} diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java index 5848f99c164..b08bf683f0c 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/ValueObjectBinder.java @@ -36,6 +36,8 @@ import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.annotation.MergedAnnotations; import org.springframework.core.convert.ConversionException; import org.springframework.util.Assert; @@ -245,7 +247,8 @@ class ValueObjectBinder implements DataObjectBinder { Parameter[] parameters = constructor.getParameters(); List result = new ArrayList<>(parameters.length); for (int i = 0; i < parameters.length; i++) { - String name = names[i]; + String name = MergedAnnotations.from(parameters[i]).get(Name.class) + .getValue(MergedAnnotation.VALUE, String.class).orElse(names[i]); ResolvableType parameterType = ResolvableType.forMethodParameter(new MethodParameter(constructor, i), type); Annotation[] annotations = parameters[i].getDeclaredAnnotations(); diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java index 4321cf47ef0..fa78b82b28b 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/ValueObjectBinderTests.java @@ -40,6 +40,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; * Tests for {@link ValueObjectBinder}. * * @author Madhura Bhave + * @author Phillip Webb */ class ValueObjectBinderTests { @@ -332,6 +333,17 @@ class ValueObjectBinderTests { assertThat(bound.getPath()).isEqualTo(Paths.get("default_value")); } + @Test + void bindToAnnotationNamedParameter() { + MockConfigurationPropertySource source = new MockConfigurationPropertySource(); + source.put("test.import", "test"); + this.sources.add(source); + Bindable target = Bindable.of(NamedParameter.class); + NamedParameter bound = this.binder.bindOrCreate("test", target); + assertThat(bound.getImportName()).isEqualTo("test"); + + } + private void noConfigurationProperty(BindException ex) { assertThat(ex.getProperty()).isNull(); } @@ -730,4 +742,18 @@ class ValueObjectBinderTests { } + static class NamedParameter { + + private final String importName; + + NamedParameter(@Name("import") String importName) { + this.importName = importName; + } + + String getImportName() { + return this.importName; + } + + } + }