Spring Boot is an opinionated Java framework for building microservices based on the Spring dependency injection framework. Spring Boot allows developers to create microservices through reduced boilerplate, configuration, and developer friction. This is a similar approach to the two other frameworks we’ll look at. Spring Boot does this by:
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 technology to do the heavy lifting: JDBC databases, message queues, file systems, application-level caching, etc. A developer would have to stop what she’s 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 or issues that would arise 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:
JPA persistence
NoSQL databases like MongoDB, Cassandra, and Couchbase
Redis caching
Tomcat/Jetty/Undertow servlet engine
JTA transactions
Adding a submodule to your application brings in the 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 WAR). The idiom that most developers who use Spring Boot prefer is
the self-contained JAR packaging for their application. This means
Spring Boot packages all dependencies and application code into a
self-contained JAR with a flat class loader. This makes it easier to understand application startup, dependency ordering, and log statements; but more importantly, it helps reduce the
number of moving pieces required to take an app safely to production.
This means 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, Dropwizard, and WildFly Swarm all follow this pattern of
packaging everything into an executable uber JAR.
But what about management things we typically expect out of an application server?
Spring Boot ships with a module called actuator which enables things like metrics and statistics about your application. For example, we can collect logs, view metrics, perform thread dumps, show environment variables, understand garbage collection, and show what beans are configured in the BeanFactory. You can expose this information via HTTP, JMX, or you can even log in directly to the process via SSH.
With Spring Boot, we 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 visiting the web version of Spring Initializr. 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 v1.3.3.RELEASE
If you can see a version for your installation of the CLI, congrats! Now navigate to a directory where you want to host your examples from the book and run the following command:
spring init --build maven --groupId com.redhat.examples--version 1.0 --java-version 1.8 --dependencies web
--name hola-springboot hola-springboot
After running this command, you should have a directory named hola-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
or
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; will be used in later iterations, so set to 1.0.
--java-version
Allows us to specify 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 if you navigate to the hola-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:
2016-03-25 10:57:08.920[
main]
AnnotationMBeanExporter : Registering beansfor
JMX exposure on startup 2016-03-25 10:57:08.982[
main]
TomcatEmbeddedServletContainer : Tomcat started on port(
s)
:8080
(
http)
2016-03-25 10:57:08.987[
main]
HolaSpringbootApplication : Started HolaSpringbootApplication in 1.0 seconds(
JVM runningfor
4.7)
Congrats! You have quickly gotten a Spring Boot application up and running! You can even navigate to http://localhost:8080 in your browser and should see the following output:
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/hola that will return “Hola Spring Boot from X” where X is the IP
address where the service is running. To do this, navigate to
src/main/java/com/example. This location should have been created for
you if you followed the preceding steps; remember, the groupId
we
passed to the spring init
program did not apply groupId to the Java
package hierarchy, and we’ve left it as it is which should be
“com.example”. Then create a new Java class called HolaRestController
, as shown in Example 2-1. We’ll add a method named hola()
that returns a string along with the IP address of where the service is running. You’ll see in Chapter 5, in our load balancing and service discovery sections, how the host IPs can be used to demonstrate proper failover, loadbalancing, etc.
public
class
HolaRestController
{
public
String
hola
()
throws
UnknownHostException
{
String
hostname
=
null
;
try
{
hostname
=
InetAddress
.
getLocalHost
()
.
getHostAddress
();
}
catch
(
UnknownHostException
e
)
{
hostname
=
"unknown"
;
}
return
"Hola Spring Boot de "
+
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
Tell Spring this is an HTTP controller capable of exposing HTTP endpoints (GET, PUT, POST, etc.).
@RequestMapping
Map specific parts of the HTTP URI path to classes, methods, and parameters in the Java code.
Note, import statements are omitted.
@RestController
@RequestMapping
(
"/api"
)
public
class
HolaRestController
{
@RequestMapping
(
method
=
RequestMethod
.
GET
,
value
=
"/hola"
,
produces
=
"text/plain"
)
public
String
hola
()
throws
UnknownHostException
{
String
hostname
=
null
;
try
{
hostname
=
InetAddress
.
getLocalHost
()
.
getHostAddress
();
}
catch
(
UnknownHostException
e
)
{
hostname
=
"unknown"
;
}
return
"Hola Spring Boot de "
+
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 = "/hola", produces = "text/plain")
, we are telling Spring to expose an HTTP GET endpoint at /hola
(which will really be /api/hola) and map requests with 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 we build our application and run spring-boot:run
again, we should
be able to reach our HTTP endpoint:
$
mvn clean package spring-boot:run
Now if we point our browser to http://localhost:8080/api/hola, we should see a response similar to:
What if we want to add some environment-aware configuration to our application? For example, instead of saying “Hola,” maybe we want to say “Guten Tag” if we deploy our app in production for German users? We need a way to inject properties to 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 I want to bind all
helloapp.*
properties to my HolaRestController
, I can add
@ConfigurationProperties(prefix="helloapp")
, and Spring Boot will
automatically try to bind helloapp.foo
and helloapp.bar
to Java Bean
properties in the HolaRestController
class. Let’s define a new
property in src/main/resources/application.properties called
helloapp.saying
. The application.properties file was automatically
created for us when we created our project. Note we could change the
file name to application.yml and Spring would still recognize it as a
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
In the HolaRestController
in Example 2-3, let’s add the @ConfigurationProperties
annotation and our new saying
field. Note we also need setters and getters.
@RestController
@RequestMapping
(
"/api"
)
@ConfigurationProperties
(
prefix
=
"helloapp"
)
public
class
HolaRestController
{
private
String
saying
;
@RequestMapping
(
method
=
RequestMethod
.
GET
,
value
=
"/hola"
,
produces
=
"text/plain"
)
public
String
hola
()
throws
UnknownHostException
{
String
hostname
=
null
;
try
{
hostname
=
InetAddress
.
getLocalHost
()
.
getHostAddress
();
}
catch
(
UnknownHostException
e
)
{
hostname
=
"unknown"
;
}
return
saying
+
" "
+
hostname
;
}
public
String
getSaying
()
{
return
saying
;
}
public
void
setSaying
(
String
saying
)
{
this
.
saying
=
saying
;
}
}
Let’s stop our application from running before (if we haven’t) and restart it:
$
mvn clean package spring-boot:run
Now if we navigate to http://localhost:8080/api/hola, we should see the German version of the saying:
We can now externalize properties that would 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 in which it runs! 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. Spring Boot comes with a prepackaged starter called 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 hola-springboot
microservice and add the following Maven
dependency within the <dependencies>...</dependencies>
section:
<dependency>
<groupId>
org.springframework.boot</groupId>
<artifactId>
spring-boot-starter-actuator</artifactId>
</dependency>
Now restart your microservice by stopping it and running:
$
mvn clean package spring-boot:run
Just by adding the actuator dependency, our application now has a lot of information exposed that would be very handy for debugging or general microservice insight. Try hitting the following URLs and examine what gets returned:
Here’s an example of what the http://localhost:8080/env endpoint looks like:
Exposing runtime insight like this relieves the developer to just focus on writing code for the microservice that delivers business value. Delegating to frameworks to do heavy lifting and boilerplate is definitely a good idea.
Up to this point we’ve been thinking through 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 us
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 our hola-springboot
microservice project and run the following commands:
$
mvn clean package$
java -jar target/hola-springboot-1.0.jar
If your project was named demo
instead of hola-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 Dropwizard and WildFly Swarm.
In a microservice environment, each service is responsible for providing the functionality or service to other collaborators. As we’ve 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-world microservice, we will need to create a service to which we can call using Spring’s REST client functionality. For this example and the rest of the examples, 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.
If you look in the source code for this book, we’ll see a Maven module called backend
which 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 does not use
any of the microservice frameworks (Spring Boot, Dropwizard, or WildFly
Swarm). 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:
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:
$
mvn clean install jetty:run
The backend
project uses the Maven Jetty plug-in, which allows us to
quickly boot up our app using mvn jetty:run
.
This service is exposed at /api/backend and takes a query parameter
greeting
. For example, when we call this service with this path
/api/backend?greeting=Hello, then the backend service will respond with a JSON object like this (can also visit this URL with your browser):
$
curl -X GET http://localhost:8080/api/backend?greeting=
Hello
We get something like this:
{
"greeting"
:
"Hello from cluster Backend"
,
"time"
:
1459189860895
,
"ip"
:
"172.20.10.3"
}
We will create a new HTTP endpoint, /api/greeting, in our Spring Boot
hola-springboot
example and use Spring to call this backend!
Create a new class in src/main/java/com/example called
GreeterRestController
and fill it in similarly to how we filled it in for the HolaRestController
(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/hello?greeting={greeting}"
,
backendServiceHost
,
backendServicePort
);
System
.
out
.
println
(
"Sending to: "
+
backendServiceUrl
);
return
backendServiceUrl
;
}
}
I’ve left out the getters/setters for the properties in this class, but
make sure to have them in your source code! Note we are using the
@ConfigureProperties
annotation again to configure a block of
configuration for our REST controller here, although this time we are
using the greeting
prefix. We also create a GET endpoint like we did
with the hola
service, and all it returns at the moment is a string
with the values of the backend service host
and port
concatenated
(and these values are injected in via the @ConfigureProperties
annotation). Let’s add the backendServiceHost
and backendServicePort
to our application.properties file:
greeting.saying=Hola 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 of this 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.
Here’s what the source looks like when using the RestTemplate
(again,
the getters/setters omitted, 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 added
the @ConfigurationProperties
annotation (Example 2-5).
@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
);
BackendDTO
response
=
template
.
getForObject
(
backendServiceUrl
,
BackendDTO
.
class
,
saying
);
return
response
.
getGreeting
()
+
" at host: "
+
response
.
getIp
();
}
}
Let’s 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, let’s
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 install jetty:run
Next let’s build and run our Spring Boot microservice. Let’s also
configure this service to run on a different port than it’s default port
(8080
) so that it doesn’t collide with the backend service which is
already running on port 8080
.
$
mvn clean install spring-boot:run -Dserver.port=
9090
Later on in the book we can see how running these microservices in their own Linux container removes the restriction of port swizzling at runtime. Now, let’s navigate our browser to http://localhost:9090/api/greeting to see if our microservice properly calls the backend and displays what we’re expecting:
In this chapter, we learned what Spring Boot was, how it’s different from traditional WAR/EAR deployments, and some simple use cases, including exposing an HTTP/REST endpoint, externalizing configuration, metrics, and how to call another service. This is just scratching the surface, and if you’re interested in learning more about Spring Boot, please take a look at the following links and book:
18.116.118.229