From b29e81fcd9dc705bca4eb4482434fabb8e0db2ff Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Sat, 3 Aug 2019 11:58:28 +0100 Subject: [PATCH] Allow defaultBindHandler to be specified on Binder Allow a `defaultBindHandler` to be specified on the `Binder` instance to save needing to pass it to each `bind` method call. Closes gh-17773 --- .../boot/context/properties/bind/Binder.java | 42 +++++++++++++++++-- .../context/properties/bind/BinderTests.java | 14 +++++++ 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java index fefa6208a35..22044710b03 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/Binder.java @@ -65,6 +65,8 @@ public class Binder { private final Consumer propertyEditorInitializer; + private final BindHandler defaultBindHandler; + /** * Create a new {@link Binder} instance for the specified sources. A * {@link DefaultFormattingConversionService} will be used for all conversion. @@ -116,12 +118,32 @@ public class Binder { */ public Binder(Iterable sources, PlaceholdersResolver placeholdersResolver, ConversionService conversionService, Consumer propertyEditorInitializer) { + this(sources, placeholdersResolver, conversionService, propertyEditorInitializer, null); + } + + /** + * Create a new {@link Binder} instance for the specified sources. + * @param sources the sources used for binding + * @param placeholdersResolver strategy to resolve any property placeholders + * @param conversionService the conversion service to convert values (or {@code null} + * to use {@link ApplicationConversionService}) + * @param propertyEditorInitializer initializer used to configure the property editors + * that can convert values (or {@code null} if no initialization is required). Often + * used to call {@link ConfigurableListableBeanFactory#copyRegisteredEditorsTo}. + * @param defaultBindHandler the default bind handler to use if non is specified when + * binding + * @since 2.2.0 + */ + public Binder(Iterable sources, PlaceholdersResolver placeholdersResolver, + ConversionService conversionService, Consumer propertyEditorInitializer, + BindHandler defaultBindHandler) { Assert.notNull(sources, "Sources must not be null"); this.sources = sources; this.placeholdersResolver = (placeholdersResolver != null) ? placeholdersResolver : PlaceholdersResolver.NONE; this.conversionService = (conversionService != null) ? conversionService : ApplicationConversionService.getSharedInstance(); this.propertyEditorInitializer = propertyEditorInitializer; + this.defaultBindHandler = (defaultBindHandler != null) ? defaultBindHandler : BindHandler.DEFAULT; } /** @@ -254,7 +276,7 @@ public class Binder { private T bind(ConfigurationPropertyName name, Bindable target, BindHandler handler, boolean create) { Assert.notNull(name, "Name must not be null"); Assert.notNull(target, "Target must not be null"); - handler = (handler != null) ? handler : BindHandler.DEFAULT; + handler = (handler != null) ? handler : this.defaultBindHandler; Context context = new Context(); return bind(name, target, handler, context, false, create); } @@ -439,8 +461,22 @@ public class Binder { * @return a {@link Binder} instance */ public static Binder get(Environment environment) { - return new Binder(ConfigurationPropertySources.get(environment), - new PropertySourcesPlaceholdersResolver(environment)); + return get(environment, null); + } + + /** + * Create a new {@link Binder} instance from the specified environment. + * @param environment the environment source (must have attached + * {@link ConfigurationPropertySources}) + * @param defaultBindHandler the default bind handler to use if non is specified when + * binding + * @return a {@link Binder} instance + * @since 2.2.0 + */ + public static Binder get(Environment environment, BindHandler defaultBindHandler) { + Iterable sources = ConfigurationPropertySources.get(environment); + PropertySourcesPlaceholdersResolver placeholdersResolver = new PropertySourcesPlaceholdersResolver(environment); + return new Binder(sources, placeholdersResolver, null, null, defaultBindHandler); } /** diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java index 538376b2b00..af637abf733 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BinderTests.java @@ -210,6 +210,20 @@ class BinderTests { isA(JavaBean.class)); } + @Test + void bindWhenHasCustomDefultHandlerShouldTriggerOnSuccess() { + this.sources.add(new MockConfigurationPropertySource("foo.value", "bar", "line1")); + BindHandler handler = mock(BindHandler.class, Answers.CALLS_REAL_METHODS); + Binder binder = new Binder(this.sources, null, null, null, handler); + Bindable target = Bindable.of(JavaBean.class); + binder.bind("foo", target); + InOrder inOrder = inOrder(handler); + inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo.value")), eq(Bindable.of(String.class)), + any(), eq("bar")); + inOrder.verify(handler).onSuccess(eq(ConfigurationPropertyName.of("foo")), eq(target), any(), + isA(JavaBean.class)); + } + @Test void bindWhenHasMalformedDateShouldThrowException() { this.sources.add(new MockConfigurationPropertySource("foo", "2014-04-01T01:30:00.000-05:00"));