Spring Boot for Cloud: Configuration and Dependencies
An overview of Spring Boot framework features that are especially important in the context of Spring Cloud applications. In this article, we will discuss dependencies and configuration management.
Join the DZone community and get the full member experience.
Join For FreeThe natural choice to implement a micro-service application in the realm of the Spring framework would be to use the Spring Cloud set of modules. But those modules cannot work without a solid platform underneath. Spring Boot sits on top of the Spring framework and is an effective base for Spring Cloud libraries (from this consideration the title of this article "Spring Boot for Cloud" makes some sense).
Spring Boot has some features that can be of great help in reducing the complexity of micro-service architectures:
- It simplifies dependency management, with the use of the so-called starters.
- It offers auto-configuration behavior on startup.
- It provides an easy and effective way to implement REST APIs (see Spring Boot for Cloud - API Development).
- It exposes a number of endpoints that are useful to monitor, gather statistics and manage the application (see Spring Boot for Cloud - Actuator).
In the following sections, we will explain the first two points mentioned above, while the last two will be covered in separate posts (see the links above).
Spring Boot Dependencies Management
When we deal with complex infrastructures the last thing we want is to be overwhelmed by dependency management chaos. This is particularly true for micro-service scenarios. Spring Boot introduces a very important concept to reduce the burden based on the so-called "starters." Starters are dependency artifacts that aim at providing a full set of libraries focused on some application macro-area.
For instance, there is a starter to provide all the libraries required to implement a web application, another to implement a JPA layer, and so on. Every starter has its own version and its child dependencies contain the versions that are fit to provide the whole set consistency. We can see an example of a Maven starter declaration in the following configuration fragment:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
The spring-boot-starter-web dependency contains all the sub-dependencies required to implement a web layer in our project. An important concept to highlight is that all the libraries in the set are fully tested. That frees us from the nightmare of introducing dependencies one by one and testing them on the way, wasting an enormous amount of time. The starters allow us to concentrate solely on the application logic we want to implement.
There are two equivalent ways two define the basic configuration of a Spring Boot project in Maven:
- Defining a parent section with the spring-boot-starter-parent artifact
- Defining a dependencyManagement section with the spring-boot-dependencies artifact
If we use the first option, our POM will inherit from the spring-boot-starter-parent artifact, which in turn will inherit from a spring-boot-dependencies defined in a parent POM, in a special Maven section named dependencyManagement. We can see below the related configuration fragment:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.0</version>
</parent>
With the second option on the other hand we are defining the spring-boot-dependencies artifact directly in our POM with a dependencyManagement section.
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Both ways are involved in a mechanism known as Maven BOM, which stands for Bill of Materials. A BOM is essentially a POM with a dependencyManagement section. The dependencies defined inside this special section are not really imported into the project but are only declared with the wanted version. Then, if we structure our project with a hierarchy of POMs with the BOM as the root of the hierarchy, we can define our child dependencies without specifying the version, which will be inherited by the BOM.
This mechanism is especially useful if we define in the dependencyManagement section an artifact that acts as a container of other dependencies, just like starters do. In fact, the spring-boot-dependencies artifact contains a set of dependencies required to implement a Spring Boot application.
If we define a dependencyManagement section with spring-boot-dependencies (or define a parent section with spring-boot-starter-parent, which is equivalent), we can then define the single required dependencies, using the regular dependencies tag, in a child POM, without putting a version, since the version is taken implicitly from the spring-boot-dependencies declaration.
This way we have a fully centralized way to control our set of dependencies, and we can be sure that they are fully tested and compliant with one another.
Spring Boot Configuration
Spring Boot tries to ease the configuration process as much as possible. When we choose a set of base starters for our application, we also have a full set of implicit configurations in place. We can leave it as it is and the application will start simply by means of the dependency choices we made. For instance, if we include a Spring Web starter in our set of dependencies, without setting any configuration, a web application with an embedded tomcat with a default port will be run.
If we still want to customize it, and this will be certainly the case in most of the real scenarios, we can always override the defaults by writing our configuration values. The typical way of configuring a Spring Boot application is to use files with .properties or YAML format. If we prefix those files with "application" (or with profile-specific names such as "application-dev" or "application-prod"), they will be detected automatically at startup and all the configuration values will be applied.
In order to be detected and loaded, the configuration files must be placed in one of the following:
- The classpath root
- A config package in the classpath
- The application's root directory
- A config sub-directory of the application root directory
The build process will put the configuration files in the generated artifact (jar or war), ready to be loaded during the application startup. There is even the possibility of changing the default "application" prefix with a custom name passing the spring.config.name as an environment property during startup.
By editing the configuration files we can override the predefined properties used by Spring Boot dependencies, but we can also define our own properties and use them in our code by two specific annotations:
- @Value: used to get a single-valued property
- @ConfigurationProperties: used to get a multiple-valued property
If we have, for instance, a piece of configuration like this in a YAML file:
myproperty: value
myproperties:
properties:
- value1
- value2
We can then use the properties defined above as shown in the following example class:
@Component
@ConfigurationProperties(prefix = "myproperties")
public class MyClass {
private List<String> properties = new ArrayList<String>();
public List<String> getProperties() {
return properties;
}
@Value("${myproperty}")
private String myproperty;
}
Generating an Application With Spring Initializr
An easy way to start a new Spring Boot application is using the online tool Spring Initializr. As we can see in the screenshot below, we can insert the metadata of our project, choose what build tool to use (Gradle or Maven), what Language, Spring Boot version, packaging (war or jar), and Java version. We can then use the "Add Dependencies" button to choose which starters we want to base our project on. Clicking eventually on the "Generate" button, a zip file with our sample project will be generated and ready to download.
In the example above, we chose a single dependency of type Spring Web, and we can see below the related POM content:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
The POM above has spring-boot-starter-parent as a parent dependency with version 2.7.5, and that means that it inherits a set of dependencies declarations from the dependencyManagement section of a parent POM containing a spring-boot-dependencies artifact with the same version. The dependency section then defines two starters:
- spring-boot-starter-web
- spring-boot-starter-test
Both starters do not define any version, and from what we have learned in the previous sections, they inherit it from the spring-boot-starter-parent set. The spring-boot-starter-web starter includes all the libraries needed to implement a web application, with an embedded tomcat web server, implicitly set with a default port.
If we look at the application.properties file in the resources folder we can see that is empty. This is related to the auto-configuration features of Spring Boot. A set of implicit values for the configuration properties is automatically chosen, and that includes the web server port. If we want to override it, we can set it in the application.properties file, with the following:
server.port=9080
Conclusion
Spring Cloud is a set of libraries aimed at implementing the architectural patterns and scenarios involved in micro-service applications. However, having a set of robust modules focused on single problems is not enough. We also need a common solid platform for those libraries, capable of reducing the complexity involved in the micro-service scenarios. Spring Boot stands a level below the Spring Cloud modules and offers a number of important features to alleviate the burden involved in dependency management, configuration, and other common tasks.
In a separate article, we will cover how Spring Boot can be used to develop a REST API, and explain how API development can be made easier by documenting it using a tool like Swagger.
Published at DZone with permission of Mario Casari. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments