This is the public sample for the pocket guide Spring Boot: How To Get Started and Build a Microservice.
I hesitated first to learn Spring Boot instead of Spring standalone. Glad I did anyway. Learned a good and easy way to develop an application with rest services and it even covered deployment. Now, I’m having other choices and know how to run a Spring app from scratch. Clearly, recommend it for beginners as it was easy to learn.
– Benoit - reader of the extended version with awesome bonuses
I love this book. Just enough info to quickly get to the heart of the matter. I was new to Spring Boot and this book helped give me perspective. Thank you for writing it.
– Larry - reader of the first edition
I bought this book because i wanted to learn a simple way of creating API endpoints AND securing them and I was happy to see that this book delivered. Most of the material I’ve read on this before was either too complex, outdated or simply skipped the security part entirely…
– Nelson - reader of the first edition
Learning to build applications and especially APIs with Spring and Spring Boot is exciting. At the same time, it is freighting when you stay at the bottom of Mt. Spring.
Spring Boot makes it much easier than it was with Spring alone in the past. However, it also hides much of the complexity involved in its magic, which brings in new complexity and might leave you confused sometimes.
Be assured that this is totally normal and everyone went through this phase. When you hike up, it gets easier as you will gain a solid understanding and control of the complexity involved and you can build frictionless applications with it.
I know I felt this way when I first started out with Spring, and there was no Spring Boot at that time. But, that’s temporary, and it gets much better over time. When I walked up Mt. Spring a bit, it already got easier. And it wasn’t just me, I noticed the same with friends and co-workers.
In the book, I reduce the complexity and focus on small, manageable parts, so you get productive quickly. It is the same approach I use successfully in my workshops or when helping co-workers in-person. Unlike in-person interaction, I’m not with you to see you when you progress through the book. Therefore, I can’t notice when you have a question so I can rephrase and explain it differently. Nonetheless, you are not alone, you can ask me, and I will help you get unstuck. Promise– just email me.
This book’s approach differs from that of other programming books that you may have encountered. I believe that the best way to learn a new framework or language is to build applications using it. I also assume that you have some experience with Java, Maven, and the IDE of your choice.
The application for the book is modeled after a real production application serving thousands of requests and running smoothly since its first deployment. The sample application follows the same implementation approach but leaves out certain complexities which are not needed for learning to build a microservice with Spring Boot.
The original application stores binary files and certain metadata, like product references, width, and height of images, file size, for covers and other marketing materials of books. The application is integrated into a large infrastructure and offers all of its services GUI-less. However, for the sample application, we will do something simpler for learning and are going to develop a commenting system.
What will the application do:
What we will build in this book:
We will test our microservice, make it production-ready with Spring Boots’ features, and finally deploy it as a standalone microservice.
The full source code of this book’s sample application is available on GitHub: Link
The project uses a Multi Module Maven layout. Build and run instructions are in the repository.
I also added all external reference to a private resource page for your convenience.
If you have any questions, do not hesitate to contact me at the email address found in the last chapter.
Installing and setting them up is not in the scope of this book.
You can use Gradle instead of Maven, but the book and the examples assume you are using Maven. However, I assume you are capable of converting it to Gradle yourself.
If you are new to Maven, check out the brief introduction to Maven in Appendix A. It will assist you in following along.
In the first chapter, we will take a closer look at the Spring Framework and what kind of problems it solves in the first place. We then examine how the core of it works and how to use it. We will focus on the basics here and ignore the numerous features it offers additionally; some of them will be covered further in the book.
If you already know what the Spring Core does and how it works, you can skip to the next chapter and directly start with Spring Boot.
We are using Maven as a build and dependency management tool in the book. If you are new to Maven, check out the brief introduction to Maven in Appendix A; it covers the basic to get started. And if you want to use Gradle, go ahead and use it, I assume you are capable of converting the dependencies to Gradle yourself.
The Spring Framework provides an environment for developing Java applications so you can focus on developing your application logic and not waste time with reinventing standard functionality and inter-component connectivity in each project. It is also built upon the concept of Inversion of Control (IoC). IoC means that service calls are not hardwired but rather run through the framework, i.e.- service A uses service B, but does not know how B is instantiated - the IoC container does it.
This concept gives us some benefits like:
IoC can be implemented in various patterns like Factory, service locators, or with Dependency Injection (DI). Spring uses the latter, DI.
DI frees your services from knowing how to instantiate and connect other services (yours or foreign). It is handled by Spring.
It is best understood by seeing it in action. So let’s step back for now and start with a simple example of service coupling in a traditional way. For that, we create a simple spam checking command line application.
We will use the service later, so let’s create a new Maven project by creating a pom.xml like:
<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
http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<groupId>de.codeboje.springbootbook</groupId>
<artifactId>spam-detection</artifactId>
<version>1.0.0-SNAPSHOT</version>
<!-- Project metadata -->
<name>Spring Boot Book - Spam Detection</name>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Next, we create a simple SimpleSpamDetector, which checks a given string if it contains one of our predefined spam words.
public class SimpleSpamDetector {
private List<String> spamWords
= new ArrayList<String>();
public SimpleSpamDetector(
List<String> spamWords
) {
this.spamWords = spamWords;
}
public boolean containsSpam(
String value
) {
for (String spam : spamWords) {
if (value.toLowerCase().contains(spam)) {
return true;
}
}
return false;
}
}
Now, let’s create an application for it. The application takes the first command line argument and checks it for spam. In the early version, we will hard code the spam words like:
public class SpamCheckerApplication {
public static void main(String[] args) {
List<String> spamWords
= new ArrayList<String>();
spamWords.add("viagra");
SimpleSpamDetector spamDetector
= new SimpleSpamDetector(spamWords);
System.out.println(
spamDetector.containsSpam(args[0])
);
}
}
For simplicity, we print the result to the console.
The next logical step would be to put the spam words in a file, so let’s do it in the main method. We also retrieve the filename as a second parameter:
public static void main(String[] args)
throws Exception
{
List<String> spamWords
= new ArrayList<String>();
if (args.length == 2) {
spamWords = Files.readAllLines(
Paths.get(args[1])
);
}
SimpleSpamDetector spamDetector
= new SimpleSpamDetector(spamWords);
System.out.println(
spamDetector.containsSpam(args[0])
);
}
If we do not pass the filename as the second argument, we run with an empty spam word list. Now we will create a file for the spam words and put a few each on a single line. Run it and verify it is working before we continue.
The main method contains our control flow and the initialization of our application and its components. Usually, you would separate them, so it is a bit more maintainable. However, in this approach either our service, i.e.- SimpleSpamDetector would be passed into the control flow or its dependencies, the spam words and filename must be passed along so the control flow or any other services using the SimpleSpamDetector can create its own instance.
In the first variation, we will have a huge center part for setting up and wiring components. With the second variation, we reduce the center lump, but scatter the setup around the components.
This works in a simple example like this, but now imagine the checker is much more complex, like reading from multiple input sources, using different spam detectors and logic what to do when it is spam. When your application grows, and you keep it like above, it will eventually become a maintenance nightmare. However, there are solutions for this, even without Spring, and we will examine one in the next section.
In this section, we are going to add a second spam detector and introduce a better setup using the Factory pattern.
The Factory pattern is a standard software development pattern and helps in creating objects without knowing the exact implementation which will be created and how it is created.
When working with factories and multiple implementations of a service, you will create a common interface, which each of the implementation must extend.
So, let’s extract it from the SimpleSpamDetector. It will look like:
public interface SpamDetector {
boolean containsSpam(String value);
}
Next, the SimpleSpamDetector must extend it by:
public class SimpleSpamDetector
implements SpamDetector {
// rest omitted
}
Now, we create the factory and move the initialization of the SimpleSpamDetector to it:
public class SpamDetectorFactory {
public static SpamDetector getInstance(
String[] args
) throws IOException {
List<String> spamWords
= new ArrayList<String>();
if (args.length == 2) {
spamWords = Files.readAllLines(
Paths.get(args[1])
);
}
return new SimpleSpamDetector(
spamWords
);
}
}
By using factories, you have multiple options of how to get parameters. The usual ways are:
They do work, and it depends on your context which way to choose. However, we won’t go deeper into these as it is not necessary to understand Spring. Nonetheless, I can tell you it ‘s not a good idea to mix them; I once worked on a project where all three ways were actively used in a single application. It was no fun and time consuming to find out where to configure something.
For this example, we keep it simple and just use the arguments from the main method and pass them along. It acts as a global config.
In our SpamCheckerApplication we will use the Factory now like:
public static void main(String[] args)
throws Exception {
SpamDetector spamDetector
= SpamDetectorFactory.getInstance(args);
System.out.println(
spamDetector.containsSpam(args[0])
);
}
In the next step, we are adding a second spam detector and assume it will do a remote check. We are not actually going to implement the check, just the base construct for showing a glimpse of the rising complexity.
public class RemoteSpamDetector
implements SpamDetector {
public RemoteSpamDetector(
String url,
String username,
String password
) {
// omitted, not needed for explanation
}
public boolean containsSpam(String value) {
// make the remote call
return false;
}
}
The dummy RemoteSpamDetector needs three parameters to work, a URL, a username, and a password. We will create it in the SpamDetectorFactory as well and define, if the application retrieves more than two arguments, we should do a remote check and use the additional arguments for the RemoteSpamDetector.
public static SpamDetector getInstance(
String[] args
) throws IOException {
if (args.length <= 2) {
List<String> spamWords
= new ArrayList<String>();
spamWords = Files.readAllLines(
Paths.get(args[1])
);
return new SimpleSpamDetector(
spamWords
);
}
return new RemoteSpamDetector(
args[1],
args[2],
args[3]
);
}
Remember, it looks different in a real world application following this pattern. You wouldn’t rely on command line args like that. Anyway, it is enough to grasp the concept and see where we are heading to.
Imagine now, how this will end if you add more services and implementations to our application. You will have a bunch of factories and config files. If you move the initialization to the application starter now, you will be basically on your first step towards IoC. However, your services still depend on the factories and are thus still coupled to the initialization of the other services.
Of course, you can implement it in a cleaner and maintainable way. However, the main disadvantage is that you have to start all over again for your next application.
This is where Spring Core with its Dependency Injection rescues us. It takes over our application initialization, gets rid of the factories, and provides a runtime for loading and coupling our services. And we can use it over and over again without reinventing it each time.
In the next section, we migrate our application to Spring.
The first thing we do in a new application is to give control of creating our services to the Spring Container. The Container initializes the services and also injects dependencies, i.e.- our SimpleSpamDetector into the control flow.
A service just declares that it needs a service that implements the SpamDetector interface and the Spring Container provides it. The only requirement now is that everything must be supervised and controlled by Spring.
In the center of Spring is the Spring Container also referred to as context or ApplicationContext (the interface). It knows every registered class and how they connect to each other.
A class made available in the context is called a bean. Beans are usually plain old Java objects (POJO) and often implement certain interfaces. In the context, each bean is identified by its id (aka name) and its type (the class). The identifier is unique across the context, and if you provide it multiple times, a previously defined one will be replaced by the new one. By default, all beans in the context are handled as singletons. You can change that, but we do not cover it in the book (search for scope and prototype).
Before we switch to Spring now, let’s first move the control flow from the main method into its own class like:
public class ControlFlow {
public void run(String[] args)
throws IOException {
SpamDetector spamDetector
= SpamDetectorFactory.getInstance(args);
System.out.println(
spamDetector.containsSpam(args[0])
);
}
}
And the main application is reduced to:
public class SpamCheckerApplication {
public static void main(String[] args)
throws Exception {
new ControlFlow().run(args);
}
}
Now, we are ready to use Spring.
First, we add the dependency to our project:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.10.RELEASE</version>
</dependency>
Next, we create the context.
public class SpamCheckerApplication {
public static void main(String[] args)
throws Exception {
ApplicationContext context
= new AnnotationConfigApplicationContext(
SpamAppConfig.class
);
context.getBean(
ControlFlow.class
).run(args);
}
}
The context is created by the line:
ApplicationContext context
= new AnnotationConfigApplicationContext(
SpamAppConfig.class
);
AnnotationConfigApplicationContext will start the Spring Container and load the config from a class named SpamAppConfig. SpamAppConfig is a regular POJO with a particular annotation on it, so Spring accepts it as a configuration and populates the context. More on that in a minute.
The ApplicationContext comes in two styles. The first one is by defining all our beans in an XML document following various Spring XML schema and the second one is to use the Java based configuration with annotations. We will use the latter as it is the norm now and will spare you some brain damage by not using XML.
To mark our SpamAppConfig as a configuration source, we annotate it with @Configuration. In it, we define our beans:
@Configuration
public class SpamAppConfig {
@Bean
public SpamDetector simpleSpamDetector(
@Value("${sbb.spamwords.filename}")
String filename
) throws IOException {
List<String> spamWords
= new ArrayList<String>();
spamWords = Files.readAllLines(
Paths.get(filename)
);
return new SimpleSpamDetector(
spamWords
);
}
@Bean
public ControlFlow controlFlow(
SpamDetector spamDetector
) {
return new ControlFlow(
spamDetector
);
}
}
In a @Configuration, we provide the beans by adding methods creating an instance and annotate these with the @Bean annotation.
Those methods can have parameters, and Spring tries to resolve these by other provided beans in the context. In this case, it will expect to have one and exact one bean in the context. It is a shortcut for the @Autowired annotation; more on that soon. Spring will load the beans in order to resolve dependencies. It will use the method name as the bean identifier in the context, except we declare a different one with the @Bean annotation like:
@Bean("myBean")
Or:
@Bean(name="myBean")
For referencing the filename, we use the @Value annotation. Here you can define an expression in the Spring Expression Language (SpEL) how to resolve the value. In the case above, we look up the value in a property source by the key sbb.spamwords.filename. This property source is populated by provided property files and the system environment. For this example, you can provide it at runtime with a JVM parameter like:
-Dsbb.spamwords.filename=PATH-TO words.spam
We also need to reflect the changes in the ControlFlow class, so create the constructor accordingly to our usage in SpamAppConfig.controlFlow. Also, we can remove the SpamDetectorFactory inside and use the spamDetector received by the constructor. At the end ControlFlow should look like:
public class ControlFlow {
private SpamDetector spamDetector = null;
public ControlFlow(SpamDetector spamDetector) {
super();
this.spamDetector = spamDetector;
}
public void run(String[] args) throws IOException {
System.out.println(spamDetector.containsSpam(args[0]));
}
}
Now, we have populated the context and can use our ControlFlow in the main application by:
context.getBean(ControlFlow.class).run(args);
getBean will look up a bean in the context of type ControlFlow and return it. It expects to find exactly one match, as in our case now and throws exceptions if not.
On the return ControlFlow, we just call our run method as before.
As you see, with just a few annotations we replaced the factory with a solution we can reuse. Plus, it is more flexible in adding common features like transaction handling. As everything is connected by Spring, it can plug in particular features during runtime. It is possible because Spring creates a proxy for our class. This proxy enables Spring to add features without the need that we must change our classes. It is transparent for us.
However, the proxy has its limits. When a class calls a method on itself, it is not going through the proxy. It only goes through the proxy, when one service calls another service. Of course, both must be maintained by Spring.
Most of the time you will not notice this limit, but if you ever do, you can overcome it by using AspectJ during compile time. Just be aware, this way has its own pros and cons.
The usual way to enable a feature is to set up the module like Spring Data, and then you can use it through various annotations. And with Spring Boot it will become even easier to enable a new feature.
In the next section we are making the configuration even smaller and introducing common annotation you will encounter all the time. But before we continue, let’s recap the used annotations so far:
In this section, we introduce a feature called component scan. It will scan the classpath for classes annotated with a particular set of annotations and add those beans to the context. You enable the feature, by adding the @ComponentScan annotation on your @Configuration class, in our case SpamAppConfig.
@Configuration
@ComponentScan
public class SpamAppConfig {
}
As you see, we already removed the explicit bean declarations here because we are providing them now with the component scan.
The component scan will search for classes annotated with @Component or one of its children. It is set on a @Configuration and by default will use the package of the config class as a starting point for the scan. All classes in this package, or any sub-packages are scanned.
Let’s provide SimpleSpamDetector to the context again by adding @Service. In the same trip, we move the file loading to its constructor and inject the filename like before, here in the constructor:
@Service
public class SimpleSpamDetector
implements SpamDetector {
private List<String> spamWords
= new ArrayList<String>();
public SimpleSpamDetector(
@Value("${sbb.spamwords.filename}")
String filename
) throws IOException {
spamWords = Files.readAllLines(
Paths.get(filename)
);
}
//rest omitted
}
We do the same on the ControlFlow, but use @Component instead, and can rerun the application:
@Component
public class ControlFlow {
//rest omitted
}
As an exercise, you can do the same with the RemoteSpamDetector and finally include it in the context. On the first run, Spring will now complain that you have more than one class of type SpamDetector in the context and won’t start.
In these situations, you need to specify which of the beans you want. Add the @Qualifier annotation to the spamDetector parameter in the ControlFlow constructor. @Qualifier accepts a name of the bean. Spring will now check for a bean with this name and type. When you use the @Component annotation, Spring uses the name of the class with the first letter in lowercase as the bean name. Of course, you can also declare your own in an annotation parameter (same as with @Bean before):
public ControlFlow(
@Qualifier("simpleSpamDetector")
SpamDetector spamDetector
) {
super();
this.spamDetector = spamDetector;
}
In this variant, we can not choose the spam detector implementation during runtime. However, it is possible, but strays away too far from the focus of the book. Nonetheless, I don’t want you to leave empty handed, so one solution could be to use the ApplicationContext in your bean. It can be injected like any other bean.
At this point, you have learned the essentials of Spring. Everything Spring does is based on this foundation. When you understood these, the Spring Universe does not look that overwhelming anymore, and you can tackle the rest successfully.
Take your time and play a bit with it to deepen your understanding.
In the next chapter we will start with Spring Boot, what it is and then we are going to develop your first microservice with it.
Before we continue, let’s check your understanding with a short quiz.
In short, Spring Boot is a new Framework with an opinionated view of building production-ready applications using the standard Spring Framework. Building applications with the Spring Framework used to be a tedious task; especially when starting a new project. Spring consists of multiple modules which you can use individually or integrate each other. The downside is that you always had to do it manually. Also, in many cases, modules were not shipped with default configurations of out the box - they assume you connect them however you like. So, you had to connect everything by yourself too, be it in XML, or with Java annotations. That changed with Spring Boot.
Spring Boot is a way to start new applications and use the world of Spring modules with defaults which make sense.
Its Features:
The easiest way to start a new project is by using the Spring Initializer.
The second way is to create a Maven pom.xml file. The pom.xml (referenced from now on as pom) is the instruction file that will be used to build your project. If you are new to Maven, check out the brief introduction to Maven in Appendix A.
Open your text editor and add the following:
<?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
http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion>
<groupId>de.codeboje.springbootbook</groupId>
<artifactId>comment-store</artifactId>
<version>1.0.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
</project>
END OF SAMPLE
Get everything you need to start to learn how to build a microservice with Spring Boot.
DELUXE EDITION:
Let’s assume you took the video route. It’s a 99 bucks video course which takes on average 10 hours of your time watching it. If your hour is worth 50$ then you actually pay 599$ and haven’t even build your microservice yet. With my guide instead, you will probably be finished in under 5 hours and already have built the microservice at least once. You can do the math.
You literally save hours of your life while still learning to build with Spring Boot the easy way.
Invest in yourself now for just $17
Want to save 12%? Get this guide as part of the Spring Boot Pocket Guides Complete Bundle.
Don’t put off getting started with Spring Boot. If you are not satisfied with the guide, email me within 14 days of your purchase, and I give you a full refund. I don’t ask questions; however, it would be great if you give me quality feedback so I can improve on it.