6.2.1 Defining configuration property holders
There’s nothing that says @ConfigurationProperties must be set on a controller or any other specific kind of bean. @ConfigurationProperties are in fact often placed on beans whose sole purpose in the application is to be holders of configuration data. This keeps configuration-specific details out of the controllers and other application classes. It also makes it easy to share common configuration properties among several beans that may make use of that information.
In the case of the pageSize property in OrderController, you could extract it to a separate class. The following listing uses the OrderProps class in such a way.
Listing 6.2 Extracting pageSize to a holder class
package tacos.web;
import org.springframework.boot.context.properties.
ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
@Component
@ConfigurationProperties(prefix="taco.orders")
@Data
public class OrderProps {
private int pageSize = 20;
}As you did with OrderController, the pageSize property defaults to 20, and OrderProps is annotated with @ConfigurationProperties to have a prefix of taco.orders. It’s also annotated with @Component so that Spring component scanning will automatically discover it and create it as a bean in the Spring application context. This is important, because the next step is to inject the OrderProps bean into OrderController.
There’s nothing particularly special about configuration property holders. They’re beans that have their properties injected from the Spring environment. They can be injected into any other bean that needs those properties. For OrderController, this means removing the pageSize property from OrderController and instead injecting and using the OrderProps bean, as shown next:
private OrderProps props;
public OrderController(OrderRepository orderRepo,
OrderProps props) {
this.orderRepo = orderRepo;
this.props = props;
}
...
@GetMapping
public String ordersForUser(
@AuthenticationPrincipal User user, Model model) {
Pageable pageable = PageRequest.of(0, props.getPageSize());
model.addAttribute("orders",
orderRepo.findByUserOrderByPlacedAtDesc(user, pageable));
return "orderList";
}Now OrderController is no longer responsible for handling its own configuration properties. This keeps the code in OrderController slightly neater and allows you to reuse the properties in OrderProps in any other bean that may need them. Moreover, you’re collecting configuration properties that pertain to orders in one place: the OrderProps class. If you need to add, remove, rename, or otherwise change the properties therein, you need to apply those changes only in OrderProps. And for testing purposes, it’s easy to set configuration properties directly on a test-specific OrderProps and give it to the controller prior to the test.
For example, let’s pretend that you’re using the pageSize property in several other beans when you decide it would be best to apply some validation to that property to limit its values to no less than 5 and no more than 25. Without a holder bean, you’d have to apply validation annotations to OrderController, the pageSize property, and all other classes using that property. But because you’ve extracted pageSize into OrderProps, you only must make the changes to OrderProps, as shown here:
package tacos.web;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import org.springframework.boot.context.properties.
ConfigurationProperties;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import lombok.Data;
@Component
@ConfigurationProperties(prefix="taco.orders")
@Data
@Validated
public class OrderProps {
@Min(value=5, message="must be between 5 and 25")
@Max(value=25, message="must be between 5 and 25")
private int pageSize = 20;
}Although you could as easily apply the @Validated, @Min, and @Max annotations to OrderController (and any other beans that can be injected with OrderProps), it would just clutter up OrderController that much more. With a configuration property holder bean, you’ve collected configuration property specifics in one place, leaving the classes that need those properties relatively clean.
