The purpose of this training is to get you familiar with the basic functions of Spring as well as to show you why they are useful.
In order to help you see why Spring is useful, the first part of the training focuses on some important design patterns instead of diving straight into Spring.
This training assumes you are able to write code in Java.
1. How does this training work?
The training organization is as following:
-
First a bit of theory explaining the concepts, principles, practices and conventions. Then a lab to bring the theory to practise
-
Each lab is self-contained, but continues on the knowledge you gained in previous labs
-
Labs contain additional practical information which you need to be able to do the lab exercises
-
Labs often contain references to resources. These resources provide you with more in-depth information on how things work, and you can most probably not do the exercises without reading through the resources. So, use the resources!
-
Each lab explains where you can find the files for the lab. All lab files are structured inside the
labs
directory -
For each lab where you need to create/adjust files, there is a sample solution available. You can find the solutions in the
solutions
directory.
The solutions are provided to give you an idea how
you could have implement the exercise. It’s good to look at them, after you implemented the exercise yourself. Peeking at the solution before you finished the exercise is not recommended. Learning is in the failing until you succeed |
2. Introduction
During this training we’ll write some bad code as well as some good code and we will see how Spring helps us to write good and maintainable code.
Labs often contain references to resources. These resources provide you with more in-depth information on how things work, and you can most probably not do the exercises without reading through the resources. So, use the resources!
2.1. Download the zip
2.2. Lab 1: What is unmaintainable code?
This lab is meant to help you understand some issues that can arise when software is designed in an unmaintainable way.
We’re going to add more bad code to already bad code. Are you ready? Good, let’s do this!
In labs/01-unmaintainable-code
you can find the first lab.
It contains a simple coffee machine application,
written in Java.
2.2.1. Exercise 1
Your team has been asked to upgrade the CoffeeMachine’s dispensers to V2, while keeping V1 operational. The previous engineer working on this upgrade was very careful and applied Test Driven Development, so you’ll find a failing unit test. Your goal is to create XxxDispenserV2 classes and use them in the brewV2 method in such a way all unit tests pass.
2.2.2. Exercise 2
A year has passed since you upgraded to V2 (yes, I know, time flies right?) and Dispenser Version 3 is now ready to implement. The previous engineer was extremely responsible again and prepared a test for you. Please use the same design method as in exercise 1 in order to upgrade to V3 while keeping V1 and V2 operational in such a way all unit tests pass.
3. Maintainable Code
So.. the last lab was fun right? Not really you say? Why? Oh it was hard to read the code because IntelliJ kept underlining it while complaining about "Code Duplication"? Who came up with that annoying warning?
In the last lab it was of course a very annoying task to upgrade, you had to copy paste a lot of code. Copy pasting is always a sign of bad code and should be avoided as it can lead to bugs. Say we want all CoffeeDispensers to be more excited and say they are "Dispensing!!!" instead of just merely "Dispensing", we would now have to change the code in three places and might easily forget one.
In fact we shouldn’t have copy pasted at all, we should have redesigned our code, because the initial design was not done with an upgrade of the dispenser in mind.
3.1. Dependency Injection, Inversion of Control and Polymorphism
What was the main thing that was wrong with the design of the CoffeeMachine class? It was made dependent on a specific implementation of CoffeeDispenser, MilkDispenser and SugarDispenser. This made upgrading the dispensers impossible to do without code duplication.
Now let’s explain those scary terms in the title of this section:
If we want to make the coffee machine more independent we could pass the Dispensers as constructor parameters instead of initializing the new Dispenser objects in the constructor. This is called Dependency Injection, simple right?
The user of the CoffeeMachine class now controls what kind of Dispensers it will give to the CoffeeMachine constructor. This is called Inversion of Control, simple right?
For instance, we could now make CoffeeDispenserV2 extend the CoffeeDispenser class and override the dispense method. If we then initialize the CoffeeMachine class with a CoffeeDispenserV2, the CoffeeMachine won’t know that it has been fooled by our masterous trickery and will execute our overridden version of the dispense method. This is called Polymorphism.
However, if you just want to override methods it is usually cleaner to make an interface instead of extending a class. The reason for this is if for instance we add a new method to the CoffeeDispenser class, we could easily forget to override this new method in the CoffeeDispenserV2 class and thereby possibly introduce a bug.
For a class which implements an interface it is guaranteed that the methods which are defined in the interface are all implemented. If we create a CoffeeDispenser interface with a dispense method, then each class which implements the CoffeeDispenser interface has to give its own implementation of the dispense method. If we extend the CoffeeDispenser interface with a new method, then we also need to make an implementation of this method in each class which implements the CoffeeDispenser interface.
3.2. Lab 2: Dependency Injection
In this lab we will redesign our coffee machine such that it is more easy to maintain.
In labs/02-dependency-injection
you can find the lab.
3.2.1. Exercise 3
In this exercise you will redesign the coffee machine such that the CoffeeMachine class does not depend on specific Dispenser implementations anymore.
The ever so diligent teammate of yours has already given you a head start with the redesign. Please continue the implementation of the coffee machine in such a way that all the tests pass.
3.2.2. Exercise 4
In the last exercise it must have been satisfying to see the CoffeeMachine class become so much cleaner, however you might have noticed that we still have a lot of code duplication over the different kind of Dispensers. What can we do about that?
If we want to create a universal Dispenser interface, we also need a universal ingredient class that we can pass to its dispense function. Let’s use class extension for that.
Create an Ingredient abstract class and use it to create a universal Dispenser interface. Don’t fail your teammate, make his tests pass =)
If you really want to make it pretty, you could also refactor the Recipe by making an IngredientPercentage class. Look at the "extra-pretty" solution in the solutions folder to see what I mean. |
4. The Spring IoC Container
Finally! A section that seems to be about Spring! You’re right! Now that we have learned about Dependency Injection and Inversion of Control we’re finally ready to learn the first things about Spring.
The main disadvantage of Inversion of Control is that the user of a class has to have the dependencies for that class or create them. If I want to create an object A which needs objects B, I need to make a B object before I can make an A object.
In our case we need to make a Dispenser object before we can make a CoffeeMachine object. Let’s say the CoffeeMachine would also require a DispenserCleaner object. Now we need to suddenly make two objects before we can make a CoffeeMachine. And what if this DispenserCleaner would require a SoapContainer and Brush class. Now we need to make 4 objects before we can make a CoffeeMachine. As you see this can get quite out of hand easily.
Especially in large applications we might find that the same objects are created multiple times in different places. This is inefficient from with a performance perspective as well as from a maintainability perspective.
-
Performance:
-
Creating new dependencies is less efficient than reusing dependencies that were created by someone else
-
-
Maintainability:
-
If you want to upgrade all code to a new version of Dispenser you still have to change code in multiple places
-
So, it would be nice to have a way to reuse your objects. This is where frameworks like Spring become interesting. Spring makes it really easy to manage your dependencies on an application level.
Spring has a thing called the Inversion of Control Container. The IoC container contains Java objects that are called Beans. A Spring Bean is basically an automatically instantiated class. You can use it anywhere in your application, Spring will make sure that the object is instantiated and hand it to you.
You can access Beans in the Inversion of Control Container via a thing called the ApplicationContext. The ApplicationContext is the place where you configure your Beans, you say how you want Spring to initialize the classes, you can give them a nice name and many more things.
4.1. Lab 3: The Spring Application Context
In this lab we will introduce Spring in our coffee machine and we’ll define and retrieve our first Beans.
In labs/03-spring-application-context
you can find the lab.
4.1.1. Exercise 5
Add Spring version 4.2.0.RELEASE to the coffee machine project.
Create an application context xml file and define a coffee machine bean in order to make the the test in the SpringBeansTest class pass.
Did the test pass? Cool!
Now take a look at the test class, coffeeMachine and coffeeMachine2 are created in a different way.
Please explain to your neighbor what the difference is.
And why does the following line in the test pass?
assertEquals(coffeeMachine, coffeeMachine2);
Now use setter injection for your CoffeeMachine bean and make the same test pass.
4.1.2. Exercise 6
Last year after the release of the Chocolate Milk Machine our company received numerous law suits from lactose intolerant people because of traces of real milk in the lactose free chocolate milk. Management is afraid it might receive these lawsuits for the Coffee Machine as well and therefore told us to make sure we wouldn’t have the same problem.
We decided we needed to make a second coffee machine Bean.
The idea is that this Bean will only be used to brew coffee recipes that don’t contain milk.
Create the second coffee Machine bean and make the test pass.
Then read the comment in the first test.
4.1.3. Exercise 7
The coffee machine was brought out last month and we still feel very proud of our achievements. However despite our best efforts, a lactose intolerant journalist released an article which went viral today. In the article he’s complaining about how he missed his once in a lifetime opportunity to interview the president because he drank a "black" coffee from our machine.
The reason for this is that we are reusing the same Dispenser object for every CoffeeMachine object. So even though we use a lactoseFreeCoffeeMachine object, internally it uses the exact same Dispenser object as our normal coffeeMachine. Hence the coffees produced by the lactose free CoffeeMachine will still contain traces of milk.
Management was furious to hear about our performance and maintainability optimizations. "Insanitary" is what they said about our beautiful design. They demanded we use a fresh Dispenser object for our lactose free CoffeeMachine Bean as well as for all future CoffeeMachine Beans we might add.
You have been challenged with the task of making an emergency fix for the coffee machine. Your teammate already made a unit test to test for unique Dispenser objects. Somewhat reluctant to smudge our beautiful maintainable design, you are looking for the easiest way to satisfy the management’s requirements.
4.1.4. Resources
5. Java Annotations
Wasn’t it awesome that we could meet the management’s requirements with such a little amount of code using Spring?
So using Spring definitely gives us a lot of power, however it is not nice to debug XML files. It would be really cool if we can configure our Beans in Java so we can debug them more easily.
Luckily, since version 3.0, Spring comes with an option to use Java annotation based configuration.
5.1. Lab 4: Java Annotations
In labs/04-java-annotations
you can find the lab.
5.1.1. Exercise 8
In the project for this exercise you’ll find one failing test in SpringBeansTest. Explain to your neighbor why this test fails while the other tests in this file pass.
Make the other tests in this file fail as well by editing the beans in the application context.
Now make all tests pass by using Java annotation based auto wiring via constructor dependency injection.
Remove the annotation you just added and now make all tests pass by auto wiring using setter dependency injection.
Remove the dispenser bean from the application context and make the tests pass again using a Java annotated dispenser bean. For this to work you need to make a Java configuration file where you declare your Dispenser Bean and create a Bean of this configuration file in your application context XML file.
Put a breakpoint in your Java Bean definition. Run the tests in debug mode and see how you are now able to properly debug your Bean! =)
Now remove your Dispenser Bean definition and use annotated components to fix the tests again.
A Component is exactly the same as a Bean, but a Bean is explicitly defined where a Component is auto-detected and auto-configured. |
5.1.2. Resources
6. Web Services
As you’ve seen in the previous exercises, Spring is not exclusively meant for web projects. However it is where it became the most popular. The reason for this is that without a framework like Spring it is much harder to set up a web service in Java yourself. You have to implement so called Servlets in order to be able to handle HTTP requests. Spring implements a servlet called the DispatcherServlet, the DispatcherServlet abstracts a lot of the difficulty of Java servlets away from you. This way you can handle HTTP requests by simply using annotations in your Java code just like we did in the previous exercise.
6.2. Lab 5: Hendrik-Jan Online
You’ve switched companies and are now working for an online store that sells books called Hendrik-Jan Online
, known to the public as hol.com
.
Hol.com is using Spring based web services.
You were hired as a Spring expert and tasked with the development of a new service called the BookService.
The BookService will contain information about each book sold by hol.com and expose it via a REST API.
In labs/05-web-services
you can find the lab.
The lab comes with a base project. In this lab we’ll focus on all sources in the webapp
directory.
.
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── hol
│ │ └── book
│ │ ├── controller (1)
│ │ │ ├── BooksController.java
│ │ │ └── WelcomeController.java
│ │ └── domain
│ │ └── Book.java
│ ├── resources
│ └── webapp (2)
│ ├── WEB-INF
│ │ ├── bookservice-servlet.xml
│ │ ├── jsp
│ │ │ └── welcome.jsp
│ │ └── web.xml
│ └── index.jsp
└── test
└── java
1 | A Controller is a class that can handle requests. We’ll focus on these classes in the next lab. |
2 | The webapp directory contains all the sources that are relevant to getting your server up and running. |
We’ll first walk through all the files in this directory.
Then we’ll see if we can succeed in getting it deployed to a Tomcat server.
And finally we’ll add some more endpoints.
6.2.1. web.xml
The core of our web application is the web.xml file in webapp/WEB-INF
.
It has nothing to do with Spring, it is purely for Java web applications.
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<display-name>BookService</display-name> (1)
<welcome-file-list>
<welcome-file>index.jsp</welcome-file> (2)
</welcome-file-list>
<servlet>
<servlet-name>bookservice</servlet-name> (4)
<servlet-class>
org.springframework.web.servlet.DispatcherServlet (3)
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping> (5)
<servlet-name>bookservice</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
1 | This is the name that will be displayed in your browser tab. |
2 | This is the template of the home page of the BookService. |
3 | Here you see the Spring DispatcherServlet defined. It will handle the incoming requests and make sure they end up in the right Controller class. You can define multiple servlets if you want to. |
4 | We gave our DispatcherServlet the name bookservice . |
5 | Here we define the url-patterns for the DispatcherServlet. Any request matching the url-patterns we state here will be routed to our DispatcherServlet. |
Url patterns: The url-pattern should be prepended to the path of the DispatcherServlet endpoint you try to reach. To reach an endpoint defined in your Spring application the url is: host:port/url-pattern/path-to-endpoint The url-pattern / means any incoming request other than the request for the home page will be routed to your DispatcherServlet.If you want it to handle the home page as well, you should add an empty url-pattern tag: <url-pattern><url-pattern>
|
6.2.2. bookservice-servlet.xml
Because in web.xml
we defined the name of our DispatcherServlet to be bookservice
, the DispatcherServlet will expect a Spring application context file called bookservice-servlet.xml
at the same level as the web.xml
file.
Of course you can change this default behavior. For that you should insert the following tag in the declaration of your servlet:
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/path-to-file/file-name.xml</param-value>
</init-param>
The bookservice-servlet.xml
file is basically the same thing as the applicationContext.xml
file that we saw in the CoffeeMachine.
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.hol.book.controller"/> (1)
<mvc:annotation-driven/> (2)
<bean id="viewResolver" (3)
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!--class="org.springframework.web.servlet.view.UrlBasedViewResolver">--> (5)
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/> (4)
<property name="suffix" value=".jsp"/>
</bean>
</beans>
1 | Here we define a component scan on the controller package so the two controller classes will be added to the Spring IoC Container. |
2 | This tag adds support for web specific Java annotations used by the controller classes. Such as @Controller and @RequestMapping . |
3 | Here we define the first Bean of our web application. It is a ViewResolver. The ViewResolver will be able to find and render .jsp files. |
4 | This is the location where all .jsp files that can be found by the ViewResolver should be placed. |
5 | As you can see there are multiple types of ViewResolvers. |
6.2.3. JSP’s
Take a look at the .jsp
files. JSP files are templates that get transformed into HTML.
You can define variables like the ${welcome}
variable in welcome.jsp
.
You can then set this variable from your Java code like is done in the Controller classes.
You can also add logic using JSTL
(JSP Standard Tag Library).
However, it is considered best practice to do as little logic as possible in your jsp files.
6.2.4. Exercise 9
Deploy the BookService to a Tomcat server.
-
Download a Tomcat 8 zip from the official web site.
-
In IntelliJ add a run configuration for Tomcat Server to your project.
-
Use the Tomcat you just downloaded for this configuration.
-
In the deployment tab configure the exploded war of the BookService to be deployed at startup.
When you have your server running, try to find the welcome page of the BooksController. Why is it under that URL?
Then try changing the url-pattern to:
<url-pattern>/dispatcher/*</url-pattern>
And figure out how you can still reach the endpoints handled by the DispatcherServlet.
6.2.5. Exercise 10
Add another JSP template with an endpoint and experiment with the features of jsp’s.
In the Java code of your endpoint create a list of strings with some arbitrary values.
Try displaying an unordered list with a bullet for each element in the list.
You need the following dependencies in order to be able to use jstl tags in your jsp files:
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-spec</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.taglibs</groupId>
<artifactId>taglibs-standard-impl</artifactId>
<version>1.2.1</version>
</dependency>
6.2.6. Exercise 11
The base project contains a fake DAO
(Data Access Object), which stores the books in an ArrayList instead of in a database.
In the next lab we’ll turn this into real database operations, but for now we focus solely on creating endpoints.
Extend the right classes such that the book service compiles again.
Then extend the BooksController class so that we expose a full REST API for our book class.
Meaning all CRUD (Create Read Update Delete) methods in the BookDao will have a corresponding endpoint which matches the REST specification.
For testing your endpoints you can use a tool like Postman.
7. JDBC
Cool, we made a REST API for our books, however our API is not much use if we don’t actually store the data.
Java has a library called JDBC
(Java Database Connectivity).
JDBC lets you manage database connections in a database-independent way.
This is nice, because this way your application does not depend on a specific type of database.
It is always good to prevent this kind of dependency so you can easily switch over to a different type of database.
At some point you might want to change to a different database, for instance because it performs better for your use case or because it is cheaper.
If you didn’t use JDBC but something database specific, it will be much more work to migrate.
7.2. Lab 6: JDBC
Alright! Let’s get that data saved!
Choose a SQL database like MariaDB or Postgres, install it locally (or use docker) and make a database called bookservicedb
with user bookserviceuser
and password password
.
Then look for the jdbc driver maven dependency of the database that you chose and include it in your pom-file.
7.2.1. Exercise 12
Configure a Bean of type BasicDataSource
and connect it to your bookservicedb.
Implement a class called RealBookDao
which implements the BookDao
interface and make it use your database.
Then make sure an instance of the real book dao is autowired into the BooksController
class.
7.2.2. Resources
7.2.3. Exercise 13
If you want to challenge yourself, try some of the following things:
-
Try to write some tests which test the ReadBookDao using an in memory database like H2.
-
Try to use prepared statements instead of the NamedParameterJdbcTemplate.
-
Try to create a deployable and functional web service from scratch without peeking into the code of this training.