diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DataSizeUnit.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DataSizeUnit.java index 658868e645..7e9bc45d81 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DataSizeUnit.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DataSizeUnit.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. @@ -30,9 +30,10 @@ import org.springframework.util.unit.DataUnit; * {@link DataSize}. * * @author Stephane Nicoll + * @author Vladislav Kisel * @since 2.1.0 */ -@Target(ElementType.FIELD) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DataSizeUnit { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationUnit.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationUnit.java index c8b60c9b53..b21057f601 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationUnit.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/DurationUnit.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * 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. @@ -29,9 +29,10 @@ import java.time.temporal.ChronoUnit; * {@link Duration}. * * @author Phillip Webb + * @author Vladislav Kisel * @since 2.0.0 */ -@Target(ElementType.FIELD) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface DurationUnit { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodUnit.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodUnit.java index e34c43948a..ea337e38d6 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodUnit.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/convert/PeriodUnit.java @@ -30,9 +30,10 @@ import java.time.temporal.ChronoUnit; * * @author Eddú Meléndez * @author Edson Chávez + * @author Vladislav Kisel * @since 2.3.0 */ -@Target(ElementType.FIELD) +@Target({ ElementType.FIELD, ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface PeriodUnit { diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java index 3ebdac2118..e5fc2e16c5 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesTests.java @@ -19,6 +19,8 @@ package org.springframework.boot.context.properties; import java.beans.PropertyEditorSupport; import java.io.File; import java.time.Duration; +import java.time.Period; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -54,6 +56,8 @@ import org.springframework.boot.context.properties.bind.DefaultValue; import org.springframework.boot.context.properties.bind.validation.BindValidationException; import org.springframework.boot.context.properties.source.ConfigurationPropertyName; import org.springframework.boot.convert.DataSizeUnit; +import org.springframework.boot.convert.DurationUnit; +import org.springframework.boot.convert.PeriodUnit; import org.springframework.boot.testsupport.system.CapturedOutput; import org.springframework.boot.testsupport.system.OutputCaptureExtension; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -107,6 +111,7 @@ import static org.mockito.Mockito.verify; * @author Phillip Webb * @author Stephane Nicoll * @author Madhura Bhave + * @author Vladislav Kisel */ @ExtendWith(OutputCaptureExtension.class) class ConfigurationPropertiesTests { @@ -129,6 +134,14 @@ class ConfigurationPropertiesTests { assertThat(this.context.getBean(BasicProperties.class).name).isEqualTo("foo"); } + @Test + void testConverting() { + load(BasicConfiguration.class, "name=foo"); + assertThat(this.context.getBeanNamesForType(BasicProperties.class)).hasSize(1); + assertThat(this.context.containsBean(BasicProperties.class.getName())).isTrue(); + assertThat(this.context.getBean(BasicProperties.class).name).isEqualTo("foo"); + } + @Test void loadShouldBindNested() { load(NestedConfiguration.class, "name=foo", "nested.name=bar"); @@ -762,6 +775,22 @@ class ConfigurationPropertiesTests { assertThat(bean.getBar()).isEqualTo(5); } + @Test + void loadWhenBindingToConstructorParametersWithConversionShouldBind() { + MutablePropertySources sources = this.context.getEnvironment().getPropertySources(); + Map source = new HashMap<>(); + source.put("test.size", "2"); + source.put("test.duration", "5"); + source.put("test.period", "6"); + sources.addLast(new MapPropertySource("test", source)); + load(ConstructorParameterWithConversionConfiguration.class); + ConstructorParameterWithConversionProperties bean = this.context + .getBean(ConstructorParameterWithConversionProperties.class); + assertThat(bean.getSize()).isEqualTo(DataSize.ofMegabytes(2)); + assertThat(bean.getDuration()).isEqualTo(Duration.ofDays(5)); + assertThat(bean.getPeriod()).isEqualTo(Period.ofYears(6)); + } + @Test // gh-17831 void loadWhenBindingConstructorParametersViaImportShouldThrowException() { assertThatExceptionOfType(BeanCreationException.class) @@ -777,6 +806,16 @@ class ConfigurationPropertiesTests { assertThat(bean.getBar()).isEqualTo(0); } + @Test + void loadWhenBindingToConstructorParametersWithConversionAndDefaultValuesShouldBind() { + load(ConstructorParameterWithConversionConfiguration.class); + ConstructorParameterWithConversionProperties bean = this.context + .getBean(ConstructorParameterWithConversionProperties.class); + assertThat(bean.getPeriod()).isEqualTo(Period.ofYears(4)); + assertThat(bean.getSize()).isEqualTo(DataSize.ofMegabytes(3)); + assertThat(bean.getDuration()).isEqualTo(Duration.ofDays(2)); + } + @Test void loadWhenBindingToConstructorParametersShouldValidate() { assertThatExceptionOfType(Exception.class) @@ -1933,6 +1972,39 @@ class ConfigurationPropertiesTests { } + @ConstructorBinding + @ConfigurationProperties(prefix = "test") + @Validated + static class ConstructorParameterWithConversionProperties { + + private final DataSize size; + + private final Duration duration; + + private final Period period; + + ConstructorParameterWithConversionProperties(@DefaultValue("4") @PeriodUnit(ChronoUnit.YEARS) Period period, + @DefaultValue("3") @DataSizeUnit(DataUnit.MEGABYTES) DataSize size, + @DefaultValue("2") @DurationUnit(ChronoUnit.DAYS) Duration duration) { + this.size = size; + this.duration = duration; + this.period = period; + } + + Period getPeriod() { + return this.period; + } + + DataSize getSize() { + return this.size; + } + + Duration getDuration() { + return this.duration; + } + + } + @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties @Import(ConstructorParameterProperties.class) @@ -1963,6 +2035,11 @@ class ConfigurationPropertiesTests { } + @EnableConfigurationProperties(ConstructorParameterWithConversionProperties.class) + static class ConstructorParameterWithConversionConfiguration { + + } + @EnableConfigurationProperties(ConstructorParameterValidatedProperties.class) static class ConstructorParameterValidationConfiguration {