Mastering Spring Boot’s Binder: From Basics to Custom Conversions
This article explains how Spring Boot’s Binder class binds external configuration properties to Java objects, demonstrates basic and custom type conversions, shows how to use binding callbacks, and outlines key places where Binder is employed within Spring Boot and Spring Cloud Gateway.
1. Introduction
This article introduces the powerful Spring Boot class Binder , which binds external configuration properties to Java objects using the @ConfigurationProperties annotation and the bind method.
2. Practical Example
2.1 Prepare Binding Object
<code>public class Person {
private Integer age;
private String name;
// getter, setter
}
</code>Add the corresponding properties to the configuration file:
<code>pack:
person:
age: 20
name: 张三
</code>2.2 Basic Binding
<code>BindResult<Person> result = Binder.get(env).bind("pack.person", Person.class);
Person person = result.get();
System.out.println(person);
</code>The default type converters (e.g., TypeConverterConversionService and ApplicationConversionService ) automatically convert the age string to an Integer .
2.3 Custom Type Conversion
Add a Date field to Person and the corresponding property:
<code>public class Person {
private Integer age;
private String name;
private Date birthday;
// getter, setter
}
</code> <code>pack:
person:
birthday: 2000-01-01
</code>Because the default converters cannot convert String to Date , a custom converter is required:
<code>@Configuration
public class DataTypeConvertConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new Converter<String, Date>() {
@Override
public Date convert(String source) {
try {
return new SimpleDateFormat("yyyy-MM-dd").parse(source);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
});
}
}
</code>Use the custom converter in binding:
<code>Iterable<ConfigurationPropertySource> propertySources = ConfigurationPropertySources.get(env);
Binder binder = new Binder(propertySources, null, conviersionService);
Person result = binder.bindOrCreate("pack.person", Person.class);
System.out.println(result);
</code>The output now shows the correctly populated Person object.
2.4 Binding Callback
Binder allows a BindHandler to receive callbacks at different stages of the binding process:
<code>Iterable<ConfigurationPropertySource> propertySources = ConfigurationPropertySources.get(env);
Binder binder = new Binder(propertySources, null, conviersionService);
Person result = binder.bindOrCreate("pack.person", Bindable.of(Person.class), new BindHandler() {
@Override
public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
System.out.printf("Preparing to bind: %s%n", name);
return target;
}
@Override
public Object onSuccess(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
System.out.printf("Binding succeeded: %s%n", result);
return result;
}
@Override
public Object onCreate(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) {
System.out.printf("Creating bound object: %s%n", result);
return result;
}
@Override
public Object onFailure(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Exception error) throws Exception {
System.out.printf("Binding failed: %s%n", error.getMessage());
return BindHandler.super.onFailure(name, target, context, error);
}
@Override
public void onFinish(ConfigurationPropertyName name, Bindable<?> target, BindContext context, Object result) throws Exception {
System.out.printf("Binding finished: %s%n", result);
BindHandler.super.onFinish(name, target, context, result);
}
});
System.out.println(result);
</code>The console prints messages for each callback stage, as shown in the following screenshot:
3. Where Is Binder Used?
3.1 SpringApplication Startup
During startup, Spring Boot binds spring.main.* properties to the SpringApplication instance:
<code>public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
ConfigurableEnvironment environment = prepareEnvironment(...);
}
private ConfigurableEnvironment prepareEnvironment(...) {
// ...
bindToSpringApplication(environment);
}
protected void bindToSpringApplication(ConfigurableEnvironment environment) {
try {
Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
} catch (Exception e) { }
}
}
</code>3.2 @ConfigurationProperties Binding
Classes annotated with @ConfigurationProperties are processed by ConfigurationPropertiesBindingPostProcessor , which uses a ConfigurationPropertiesBinder to perform the actual binding:
<code>public class ConfigurationPropertiesBindingPostProcessor {
private ConfigurationPropertiesBinder binder;
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
bind(ConfigurationPropertiesBean.get(applicationContext, bean, beanName));
return bean;
}
}
</code> <code>class ConfigurationPropertiesBinder {
BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
Bindable<?> target = propertiesBean.asBindTarget();
ConfigurationProperties annotation = propertiesBean.getAnnotation();
BindHandler bindHandler = getBindHandler(target, annotation);
return getBinder().bind(annotation.prefix(), target, bindHandler);
}
}
</code>3.3 Spring Cloud Gateway Route Binding
When a request arrives, the gateway converts route definitions from YAML into Route objects, using Binder to bind configuration properties to filter and predicate beans:
<code>public class RoutePredicateHandlerMapping {
protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
return lookupRoute(exchange);
}
protected Mono<Route> lookupRoute(...) {
return routeLocator.getRoutes()...;
}
}
</code> <code>public class RouteDefinitionRouteLocator {
public Flux<Route> getRoutes() {
Flux<Route> routes = routeDefinitionLocator.getRouteDefinitions()
.map(this::convertToRoute);
return routes;
}
private Route convertToRoute(RouteDefinition routeDefinition) {
AsyncPredicate<ServerWebExchange> predicate = combinePredicates(routeDefinition);
List<GatewayFilter> filters = getFilters(routeDefinition);
return new Route(...);
}
private List<GatewayFilter> getFilters(RouteDefinition routeDefinition) {
List<GatewayFilter> filters = new ArrayList<>();
if (!gatewayProperties.getDefaultFilters().isEmpty()) {
filters.addAll(loadGatewayFilters(routeDefinition.getId(),
new ArrayList<>(gatewayProperties.getDefaultFilters())));
}
return filters;
}
private List<GatewayFilter> loadGatewayFilters(...) {
Object configuration = configurationService.with(factory)
// ...
.bind();
return ...;
}
}
</code>These examples illustrate that Binder is the central mechanism for externalized configuration throughout Spring Boot and related projects.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.