From d2164a1ca3195b23ae2c1105c5965b5118901017 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 14 Mar 2019 12:01:23 +0100 Subject: [PATCH] Document constructor binding Closes gh-8762 --- .../main/asciidoc/spring-boot-features.adoc | 135 +++++++++++++++--- 1 file changed, 116 insertions(+), 19 deletions(-) diff --git a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index dc122dc68f..18668d5a73 100644 --- a/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-project/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -894,7 +894,17 @@ Using the `@Value("${property}")` annotation to inject configuration properties sometimes be cumbersome, especially if you are working with multiple properties or your data is hierarchical in nature. Spring Boot provides an alternative method of working with properties that lets strongly typed beans govern and validate the configuration of -your application, as shown in the following example: +your application. + +TIP: See also the <>. + + + +[[boot-features-external-config-java-bean-binding]] +==== JavaBean properties binding +It is possible to bind a bean declaring standard JavaBean properties as shown in the +following example: [source,java,indent=0] ---- @@ -958,13 +968,13 @@ The preceding POJO defines the following properties: the name of the property. In particular, the return type is not used at all there and could have been `SecurityProperties`. * `acme.security.password`. -* `acme.security.roles`, with a collection of `String`. +* `acme.security.roles`, with a collection of `String` that defaults to `USER`. [NOTE] ==== -Getters and setters are usually mandatory, since binding is through standard Java Beans -property descriptors, just like in Spring MVC. A setter may be omitted in the following -cases: +Such arrangement relies on a default empty constructor and getters and setters are usually +mandatory, since binding is through standard Java Beans property descriptors, just like in +Spring MVC. A setter may be omitted in the following cases: * Maps, as long as they are initialized, need a getter but not necessarily a setter, since they can be mutated by the binder. @@ -984,11 +994,84 @@ Finally, only standard Java Bean properties are considered and binding on static properties is not supported. ==== -TIP: See also the <>. -You also need to list the properties classes to register in the -`@EnableConfigurationProperties` annotation, as shown in the following example: + +[[boot-features-external-config-constructor-binding]] +==== Constructor binding +The example in the previous section can be rewritten in an immutable fashion as shown in +the following example: + +[source,java,indent=0] +---- + package com.example; + + import java.net.InetAddress; + import java.util.List; + + import org.springframework.boot.context.properties.ConfigurationProperties; + import org.springframework.boot.context.properties.ConfigurationPropertyDefaultValue; + + @ConfigurationProperties("acme") + public class AcmeProperties { + + private final boolean enabled; + + private final InetAddress remoteAddress; + + private final Security security; + + public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security) { + this.enabled = enabled; + this.remoteAddress = remoteAddress; + this.security = security; + } + + public boolean isEnabled() { ... } + + public InetAddress getRemoteAddress() { ... } + + public Security getSecurity() { ... } + + public static class Security { + + private final String username; + + private final String password; + + private final List roles; + + public Security(String username, String password, + @ConfigurationPropertyDefaultValue("USER") List roles) { + this.username = username; + this.password = password; + this.roles = roles; + } + + public String getUsername() { ... } + + public String getPassword() { ... } + + public List getRoles() { ... } + + } + + } +---- + +In this setup one, and only one constructor must be defined with the list of properties +that you wish to bind and not other properties than the ones in the constructor are bound. + +Default values can be specified using `@ConfigurationPropertyDefaultValue` and the same +conversion service will be applied to coerce the `String` value to the target type of a +missing property. + + + +[[boot-features-external-config-enabling]] +==== Enabling `@ConfigurationProperties`-annotated types +Spring Boot provides an infrastructure to bind such types and register them as beans +automatically. Any `@Configuration` class can specify the list of types to process as +shown in the following example: [source,java,indent=0] ---- @@ -1006,15 +1089,19 @@ specified in the `@ConfigurationProperties` annotation and `` is the fully name of the bean. If the annotation does not provide any prefix, only the fully qualified name of the bean is used. -The bean name in the example above is `acme-com.example.AcmeProperties`. +The bean name in the examples above is `acme-com.example.AcmeProperties`. ==== -The preceding configuration creates a regular bean for `AcmeProperties`. We recommend that -`@ConfigurationProperties` only deal with the environment and, in particular, does not -inject other beans from the context. Keep in mind that the -`@EnableConfigurationProperties` annotation is _also_ automatically applied to your -project so that any _existing_ bean annotated with `@ConfigurationProperties` is -configured from the `Environment`. Instead of annotating `MyConfiguration` with +We recommend that `@ConfigurationProperties` only deal with the environment and, in +particular, does not inject other beans from the context. In particular, it is not +possible to inject other beans using the constructor as this would trigger the constructor +binder that only deals with the environment. + +For corner cases, setter injection can be used or any of the `*Aware` interfaces provided +by the framework (such as `EnvironmentAware` if you need access to the `Environment`). + +If you find using `@EnableConfigurationProperties` tedious, you can also declare a bean +yourself. For instance, instead of annotating `MyConfiguration` with `@EnableConfigurationProperties(AcmeProperties.class)`, you could make `AcmeProperties` a bean, as shown in the following example: @@ -1024,11 +1111,21 @@ a bean, as shown in the following example: @ConfigurationProperties(prefix="acme") public class AcmeProperties { - // ... see the preceding example + private boolean enabled; + + private InetAddress remoteAddress; + + private final Security security = new Security(); + + // ... see the preceding JavaBean properties binding example } ---- + + +[[boot-features-external-config-using]] +==== Using `@ConfigurationProperties`-annotated types This style of configuration works particularly well with the `SpringApplication` external YAML configuration, as shown in the following example: @@ -1097,8 +1194,8 @@ its bean registration, as shown in the following example: } ---- -Any property defined with the `another` prefix is mapped onto that `AnotherComponent` bean -in manner similar to the preceding `AcmeProperties` example. +Any JavaBean property defined with the `another` prefix is mapped onto that +`AnotherComponent` bean in manner similar to the preceding `AcmeProperties` example.