Spring Boot is an opinionated Java framework for building microservices based on the Spring dependency injection framework. Spring Boot facilitates creation of microservices through reduced boilerplate, configuration, and developer friction. This is a similar approach to the two other frameworks we’ll look at.
Spring Boot offers the following advantages in comparison to the Spring framework:
Favoring automatic, conventional configuration by default
Curating sets of popular starter dependencies for easier consumption
Simplifying application packaging
Baking in application insight (e.g., metrics and environment info)
Spring historically was a nightmare to configure. Although the framework
improved upon other high-ceremony component models (EJB 1.x, 2.x, etc.),
it did come along with its own set of heavyweight usage patterns.
Namely, Spring required a lot of XML configuration and a deep
understanding of the individual beans needed to construct JdbcTemplates
,
JmsTemplates
, BeanFactory
lifecycle hooks, servlet listeners, and many
other components. In fact, writing a simple “Hello World” with Spring MVC required understanding of DispatcherServlet and a whole host of
Model–View–Controller classes. Spring Boot aims to eliminate all of this
boilerplate configuration with some implied conventions and simplified
annotations—although, you can still finely tune the underlying beans
if you need to.
Spring was used in large enterprise applications that typically leveraged lots of different technologies to do the heavy lifting: JDBC databases, message queues, file systems, application-level caching, etc. Developers often had to stop what they were doing, switch cognitive contexts, figure out what dependencies belonged to which piece of functionality (“Oh, I need the JPA dependencies!”), and spend lots of time sorting out versioning mismatches and other issues that arose when trying to use these various pieces together. Spring Boot offers a large collection of curated sets of libraries for adding these pieces of functionality. These starter modules allow you to add things like:
Java Persistence API (JPA)
NoSQL databases like MongoDB, Cassandra, and Couchbase
Redis caching
Tomcat/Jetty/Undertow servlet engines
Java Transaction API (JTA)
Adding a submodule to an application brings in a curated set of transitive dependencies and versions that are known to work together, saving developers from having to sort out dependencies themselves.
Spring Boot really is a set of bootstrap libraries with some convention
for configurations, but there’s no reason why you couldn’t run a Spring
Boot application inside your existing application servers as a Web Application Archive (WAR). The idiom that most developers who use Spring Boot prefer for their applications is the self-contained Java Archive (JAR) packaging, where all dependencies and application code are bundled together with a flat class loader in a single JAR. This makes it easier to understand application startup, dependency ordering, and log statements. More importantly, it also helps reduce the
number of moving pieces required to take an app safely to production.
You don’t take an app and chuck it into an app server; the
app, once it’s built, is ready to run as is—standalone—including
embedding its own servlet container if it uses servlets. That’s right, a
simple java -jar
<name.jar>
is enough to start your application now!
Spring Boot, MicroProfile/Thorntail, and many other frameworks like Vert.x and Dropwizard all follow this pattern of
packaging everything into an executable uber-AR.
But what about the management things we typically expect out of an application server?
Spring Boot ships with a module called spring boot actuator
that enables things
like metrics and statistics about your application. For example, you can
collect logs, view metrics, perform thread dumps, show environment
variables, understand garbage collection, and show which beans are configured
in the BeanFactory. You can expose this information via HTTP or Java Management Extensions (JMX), or you can even log in directly to the process via SSH.
With Spring Boot, you can leverage the power of the Spring framework and reduce boilerplate configuration and code to more quickly build powerful, production-ready microservices. Let’s see how.
We’re going to use the Spring Boot command-line interface (CLI) to bootstrap our first Spring Boot application (the CLI uses Spring Initializr under the covers). You are free to explore the different ways to do this if you’re not comfortable with the CLI. Alternatives include using Spring Initializr plug-ins for your favorite IDE, or using the web version. The Spring Boot CLI can be installed a few different ways, including through package managers and by downloading it straight from the website. Check for instructions on installing the CLI most appropriate for your development environment.
Once you’ve installed the CLI tools, you should be able to check the version of Spring you have:
$ spring --version Spring CLI v2.1.1.RELEASE
If you can see a version for your installation of the CLI, congrats! Now navigate to the directory where you want to host your examples from the report and run the following command:
$ spring init --build maven --groupId com.redhat.examples --version 1.0 --java-version 1.8 --dependencies web --name hello-springboot hello-springboot
After running this command, you should have a directory named hello-springboot with a complete Spring Boot application. If you run the command and end up with a demo.zip, then just unzip it and continue. Let’s take a quick look at what those command-line options are:
--build
The build management tool we want to use. maven
and
gradle
are the two valid options at this time.
--groupId
The groupId
to use in our Maven coordinates for our
pom.xml. Unfortunately this does not properly extend to the Java
package names that get created; these need to be modified by hand.
--version
The version of our application. This will be used in later iterations, so set to 1.0.
--java-version
The build compiler version for the JDK.
--dependencies
This is an interesting parameter; we can specify
fully baked sets of dependencies for doing common types of development.
For example, web
will set up Spring MVC and embed an internal servlet
engine (Tomcat by default, Jetty and Undertow as options). Other convenient dependency bundles/starters include jpa
, security
, and cassandra
).
Now, from the hello-springboot directory, try running the following command:
$ mvn spring-boot:run
If everything boots up without any errors, you should see some logging similar to this:
2018-12-13 13:18:19 ---[
main]
TomcatWebServer : Tomcat started on port(
s)
:8080
(
http)
with context path''
2018-12-13 13:18:19 ---[
main]
HelloSpringbootApplication : Started HelloSpringbootApplication in 2.3 seconds(
JVM runningfor
10.265)
Congrats! You have just gotten your first Spring Boot application up and running. If you navigate to http://localhost:8080 in your browser, you should see the output shown in Figure 2-1.
This default error page is expected since our application doesn’t do anything yet. Let’s move on to the next section to add a REST endpoint to put together a Hello World use case.
Now that we have a Spring Boot application that can run, let’s add some
simple functionality. We want to expose an HTTP/REST endpoint at
/api/hello that will return “Hello Spring Boot from X" where X is the IP
address where the service is running. To do this, navigate to
src/main/java/com/examples/hellospringboot. This location should have been created for
you if you followed the preceding steps. Then create a new Java class called HelloRestController
, as shown in Example 2-1. We’ll add a method named hello()
that returns a string along with the IP address of where the service is running. You’ll see in Chapter 6, when we discuss load balancing and service discovery, how the host IPs can be used to demonstrate proper failover, load balancing, etc.
public
class
HelloRestController
{
public
String
hello
()
{
String
hostname
=
null
;
try
{
hostname
=
InetAddress
.
getLocalHost
()
.
getHostAddress
();
}
catch
(
UnknownHostException
e
)
{
hostname
=
"unknown"
;
}
return
"Hello Spring Boot from "
+
hostname
;
}
}
At this point, this piece of code is just a POJO (plain old Java object), and you could (and should) write a unit test that verifies its behavior. To expose this as a REST endpoint, we’re going to make use of the following annotations in Example 2-2:
@RestController
Tells Spring this is an HTTP controller capable of exposing HTTP endpoints (GET, PUT, POST, etc.)
@RequestMapping
Maps specific parts of the HTTP URI path to classes, methods, and parameters in the Java code
Note that import statements are omitted.
@RestController
@RequestMapping
(
"/api"
)
public
class
HelloRestController
{
@RequestMapping
(
method
=
RequestMethod
.
GET
,
value
=
"/hello"
,
produces
=
"text/plain"
)
public
String
hello
(){
String
hostname
=
null
;
try
{
hostname
=
InetAddress
.
getLocalHost
()
.
getHostAddress
();
}
catch
(
UnknownHostException
e
)
{
hostname
=
"unknown"
;
}
return
"Hello Spring Boot from "
+
hostname
;
}
}
In this code, all we’ve done is add the aforementioned annotations. For
example, @RequestMapping("/api")
at the class level says “map any
method-level HTTP endpoints under this root URI path.” When we add
@RequestMapping(method = RequestMethod.GET, value = "/hello", produces = "text/plain")
, we are telling Spring to expose an HTTP GET endpoint at /hello (which will really be /api/hello) and map requests with a media type of
Accept: text/plain
to this method. Spring Boot defaults to using an
embedded Tomcat servlet container, but this can be switched to other
options like Undertow or Jetty.
If you build the application and run spring-boot:run
again, you should
be able to reach your HTTP endpoint:
$ mvn clean spring-boot:run
Now if you point your browser to http://localhost:8080/api/hello, you should see a response similar to Figure 2-2.
What if we want to add some environment-aware configuration to our application? For example, instead of saying “Hello,” maybe you want to say “Guten Tag” if we deploy our app in production for German users. We need a way to inject properties into our app.
Spring Boot makes it easy to use external property sources like
properties files, command-line arguments, the OS environment, or Java
system properties. We can even bind entire “classes” of properties to
objects in our Spring context. For example, if we want to bind all
helloapp.*
properties to the HelloRestController
, we can add
@ConfigurationProperties(prefix="helloapp")
, and Spring Boot will
automatically try to bind helloapp.foo
and helloapp.bar
to Java Bean
properties in the HelloRestController
class. We can define new
properties in src/main/resources/application.properties. The application.properties file was automatically
created for us when we created our project. (Note that we could change the
filename to application.yml and Spring would still recognize the
YAML file as the source of properties.)
Let’s add a new property to our src/main/resources/application.properties file:
helloapp.saying=Guten Tag aus
Next we add the @ConfigurationProperties
annotation and our new saying
field to the HelloRestController
class, as shown in Example 2-3. Note we also need setters.
@RestController
@RequestMapping
(
"/api"
)
@ConfigurationProperties
(
prefix
=
"helloapp"
)
public
class
HelloRestController
{
private
String
saying
;
@RequestMapping
(
method
=
RequestMethod
.
GET
,
value
=
"/hello"
,
produces
=
"text/plain"
)
public
String
hello
(){
String
hostname
=
null
;
try
{
hostname
=
InetAddress
.
getLocalHost
()
.
getHostAddress
();
}
catch
(
UnknownHostException
e
)
{
hostname
=
"unknown"
;
}
return
saying
+
" "
+
hostname
;
}
public
void
setSaying
(
String
saying
)
{
this
.
saying
=
saying
;
}
}
Stop the application from running (if you haven’t already) and restart it:
$ mvn clean spring-boot:run
Now if you navigate to http://localhost:8080/api/hello, you should see the German version of the saying as shown in Figure 2-3.
We can now externalize properties that will change depending on the environment in which we are running. Things like service URIs, database URIs and passwords, and message queue configurations would all be great candidates for external configuration. Don’t overdo it, though; not everything needs to change depending on the environment! Ideally an application would be configured exactly the same in all environments, including timeouts, thread pools, retry thresholds, etc.
If we want to put this microservice into production, how will we monitor
it? How can we get any insight about how things are running? Often
our microservices are black boxes unless we explicitly think through how
we want to expose metrics to the outside world. Fortunately, Spring Boot comes with a
prepackaged starter (spring-boot-starter-actuator
) that makes doing this a breeze.
Let’s see what it takes to enable the actuator. Open up the pom.xml
file for your hello-springboot microservice and add the following Maven
dependency in the <dependencies>...</dependencies>
section:
<dependency>
<groupId>
org.springframework.boot</groupId>
<artifactId>
spring-boot-starter-actuator</artifactId>
</dependency>
Because we’ve added the actuator dependency, our application now can expose a lot of information that will be very handy for debugging or general microservice insight.
Not all endpoints provided by the actuator dependency are exposed by default, however. We need to manually specify which endpoints will be exposed.
Add the following property to src/main/resources/application.properties to expose some technology-agnostic endpoints:
#
Enable
management
endpoints
management
.
endpoints
.
web
.
exposure
.
include
=
beans
,
env
,
health
,
metrics
,
httptrace
,
mappings
Now restart your microservice by stopping it and running:
$ mvn clean spring-boot:run
Try hitting the following URLs and examine what gets returned:
http://localhost:8080/actuator/beans
http://localhost:8080/actuator/env
http://localhost:8080/actuator/health
http://localhost:8080/actuator/metrics
http://localhost:8080/actuator/httptrace
http://localhost:8080/actuator/mappings
Figure 2-4 shows an example of what the http://localhost:8080/env endpoint looks like.
Exposing runtime insights like this frees up the developer to just focus on writing code for the microservice that delivers business value. Delegating the heavy lifting and boilerplate to frameworks is definitely a good idea.
Up to this point we’ve been thinking about development and building our Hello World microservice from the perspective of a developer’s laptop using Maven. But what if you want to distribute your microservice to others or run it in a live environment (development, QA, production)?
Luckily, with Spring Boot it only takes a few steps to get
ready for shipment and production. Spring Boot prefers atomic, executable JARs with all dependencies packed into a flat classpath. This means the JAR that we create as part of a call to mvn clean package
is
executable and contains all we need to run our microservice in a Java
environment. To test this out, go to the root of the hello-springboot
microservice project and run the following commands:
$ mvn clean package $ ava -jar target/hello-springboot-1.0.jar
If your project was named demo instead of hello-springboot, then substitute the properly named JAR file (demo-1.0.jar).
That’s it!
We’ll notice this sort of idiom when we explore MicroProfile in the next chapter, too.
In a microservices environment, each service is responsible for providing its functionality or service to other collaborators. As we discussed in the first chapter, building distributed systems is hard, and we cannot abstract away the network or the potential for failures. We will cover how to build resilient interactions with our dependencies in Chapter 5. In this section, however, we will just focus on getting a service to talk to a dependent service.
If we wish to extend the hello-springboot microservice, we will need to create a service that we can call using Spring’s REST client functionality. For this example and the rest of the examples in the report, we’ll use a backend service and modify our service to reach out to the backend to generate the greetings we want to be able to use, as indicated by Figure 2-5.
If you look at the source code for this report, you’ll see a Maven module called backend
that contains a very simple
HTTP servlet that can be invoked with a GET request and query
parameters. The code for this backend is very simple, and it does not use
any of the microservice frameworks (Spring Boot, MicroProfile etc.). We have created a ResponseDTO
object that encapsulates time
,
ip
, and greeting
fields. We also leverage the awesome Jackson
library for JSON data binding, as seen here:
@WebServlet
(
urlPatterns
=
{
"/api/backend"
})
public
class
BackendHttpServlet
extends
HttpServlet
{
@Override
protected
void
doGet
(
HttpServletRequest
req
,
HttpServletResponse
resp
)
throws
ServletException
,
IOException
{
resp
.
setContentType
(
"application/json"
);
ObjectMapper
mapper
=
new
ObjectMapper
();
String
greeting
=
req
.
getParameter
(
"greeting"
);
ResponseDTO
response
=
new
ResponseDTO
();
response
.
setGreeting
(
greeting
+
" from cluster Backend"
);
response
.
setTime
(
System
.
currentTimeMillis
());
response
.
setIp
(
getIp
());
PrintWriter
out
=
resp
.
getWriter
();
mapper
.
writerWithDefaultPrettyPrinter
()
.
writeValue
(
out
,
response
);
}
private
String
getIp
()
{
String
hostname
=
null
;
try
{
hostname
=
InetAddress
.
getLocalHost
().
getHostAddress
();
}
catch
(
UnknownHostException
e
)
{
hostname
=
"unknown"
;
}
return
hostname
;
}
}
To start up the backend service on port 8080
, navigate to the
backend directory and run the following command:
$ mvn wildfly:run
The backend project uses the Maven WildFly plug-in, which allows us to
quickly boot up our app using mvn wildfly:run
.
This service is exposed at /api/backend and takes a query parameter called
greeting
. For example, when we call this service with the path
/api/backend?greeting=Hello, like this (you can also visit this URL with your browser):
$ curl -X GET http://localhost:8080/api/backend?greeting=Hello
Then the backend service will respond with a JSON object something like this:
{
"greeting"
:
"Hello from cluster Backend"
,
"time"
:
1459189860895
,
"ip"
:
"172.20.10.3"
}
Next, we’ll create a new HTTP endpoint, /api/greeting, in our hello-springboot microservice and use Spring to call this backend.
First, we create a class in src/main/java/com/examples/hellospringboot called
GreeterRestController
and fill it in similarly to how we filled in the HelloRestController
class (see Example 2-4).
@RestController
@RequestMapping
(
"/api"
)
@ConfigurationProperties
(
prefix
=
"greeting"
)
public
class
GreeterRestController
{
private
String
saying
;
private
String
backendServiceHost
;
private
int
backendServicePort
;
@RequestMapping
(
value
=
"/greeting"
,
method
=
RequestMethod
.
GET
,
produces
=
"text/plain"
)
public
String
greeting
(){
String
backendServiceUrl
=
String
.
format
(
"http://%s:%d/api/backend?greeting={greeting}"
,
backendServiceHost
,
backendServicePort
);
System
.
out
.
println
(
"Sending to: "
+
backendServiceUrl
);
return
backendServiceUrl
;
}
}
We’ve left out the setters for the properties in this class, but
make sure you have them in your source code! Note that we are using the
@ConfigurationProperties
annotation again to configure the REST controller here, although this time we are
using the greeting
prefix. We also create a GET endpoint, like we did
with the hello
service; and all it returns at the moment is a string
with the values of the backend service host and port concatenated
(these values are injected in via the @ConfigurationProperties
annotation). Let’s add the backendServiceHost
and backendServicePort
to our application.properties file:
greeting.saying=Hello Spring Boot greeting.backendServiceHost=localhost greeting.backendServicePort=8080
Next, we’re going to use Spring’s RestTemplate
to do the invocation of
the remote service. Following a long-lived Spring convention with its
template patterns, the RestTemplate
wraps common HTTP/REST idioms inside a convenient wrapper abstraction which then handles all the
connections and marshalling/unmarshalling the results of an invocation.
RestTemplate
uses the native JDK for HTTP/network access, but you can
swap that out for Apache HttpComponents, OkHttp, Netty, or others.
Example 2-5 shows what the source looks like when using the RestTemplate
(again,
the getters/setters are omitted here, but required). We are communicating with
the backend service by constructing a URL based on the host and port
that have been injected, and we add a GET query parameter called
greeting
. The value we send to the backend service for the greeting
parameter is from the saying
field of the GreeterRestController
object, which gets injected as part of the configuration when we add
the @ConfigurationProperties
annotation.
@RestController
@RequestMapping
(
"/api"
)
@ConfigurationProperties
(
prefix
=
"greeting"
)
public
class
GreeterRestController
{
private
RestTemplate
template
=
new
RestTemplate
();
private
String
saying
;
private
String
backendServiceHost
;
private
int
backendServicePort
;
@RequestMapping
(
value
=
"/greeting"
,
method
=
RequestMethod
.
GET
,
produces
=
"text/plain"
)
public
String
greeting
(){
String
backendServiceUrl
=
String
.
format
(
"http://%s:%d/api/backend?greeting={greeting}"
,
backendServiceHost
,
backendServicePort
);
System
.
out
.
println
(
"Sending to: "
+
backendServiceUrl
);
BackendDTO
response
=
template
.
getForObject
(
backendServiceUrl
,
BackendDTO
.
class
,
saying
);
return
response
.
getGreeting
()
+
" at host: "
+
response
.
getIp
();
}
}
Next, we add the BackendDTO
class, which is used to encapsulate
responses from the backend (Example 2-6).
public
class
BackendDTO
{
private
String
greeting
;
private
long
time
;
private
String
ip
;
public
String
getGreeting
()
{
return
greeting
;
}
public
void
setGreeting
(
String
greeting
)
{
this
.
greeting
=
greeting
;
}
public
long
getTime
()
{
return
time
;
}
public
void
setTime
(
long
time
)
{
this
.
time
=
time
;
}
public
String
getIp
()
{
return
ip
;
}
public
void
setIp
(
String
ip
)
{
this
.
ip
=
ip
;
}
}
Now let’s build the microservice and verify that we can call this new greeting endpoint and that it properly calls the backend. First, start the backend if it’s not already running. Navigate to the backend directory of the source code that comes with this application and run it:
$ mvn clean wildfly:run
Next, we’ll build and run the Spring Boot microservice. Let’s also
configure this service to run on a different port than the default port
(8080
) so that it doesn’t collide with the backend service, which is
already running on port 8080
:
$ mvn clean spring-boot:run -Dserver.port=9090
Later in the report we’ll see how running these microservices in their own Linux container removes the restriction of port swizzling at runtime.
Now, point your browser to http://localhost:9090/api/greeting to see if the microservice properly calls the backend and displays what we’re expecting, as shown in Figure 2-6.
In this chapter, you learned what Spring Boot is and how it’s different from traditional WAR/EAR deployments. You also saw some simple use cases, including exposing an HTTP/REST endpoint, externalizing configuration, exposing metrics, and calling another service. This is just scratching the surface; if you’re interested in learning more about Spring Boot, please take a look at the following references: