From c8bdf7432733bc99df0b66fe6ec9f0013610e965 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Thu, 15 Dec 2016 09:20:24 +0100 Subject: [PATCH] Expand ConfigurationProperties doc Closes gh-7620 --- .../main/asciidoc/spring-boot-features.adoc | 163 ++++++++++++------ 1 file changed, 115 insertions(+), 48 deletions(-) diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 7745178f7d..8cdb21313c 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -815,34 +815,95 @@ will contain _one_ `MyPojo` entry (with name "`my another name`" and description [[boot-features-external-config-typesafe-configuration-properties]] === Type-safe Configuration Properties Using the `@Value("${property}")` annotation to inject configuration properties can -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 allows strongly typed beans to govern and validate -the configuration of your application. For example: +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 allows strongly typed beans to govern and validate the configuration of +your application. [source,java,indent=0] ---- - @ConfigurationProperties(prefix="connection") - public class ConnectionProperties { + package com.example; + + import java.net.InetAddress; + import java.util.ArrayList; + import java.util.Collections; + import java.util.List; + + import org.springframework.boot.context.properties.ConfigurationProperties; + + @ConfigurationProperties("foo") + public class FooProperties { - private String username; + private boolean enabled; private InetAddress remoteAddress; - // ... getters and setters + private final Security security = new Security(); + + public boolean isEnabled() { ... } + + public void setEnabled(boolean enabled) { ... } + + public InetAddress getRemoteAddress() { ... } + + public void setRemoteAddress(InetAddress remoteAddress) { ... } + + public Security getSecurity() { ... } + + public static class Security { + + private String username; + + private String password; + + private List roles = new ArrayList<>(Collections.singleton("USER")); + + public String getUsername() { ... } + + public void setUsername(String username) { ... } + + public String getPassword() { ... } + public void setPassword(String password) { ... } + + public List getRoles() { ... } + + public void setRoles(List roles) { ... } + + } } ---- -NOTE: The getters and setters are advisable, since binding is via standard Java Beans -property descriptors, just like in Spring MVC. They are mandatory for immutable types or -those that are directly coercible from `String`. As long as they are initialized, maps, -collections, and arrays need a getter but not necessarily a setter since they can be -mutated by the binder. If there is a setter, Maps, collections, and arrays can be created. -Maps and collections can be expanded with only a getter, whereas arrays require a setter. -Nested POJO properties can also be created (so a setter is not mandatory) if they have a -default constructor, or a constructor accepting a single value that can be coerced from -String. Some people use Project Lombok to add getters and setters automatically. +The POJO above defines the following properties: + +* `foo.enabled`, `false` by default +* `foo.remote-address`, with a type that can be coerced from `String` +* `foo.security.username`, with a nested "security" whose name is determined by the name +of the property. In particular the return type is not used at all there and could have +been `SecurityProperties` +* `foo.security.password` +* `foo.security.roles`, with a collection of `String` + +[NOTE] +==== +Getters and setters are usually mandatory, since binding is via standard Java Beans +property descriptors, just like in Spring MVC. There are cases where a setter may be +omitted: + +* Maps, as long as they are initialized, need a getter but not necessarily a setter since +they can be mutated by the binder. +* Collections and arrays can be either accessed via an index (typically with YAML) or +using a single comma-separated value (properties). In the latter case, a setter is +mandatory. We recommend to always add a setter for such types. If you initialize a +collection, make sure it is not immutable (as in the example above) + * If nested POJO properties are initialized (like the `Security` field in the example +above), a setter is not required. If you want the binder to create the instance on-the-fly +using its default constructor, you will need a setter. + +Some people use Project Lombok to add getters and setters automatically. Make sure that +Lombok doesn't generate any particular constructor for such type as it will be used +automatically by the container to instantiate the object. +==== TIP: See also the <>. @@ -853,7 +914,7 @@ You also need to list the properties classes to register in the [source,java,indent=0] ---- @Configuration - @EnableConfigurationProperties(ConnectionProperties.class) + @EnableConfigurationProperties(FooProperties.class) public class MyConfiguration { } ---- @@ -862,29 +923,28 @@ You also need to list the properties classes to register in the ==== When `@ConfigurationProperties` bean is registered that way, the bean will have a conventional name: `-`, where `` is the environment key prefix -specified in the `@ConfigurationProperties` annotation and the fully qualified +specified in the `@ConfigurationProperties` annotation and `` the fully qualified 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 will be `connection-com.example.ConnectionProperties`, -assuming that `ConnectionProperties` sits in the `com.example` package. +The bean name in the example above will be `foo-com.example.FooProperties`. ==== -Even if the configuration above will create a regular bean for `ConnectionProperties`, we +Even if the configuration above will create a regular bean for `FooProperties`, we recommend that `@ConfigurationProperties` only deal with the environment and in particular does not inject other beans from the context. Having said that, The `@EnableConfigurationProperties` annotation is _also_ automatically applied to your project so that any _existing_ bean annotated with `@ConfigurationProperties` will be configured -from the `Environment` properties. You could shortcut `MyConfiguration` above by making -sure `ConnectionProperties` is a already a bean: +from the `Environment`. You could shortcut `MyConfiguration` above by making sure +`FooProperties` is a already a bean: [source,java,indent=0] ---- - @Component - @ConfigurationProperties(prefix="connection") - public class ConnectionProperties { + @Component + @ConfigurationProperties(prefix="foo") + public class FooProperties { - // ... getters and setters + // ... see above } ---- @@ -896,9 +956,13 @@ This style of configuration works particularly well with the ---- # application.yml - connection: - username: admin - remoteAddress: 192.168.1.1 + foo: + remote-address: 192.168.1.1 + security: + username: foo + roles: + - USER + - ADMIN # additional configuration as required ---- @@ -911,26 +975,27 @@ as any other bean. @Service public class MyService { - private final ConnectionProperties connection; + private final FooProperties properties; @Autowired - public MyService(ConnectionProperties connection) { - this.connection = connection; + public MyService(FooProperties properties) { + this.properties = properties; } //... @PostConstruct public void openConnection() { - Server server = new Server(); - this.connection.configure(server); + Server server = new Server(this.properties.getRemoteAddress()); + // ... } } ---- TIP: Using `@ConfigurationProperties` also allows you to generate meta-data files that can -be used by IDEs. See the <> appendix for details. +be used by IDEs to offer auto-completion for your own keys, see the +<> appendix for details. @@ -945,15 +1010,15 @@ its bean registration: [source,java,indent=0] ---- - @ConfigurationProperties(prefix = "foo") + @ConfigurationProperties(prefix = "bar") @Bean - public FooComponent fooComponent() { + public BarComponent barComponent() { ... } ---- -Any property defined with the `foo` prefix will be mapped onto that `FooComponent` bean -in a similar manner as the `ConnectionProperties` example above. +Any property defined with the `bar` prefix will be mapped onto that `BarComponent` bean +in a similar manner as the `FooProperties` example above. @@ -1031,8 +1096,8 @@ annotations to your `@ConfigurationProperties` class: [source,java,indent=0] ---- - @ConfigurationProperties(prefix="connection") - public class ConnectionProperties { + @ConfigurationProperties(prefix="foo") + public class FooProperties { @NotNull private InetAddress remoteAddress; @@ -1044,23 +1109,25 @@ annotations to your `@ConfigurationProperties` class: In order to validate values of nested properties, you must annotate the associated field as `@Valid` to trigger its validation. For example, building upon the above -`ConnectionProperties` example: +`FooProperties` example: [source,java,indent=0] ---- @ConfigurationProperties(prefix="connection") - public class ConnectionProperties { + public class FooProperties { @NotNull + private InetAddress remoteAddress; + @Valid - private RemoteAddress remoteAddress; + private final Security security = new Security(); // ... getters and setters - public static class RemoteAddress { + public static class Security { @NotEmpty - public String hostname; + public String username; // ... getters and setters