A1 JavaSpringBootRestAPI
A1 JavaSpringBootRestAPI
Course Objectives
The main objective of this course is to go, in details, through the process of implementing a
realistic RESTful API project using Java, Spring Boot and relational databases.
This course explains the most important development and operational concerns that need to be
addressed when creating a real life application. At the end of the course you should be able to
create your own production grade RESTful API application which is ready for deployment.
Project Objectives
The basis of this course is a completed Eclipse based Java project that contains all
development assets required to create a production ready RESTful API application.
The project is delivered with this course as an archive: coursea1.zip
Project Output
The main outcome of this project is a RESTful API that allows for the management of a rental
process for physical assets.
The code is available as an archive with this course and it is heavily commented. We
recommended that you create two Eclipse projects: one with the provided project and one that
you build step by step using the instructions in this course.
Business Requirements
We need to implement three major features:
Software Versions
These are the versions we are going to use for the major pieces of software:
● Java - 9
● Spring Boot - 2.0.2
● Eclipse Java EE IDE - Oxygen
● MySQL - 8.0.11
© coaxial.ro 2018 2
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Windows:
https://download.java.net/java/GA/jdk9/9.0.4/binaries/openjdk-9.0.4_windows-x64_bin.tar.gz
MacOS
Decompress the archive into /Library/Java/JavaVirtualMachines
sudo su
cd /Library/Java/JavaVirtualMachines
mv ~/Downloads/openjdk-9.0.4_osx-x64_bin.tar.gz .
tar -zxvf openjdk-9.0.4_osx-x64_bin.tar.gz
© coaxial.ro 2018 3
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
© coaxial.ro 2018 4
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Click Finish
© coaxial.ro 2018 5
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
© coaxial.ro 2018 6
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Click on Apply.
Change the Compiler compliance level to 9. In the Preferences dialog box click on Compiler.
© coaxial.ro 2018 7
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
© coaxial.ro 2018 8
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Unzip the coursea1.zip on your workstation. In Eclipse navigate to File > Import…, select
Existing Maven Projects and click Next.
© coaxial.ro 2018 9
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
In the next dialog box (Import Maven Projects) click on Browse, select the rentals folder
inside coursea1 then click on Finish. The Eclipse project name is rentals.
Project Metadata
Group: course.a1
Artifact: rentals2
Dependencies
© coaxial.ro 2018 10
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Download the generated project, unzip it in ~/courses/a1. In Eclipse, go to File > Import…
and select “Existing Maven Projects” to import the newly generated project
(~/courses/a1/rentals2).
cd ~/courses/a1/rentals2
mvnw compile
When run for the first time the mvnw command will download Maven and all project’s
dependencies so it will take a couple of minutes.
Note If this command fails with an error like this: “ZipFile invalid LOC header (bad signature)”,
clear Maven’s cache. This should only happen if you already used Maven for other projects
and already had a Maven cache in place.
cd ~
rm -rf .m2
mvnw compile
© coaxial.ro 2018 11
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Sometimes it is convenient to use Maven commands from within Eclipse’s UI. Eclipse comes
with a built-in Maven installation but to ensure we use the same version of Maven in and outside
Eclipse we configure Eclipse to use the version of Maven that is used by the Spring Maven
plugin.
Debugging
To start a debugging session run the application using the command below.
That will start the application JVM in debug mode and the JVM will suspend until a debugger
attaches to the specified port. This are the last lines you see when starting the application in
debug mode:
The JMV now waits for the remote debugger. In Eclipse click on Run > Debug
Configurations… In the Debug Configurations dialog box right click on Remote Java
Application then click on New. Fill in the right hand form panel with these values:
© coaxial.ro 2018 13
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Name: RentalsMvn
Project: rentals
Host: localhost
Port: 5005
To attach to the running application click again on Run > Debug Configurations..., select the
newly added Remote Java Application entry and click on Debug.
© coaxial.ro 2018 14
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
We can test our application during development using Postman - a tool that makes it easy to
build HTTP requests using a GUI.
In this section we explain how to import in Postman the collection of requests which is provided
in this course and how to build your own requests.
You should now have a CourseA1 collection in Postman that contains ready to be used
requests that can be used to test the application during development.
Because our application is secured we need to obtain an access token before we can issue
requests against it.
© coaxial.ro 2018 15
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
An access token is obtained by accessing the /oauth/token URL. In the CourseA1 Postman
collection this is done via the OAuth-Token request.
Open the OAuth-Token request and provide the username for the test you want to perform.
To issue a request to any of our Rentals REST API you need to provide the access token via
the HTTP Authorization header.
© coaxial.ro 2018 16
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Open any request in the provided collection, click on the Authorization tab and provide the
access token in the Token field.
As examples we are going to build the request to get the access token from the application
(OAuth-Token) and a request to search for users (Users Search)
In Postman create a new request. Click on the New button, provide a Request name
(OAuth-Token), select CourseA1 (or any other folder if you have not yet imported the provided
collection) and then click on Save to CourseA1.
© coaxial.ro 2018 17
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Click on the newly created request. Set the HTTP method to POST, provide the URL
http://localhost:8080/oauth/token then click on the Authorization tab and provide the client id
for Username (rentals-client-id) and the client secret for Password (ddkl44#SDsfg) (see
screenshot below).
© coaxial.ro 2018 18
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Next click on the Body tab, click on x-www-form-urlencoded and provide the following values:
grant_type - password
username - [email protected] (see the S
ecurity section for the list of users created for testing
during development)
password - pass
scope - read write
© coaxial.ro 2018 19
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
© coaxial.ro 2018 20
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
<?
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
>
<!-- Project information -->
<
name
>rentals</
name
>
<
description
>Course A1 project for Spring Boot - Rentals RESTful
API</
description
>
<!-- Project ID: <groupId>:<artifactId>:<version> -->
<
groupId
>course.a1</
groupId
>
<
artifactId
>rentals</
artifactId
>
<
version
>0.0.1-SNAPSHOT</
version
>
© coaxial.ro 2018 21
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
<!-- We package our application as a standalone jar file -->
<
packaging
>jar</
packaging
>
<!-- We want to inherit the Maven configuration from
spring-boot-starter-parent as it will provide default configuration for
Maven plugins and dependency versions -->
<
parent
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-parent</
artifactId
>
<
version
>2.0.2.RELEASE</
version
>
<
relativePath/
>
</
parent
>
<!-- Project properties -->
<
properties
>
<
project.build.sourceEncoding
>UTF-8</
project.build.sourceEncoding
>
<
project.reporting.outputEncoding
>UTF-8
</
project.reporting.outputEncoding
>
<
!-- Source and target java set to Java 9 -->
<
java.version
>9</
java.version
>
<
!-- We need to explicitly set the version of this plugin as earlier
releases have a bug when using Java 9 and 10:
https://issues.apache.org/jira/browse/SUREFIRE-1439.
This plugin is used for running tests with Maven. -->
<
maven-surefire-plugin.version
>2.21.0</
maven-surefire-plugin.version
>
</
properties
>
<!-- We are using Maven profiles to build different versions of our
application depending on the environment in which it runs (development,
production or test) -->
<
profiles
>
<
!-- Development profile -->
<
profile
>
<
id
>dev</
id
>
<
properties
>
<
activatedProperties
>dev</
activatedProperties
>
</
properties
>
<
!-- We use the H2 database for development -->
© coaxial.ro 2018 22
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
<
dependencies
>
<
dependency
>
<
groupId
>com.h2database</
groupId
>
<
artifactId
>h2</
artifactId
>
<
scope
>runtime</
scope
>
</
dependency
>
<
!-- This will restart the application when files are changed
in your IDE -->
<
!-- It will also make the H2 console available in developer
mode at this location: http://localhost:8080/h2-console -->
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-devtools</
artifactId
>
<
optional
>true</
optional
>
</
dependency
>
</
dependencies
>
</
profile
>
<
!-- Production profile -->
<
profile
>
<
id
>prod</
id
>
<
properties
>
<
activatedProperties
>prod</
activatedProperties
>
</
properties
>
<
dependencies
>
<
!-- We use MySQL for production -->
<
dependency
>
<
groupId
>mysql</
groupId
>
<
artifactId
>mysql-connector-java</
artifactId
>
<
scope
>runtime</
scope
>
</
dependency
>
</
dependencies
>
</
profile
>
<
!-- Test profile -->
<
profile
>
<
id
>test</
id
>
<
properties
>
<
activatedProperties
>test</
activatedProperties
>
© coaxial.ro 2018 23
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
</
properties
>
<
dependencies
>
<
!-- We use the H2 database for testing -->
<
dependency
>
<
groupId
>com.h2database</
groupId
>
<
artifactId
>h2</
artifactId
>
<
scope
>test</
scope
>
</
dependency
>
</
dependencies
>
</
profile
>
<
!-- Integration test profile -->
<
profile
>
<
id
>itest</
id
>
<
properties
>
<
activatedProperties
>itest</
activatedProperties
>
</
properties
>
<
dependencies
>
<
!-- We use the H2 database for testing -->
<
dependency
>
<
groupId
>com.h2database</
groupId
>
<
artifactId
>h2</
artifactId
>
<
scope
>test</
scope
>
</
dependency
>
<
!-- required by TestRestTemplate -->
<
dependency
>
<
groupId
>org.apache.httpcomponents</
groupId
>
<
artifactId
>httpclient</
artifactId
>
<
scope
>test</
scope
>
</
dependency
>
</
dependencies
>
</
profile
>
</
profiles
>
<
dependencies
>
© coaxial.ro 2018 24
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
<
!-- We use Spring Data JPA repositories -->
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-data-jpa</
artifactId
>
</
dependency
>
<
!-- Required for building RESTful applications -->
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-web</
artifactId
>
</
dependency
>
<
!-- Required for metrics and health check -->
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-actuator</
artifactId
>
</
dependency
>
<
!-- Required for exposing a prometheus endpoint for monitoring -->
<
dependency
>
<
groupId
>io.micrometer</
groupId
>
<
artifactId
>micrometer-registry-prometheus</
artifactId
>
</
dependency
>
<
!-- Required to provide REST API documentation -->
<
dependency
>
<
groupId
>io.springfox</
groupId
>
<
artifactId
>springfox-swagger2</
artifactId
>
<
version
>2.8.0</
version
>
</
dependency
>
<
!-- We want to have Swagger UI installed to visualize our REST API
-->
<
dependency
>
<
groupId
>io.springfox</
groupId
>
<
artifactId
>springfox-swagger-ui</
artifactId
>
<
version
>2.8.0</
version
>
</
dependency
>
© coaxial.ro 2018 25
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
<
!-- Needed to run tests with Maven before building the application
-->
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-test</
artifactId
>
<
scope
>test</
scope
>
</
dependency
>
<
!-- Security -->
<
dependency
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-starter-security</
artifactId
>
</
dependency
>
<
!-- OAuth -->
<
dependency
>
<
groupId
>org.springframework.security.oauth</
groupId
>
<
artifactId
>spring-security-oauth2</
artifactId
>
<
version
>2.3.3.RELEASE</
version
>
</
dependency
>
<
!-- Security testing helpers -->
<
dependency
>
<
groupId
>org.springframework.security</
groupId
>
<
artifactId
>spring-security-test</
artifactId
>
<
scope
>test</
scope
>
</
dependency
>
<
!-- Jackson/JSON library -->
<
!-- We need this to allow conversion of new Date and Time API
(JSR-310) classes in Java to a human readable format suitable for JSON
output
-->
<
dependency
>
<
groupId
>com.fasterxml.jackson.datatype</
groupId
>
<
artifactId
>jackson-datatype-jsr310</
artifactId
>
</
dependency
>
<
!-- Needed to handle Hibernate proxies correctly -->
<
dependency
>
© coaxial.ro 2018 26
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
<
groupId
>com.fasterxml.jackson.datatype</
groupId
>
<
artifactId
>jackson-datatype-hibernate5</
artifactId
>
</
dependency
>
<
!-- JAXB APIs no longer in Java 9 SE - so they require explicit
listing. These libraries are needed by the Surefire Maven plugin that
helps with testing -->
<
dependency
>
<
groupId
>javax.xml.bind</
groupId
>
<
artifactId
>jaxb-api</
artifactId
>
<
version
>2.2.11</
version
>
</
dependency
>
<
dependency
>
<
groupId
>com.sun.xml.bind</
groupId
>
<
artifactId
>jaxb-core</
artifactId
>
<
version
>2.2.11</
version
>
</
dependency
>
<
dependency
>
<
groupId
>com.sun.xml.bind</
groupId
>
<
artifactId
>jaxb-impl</
artifactId
>
<
version
>2.2.11</
version
>
</
dependency
>
<
dependency
>
<
groupId
>javax.activation</
groupId
>
<
artifactId
>activation</
artifactId
>
<
version
>1.1.1</
version
>
</
dependency
>
<
!-- Apache Commons utilities -->
<
dependency
>
<
groupId
>org.apache.commons</
groupId
>
<
artifactId
>commons-lang3</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.apache.commons</
groupId
>
<
artifactId
>commons-collections4</
artifactId
>
<
version
>4.1</
version
>
© coaxial.ro 2018 27
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
</
dependency
>
<
!-- We need to deal with money amounts in our business code -->
<
dependency
>
<
groupId
>javax.money</
groupId
>
<
artifactId
>money-api</
artifactId
>
</
dependency
>
<
dependency
>
<
groupId
>org.javamoney</
groupId
>
<
artifactId
>moneta</
artifactId
>
<
version
>1.1</
version
>
</
dependency
>
<
!-- Get H2 jars added to the classpath when running the tests -->
<
dependency
>
<
groupId
>com.h2database</
groupId
>
<
artifactId
>h2</
artifactId
>
<
scope
>test</
scope
>
</
dependency
>
</
dependencies
>
<
build
>
<
!-- Process application.properties file to activate the current
profile -->
<
resources
>
<
resource
>
<
directory
>src/main/resources</
directory
>
<
filtering
>true</
filtering
>
</
resource
>
</
resources
>
<
plugins
>
<
plugin
>
<
groupId
>org.springframework.boot</
groupId
>
<
artifactId
>spring-boot-maven-plugin</
artifactId
>
<
executions
>
<
execution
>
© coaxial.ro 2018 28
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
<
goals
>
<
!-- This execution goal allows the application
to be built using 'mvnw package' command.
Without it you can still build the application
Using the 'mvnw package spring-boot:repackage'
-->
<
goal
>repackage</
goal
>
</
goals
>
</
execution
>
</
executions
>
</
plugin
>
<
!-- We need to customize the Surefire plugin - responsible for
running unit tests; specify the Spring profile to be used when
running the unit tests -->
<
plugin
>
<
groupId
>org.apache.maven.plugins</
groupId
>
<
artifactId
>maven-surefire-plugin</
artifactId
>
<
configuration
>
<
argLine
>-Dspring.profiles.active=test</
argLine
>
<
forkCount
>1</
forkCount
>
<
reuseForks
>true</
reuseForks
>
</
configuration
>
</
plugin
>
<
!-- We need to customize the Failsafe plugin - responsible for
running integration tests; specify the Spring profile to be used
when running the tests -->
<
plugin
>
<
groupId
>org.apache.maven.plugins</
groupId
>
<
artifactId
>maven-failsafe-plugin</
artifactId
>
<
configuration
>
<
argLine
>-Dspring.profiles.active=itest</
argLine
>
<
forkCount
>1</
forkCount
>
<
reuseForks
>true</
reuseForks
>
</
configuration
>
</
plugin
>
</
plugins
>
© coaxial.ro 2018 29
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
</
build
>
</
project
>
./mvnw package
Note that this command builds the application after running the unit tests.
Design
Before we start coding our application we need to think about a design that will drive the actual
work. This step is essential in producing consistent, easily readable code. Once you decided on
a design it becomes straightforward to organize your code and it reduces the amount of
redundant decision making that occurs during development.
We structure our application in several natural layers, each layer being accessible from other
layers only via clearly defined interfaces. These layers are:
© coaxial.ro 2018 30
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
The diagram above shows the basic interactions between components in different layers. A
client invokes a Rest controller via the Rest API, the Rest controller invokes zero or more
services via service interfaces, these services invoke one or more components inside the
service layer and zero or more repositories via Repository interfaces. The components in the
service layer are reusable bits of code that are only accessible to services in the service layer.
Other layers could have their own components but most of them will reside in the service layer.
During this phase we also need to think about how we manage our transactions. This requires
some knowledge on how our technology stack (Spring + Hibernate) handles transactions. The
main transaction is handled in the service layer since all operations that manipulate our
application’s data take place there. However this technology stack modifies/enhances some of
the entity instances that might be returned by the service layer so that it can support lazyly
loading the associated entities; these changes are done via bytecode manipulation. That means
that some of the entity’s get methods will return an instance of an object that cannot be used
outside a persistence session.
Spring provides a mechanism called Open Session In View (OSIV) that allows accessing these
getter methods on entities in the Rest layer, outside the service layer where a persistence
session is normally available. This mechanism makes it very easy to work with Spring and
Hibernate but it has some drawbacks that you need to be aware of. A persistence session is
attached to the thread handling the HTTP request and is available until that request is fully
processed. That allows you to transparently invoke the entity getter operations returning
enhanced entity objects in the Rest layer. Each time an entity getter operation that returns a
lazily loaded object is invoked in the Rest layer a new transaction is started and committed. You
© coaxial.ro 2018 31
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
can also end up loading a lot of objects from the database when invoking such method that
returns collections of objects. In order to develop a well behaving application you have to keep
this in mind and pay attention to how you access the entities in the Rest layer.
There are ways to do this differently but none are hassle free - you need to process your entities
in the service layer before returning them to the Rest controllers; this can be done manually or
you can write code to automatically strip off any uninitialized Hibernate proxies in the entities.
Using the OSIV mechanism is very convenient as long as you understand how it works and pay
attention when writing your code. OSIV is used in this course’s project.
Data Modeling
We start this project from the bottom up by modelling the basic data entities.An entity is the
basic building block that describes the structure of our data. It maps directly to both a database
table and a Java object.
Below is a diagram describing each entity and its relationships with other entities.
Note: You can use an online SQL data modelling tool such as sqldbm.com. For basic usage
it was free at the time this course material was prepared. There are other similar services that
offer a free tier.
.
Entities
.
© coaxial.ro 2018 32
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Each entity in this case has a simple primary key. A CHAR(40) data type was used for columns
representing finite sets of values(enumerations) such as state and type.
The naming used for the design of the data model is relatively common to MySQL. There’s no
standard for naming things in SQL - not even for MySQL specifically but these rules do work
well:
● table and column names are in snake case (e.g. asset_item) - it is a good idea to avoid
using a mixture of lower and upper case as some databases display platform specific
case sensitivity
● table names are singular - it is a better match for the naming convention of Java objects;
some SQL developers prefer plurals for table names but I believe singular works better
in this case
● column names are singular - most commonly used convention for SQL/database
development
● no prefixes used unless necessary - to keep things simple and readable
© coaxial.ro 2018 33
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
We also modelled a link table user_role - this will be handled automatically for us by the JPA
provider.
DDL Generation
For the production database we are going to use MySQL. In order to setup the database we
need to produce the DDL to be run against the MySQL database. There are two options to
generate the DDL from the data model we designed earlier:
● use the modelling tool to generate the DDL and then complete manually any missing
items
© coaxial.ro 2018 34
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
● temporarily change the production properties file to configure Hibernate, our JPA
provider, to generate the DDL, then export the database and extract the DDL
At the top of the generated file add these the following lines to create and use the RENTALS
database and to create the sequence table that is going to be used by Hibernate, our JPA
provider:
© coaxial.ro 2018 35
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
USE RENTALS;
At the bottom of the file add SQL statements to populate the database with application
configuration data - in our case that is asset rental pricing information.
You can execute this SQL file against a MySQL database using the following command:
spring.jpa.hibernate.ddl-
auto
=create-drop
© coaxial.ro 2018 36
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
This will configure the JPA provider to run the DDL to create all the required database artefacts.
Then you can export the SQL using the mysqdump utility:
In the application.properties file we have the properties that are common across all
environments and properties specific to each environment are stored in dedicated files:
© coaxial.ro 2018 37
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
To activate a specific profile we need to change the application.properties source file. In order
to avoid that we can pass this property in the command line using the Spring command line
switch syntax (--spring.profiles.active=dev) when you run your application. However
if you want to minimize the risk of accidentally turning on the wrong profile we can use Maven
profiles so that we can build the application with the correct profile before it is deployed in its
execution environment.
The special token @activatedProperties@ provided as the value of the active Spring profile is
replaced during building by the active Maven profile (dev or prod).
Now we can build the application with the following style of command, where the -P Maven
switch specifies the profile name.
You can also pass the -P switch when executing other Maven phases, such as when starting up
the application with spring-boot:run
Let’s go in details through the application properties files. Each section and each entry in these
files is commented and we recommend going through the content of each of these files in order
to understand how the application is configured.
application.properties
As mentioned earlier this file stores application properties that are common across all
environment types.
© coaxial.ro 2018 38
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
#########################################
### Main Spring configuration file for the application
### It contains properties that are common to all profiles
#########################################
# This value needs to be changed to 'prod' when deploying to production.
# and to 'dev' during development. We are using Maven profiles to
# determine the actual value
spring.profiles.
active
=@activatedProperties@
#########################################
### Spring JPA configurations
#########################################
# tell Hibernate to use MySQL SQL
spring.jpa.database-
platform
=org.hibernate.dialect.MySQL
5D
ialect
# this instructs Hibernate to use quotes around table and columns to avoid
conflicts
# with keywords
spring.jpa.properties.hibernate.
globally_quoted_identifiers
=true
# this is true by default but it is very important
# so it is explicitly enabled here as well
# this property allows the Rest controllers to access the
# database when encountering unitilialized proxy objects in the
# objects returned from the service layer
spring.jpa.open-in-
view
=true
##########################################
### Spring boot actuator properties - Spring actuator is a module
### that helps exposing operational information about your running
### application (metrics, health indicator, etc)
##########################################
© coaxial.ro 2018 39
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
© coaxial.ro 2018 40
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
application-dev.properties
This file contains application properties which are only available in the dev profile (during
development).
For development we are using an H2 embedded database, we drop and create the application
database tables every time we restart the application and we enable SQL tracing in JPA to help
with troubleshooting issues.
#########################################
### Spring configuration file used for development
###
### Properties set in this file override properties
### in the application.properties file
#########################################
#########################################
### Spring datasource configurations
### We want to use H2 for development instead of MySQL
#########################################
# JDBC URL for the H2 database
spring.datasource.
url
=jdbc:h
2
:file:~/rentals
# H2 database credentials
spring.datasource.
username
=sa
spring.datasource.
password
=
# H2 JDBC driver class
spring.datasource.driver-class-
name
=org.h
2
.Driver
#########################################
### Spring JPA configurations
### Setting useful properties for a development environment
© coaxial.ro 2018 41
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
#########################################
# This creates Hibernate to create the tables based on the entity definitions
# every time the application is started
# For a production environment turn this off
spring.jpa.hibernate.ddl-
auto
=create-drop
# it helps with troubleshooting SQL issues; all SQL statements issued
# are logged to output
spring.jpa.show-
sql
=true
# this is useful for troubleshooting issues with more complicated SQLs
spring.jpa.properties.hibernate.
format_sql
=true
# point to Logback's configuration file for development
logging.
config
=classpath:logback-dev.xml
application-prod.properties
This file contains application properties for the prod (production) profile.
For production we are using a MySQL database, we use a dedicated logging configuration file
and we enable SSL for our embedded Tomcat server serving our RESTful API.
#########################################
### Spring configuration file for production
#########################################
#########################################
### Spring datasource configurations
#########################################
# JDBC URL for the MySQL database
spring.datasource.
url
=jdbc:mysql://localhost:
3306
/RENTALS?
useSSL
=false
© coaxial.ro 2018 42
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
As you might have noticed in the application-*.properties files so far we only provided
infrastructure and operational parameters.
For business level application properties we define a separate configuration file:
application-configuration.properties. This separation is not required but it is done purely for
© coaxial.ro 2018 43
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
organizational reasons. If you have business level properties that are profile dependent you
would need to add those properties to the corresponding application-*.properties file for the
respective profile.
For details on how these business level application properties are managed see section
Application Configuration.
Validation
Validation of data for our data model entities is performed by Spring Boot using a set of
annotations defined in Java Bean Validation API (JSR-380). These annotation allows you to set
constraints on the value for each of the fields of an entity class.
Sometimes a single entity has a different set of constraints for its fields depending on the
application layer at which the entity is processed. For instance an entity might have a timestamp
field createdAt that can be null in the REST layer but it has to be not null in the persistence
layer.
To distinguish the application of constraints at various layers in the application we create two
empty interface classes called Rest and P
ersistence in the course.a1.rentals.validation
package. These interfaces will be used in the validation annotations to specify what type of
validation needs to be done (validation groups).
© coaxial.ro 2018 44
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Caching
Caching is another cross cutting concern that needs to be addressed in most applications. We
use in our project Spring’s facilities for managing caches which makes it very easy to cache
data and avoid repetitive request to the persistence layer.
In order to enable Spring’s annotation based cache management you need to annotate the
application startup class with the aptly named @EnableCaching annotation. That instructs
Spring to search for method names annotated with the @Cacheable annotation and cache the
returned data.
The default cache manager that Spring uses is very simple and based on Java’s
ConcurrentHashMap - that is acceptable for some applications and some types of data.
If a more complex cache manager is required, it can be configured via Spring - Spring has
support for a large number of commonly used cache managers (see this Spring Boot Caching
documentation for the full list).
For instance, if you want to use Ehcache, you need to add the spring-boot-starter-cache
dependency in your pom.xml file and provide in the src/main/resources folder the
ehcache.xml cache configuration file.
© coaxial.ro 2018 45
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Spring will create a cache with the key being formed by the method’s parameters, in our case
that is just the asset type. The value to be cached is the data returned by the annotated method.
To refresh a value in the cache you need to mark a method with the @CachePut annotation -
that will cause the value returned from that method to be placed into the cache and replace the
existing entry with the provided key (assetType in our case).
Exception Handling
There are two important aspects to exception handling:
● It has to provide the best possible error message in the application logs so that
troubleshooting issues is easier
● It has to provide the client with clear error message when an error is encountered.
Spring will select the right method in the class for handling a particular exception based on the
@ExceptionHandler annotation defined for methods.
Below is the method in our exception handler class used to handle all application exceptions
(instances of RentalsException). The method name is not important; the important bits are the
@ExceptionHandler(RentalsException.class) annotation and the method signature which
can be very flexible; we use a RentalsException parameter and a Locale parameter.
Spring will pass the actual exception to be handled in the RentalsException parameter and the
locale for the request that triggered the exception in the Locale parameter. These are used to
generate a localized error message to be sent to the client.
We also define a method to handle any generic exceptions. We send the client a generic,
localized error message instead of the default error message. Note that Spring provides a
decent error message for unhandled exceptions (see below) but it’s still good practice to be in
control of what is being sent to the client in this case - for security reasons you want to ensure
no stack traces or other unfiltered error messages reach the client.
We also check here if the development profile (dev) is activated and in that case we append the
stack trace to error message sent to the client.
© coaxial.ro 2018 47
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
// catch all handler; provide to the REST client a generic "unknown error" message
/
/ in its own Locale instead of the default, possible more verbose error message
@
ExceptionHandler
(Exception
.class
)
p
ublic
ResponseEntity
<
RestErrorMessage
>
handleException
(Exceptione
x
,
Locale
locale
) {
// log the exception first
l
og
.log
(
Level
.SEVERE
, "
Error"
, e
x
);
S
tring
errorMessage= m
essageSource
.getMessage
(
UNKNOWN_ERROR
, n
ull
,
locale
);
// if the development profile is active, add the stacktrace to the error
// message
S
tring
stackTrace=
null
;
i
f
(rentalsUtils
.
isDevProfileActive
()) {
stackTrace=
ExceptionUtils
.
getStackTrace
(ex
);
}
r
eturn
new
ResponseEntity
<>(
n
ew
RestErrorMessage
(UNKNOWN_ERROR
,
errorMessage
,
stackTrace
),
H
ttpStatus
.INTERNAL_SERVER_ERROR
);
}
@ExceptionHandler
(AccessDeniedException
.c
lass
)
© coaxial.ro 2018 48
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
publicR
esponseEntity
<RestErrorMessage
>
handleMethodNotSupportedException
(
AccessDeniedException
ex
, L
ocale
locale
) {
l
og
.log
(Level
.INFO
, "
Access denied error"
, e
x
);
r
eturn
newR
esponseEntity
<RestErrorMessage
>(
new
RestErrorMessage
(e
x
.g
etMessage
()),
HttpStatus
.FORBIDDEN
);
}
@ExceptionHandler
(EntityNotFoundException
.class
)
publicR
esponseEntity
<RestErrorMessage
> h
andleEntityNotFoundException
(
EntityNotFoundExceptione
x
,
Localel
ocale
) {
r
eturn
newR
esponseEntity
<RestErrorMessage
>(
new
RestErrorMessage
(e
x
.g
etMessage
()),
HttpStatus
.B
AD_REQUEST
);
}
Transaction Management
Transaction management is handled by Spring - service classes and methods are annotated
with the @Transactional annotations.
A class marked with this annotation is modified by Spring so that every method that does not
override the @Transactional annotation executes within the boundaries of a transaction.
© coaxial.ro 2018 49
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Logging
By default Spring Boot provides support for Logback logging framework. It’s a dependency
inherited from any of the spring-boot-starter-* projects we listed as dependencies in our
pom.xml file.
The default logging configuration writes log messages to console. If we want to change any
logging parameters we need to provide values in our application*.properties files.
In practice the logging requirements for the dev and prod profiles are different so we provide
specific configuration in the respective properties files: application-dev.properties or
application-prod.properties.
If you need to change the logging levels for some loggers you need to add entries of the form
logging.level.<logger_name> in the corresponding application*.properties file (see
example below).
In order to enable logging to a log file you can to provide the name of the file in the properties
file using the logging.file property.
logging.file=rentals.log
© coaxial.ro 2018 50
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
logging.level.org.springframework.security=DEBUG
logging.level.org.hibernate=ERROR
Note that only basic logging configuration can be provided via the application*.properties files.
For finer grained configuration such as log file rolling - which is a requirement for a production
environment - we need to use the logging framework’s native configuration mechanism.
In our Eclipse project we created a logback-prod.xml file in src/main/resources with the content
below. This xml file contains native Logback configuration data.
<configuration>
<appender name="FILE"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>rentals.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- daily rollover based on size - it contains the date and the file
index -->
<fileNamePattern>rentals.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- keep 30 days of history and cap it at 10GB total size -->
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
<!-- cap each daily file at 100MB - a day can have more than one log
file -->
<maxFileSize>100MB</maxFileSize>
</rollingPolicy>
<encoder>
<pattern>%date{ISO8601} [%thread] %-5level %logger{35} -
%msg%n</pattern>
</encoder>
</appender>
<root level="ERROR">
<appender-ref ref="FILE" />
</root>
</configuration>
© coaxial.ro 2018 51
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
This Logback configuration file that’s used in production for our application is rolling log files
daily while respecting a few size constraints: each file can be up to 100MB, it keeps 30 days
worth of log files and the total log file is capped at 10GB.
In the application-prod.properties file we specify the log configuration file to be used via the
logging.config property; the classpath: prefix loads the file from the application’s class path.
Metrics
A production level application has to provide operational metrics so that its state can be
analyzed when needed.
Spring Boot Actuator is a module that provides an application with production ready features
such as monitoring, auditing and basic operational management tasks.
management.endpoints.enabled-by-default=false
management.endpoint.health.enabled=true
management.endpoint.metrics.enabled=true
management.endpoints.web.base-path=/mng3
management.server.port=8081
Once you start the application with ./mvnw spring-boot:run -Pdev you can see the list
of metrics available by accessing this URL: http://localhost:8081/mng3/metrics
© coaxial.ro 2018 52
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
To access a single metric use URL http://localhost:8081/mng3/metrics/<metric_name> (e.g.
http://localhost:8081/mng3/metrics/jvm.memory.committed).
Getting metrics one REST call at a time is not very useful; Spring actuator has support for a
Prometheus endpoint. Prometheus is an open source monitoring solution and the Spring
endpoint for it allows easy integration - a Prometheus server can now scrape all metrics from
your application in one go.
© coaxial.ro 2018 53
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
1.073741824E9
jvm_memory_max_bytes{area="heap",id="G1 Eden Space",} -1.0
jvm_memory_max_bytes{area="heap",id="G1 Old Gen",} 4.294967296E9
jvm_memory_max_bytes{area="heap",id="G1 Survivor Space",} -1.0
jvm_memory_max_bytes{area="nonheap",id="CodeHeap 'non-profiled nmethods'",}
1.22912768E8
…
The actuator endpoints are available by default at the /actuator path and on the same port as
the main application (8080). In most production environments access to the management
endpoints should be restricted so that they are only accessible from the same location where
the main application is deployed.
One option of controlling access is to use a separate port number and a custom path for the
actuator endpoints. We can do this by using the following properties in the
application.properties file: management.server.port and
management.endpoints.web.base-path. We changed them so that the management
endpoints are run on port 8081 with path /mng3
© coaxial.ro 2018 54
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Security
Secure access to a REST API is critical for most applications. We build our rentals REST API
for secure consumption by various applications such as a mobile or a SPA web application and
we want different users to have different permissions.
1. Ensure only secure client applications (mobile or SPA) can connect to our REST API
2. Ensure only users registered in our application can connect to our REST API
3. Ensure that certain methods and URLs in our REST API can only be accessed by
certain users
We define three roles for users in our application: ADMIN, CLERK and CLIENT.
A user can have any combination of roles. Roles will dictate the level of access to methods and
URLs in our application.
For testing during development we create 3 users, each with one role associated to it.
OAuth2
We are going to use OAuth2 to control access to our application’s API . OAuth2 is an industry
standard authorization framework. It allows us to ensure that only trusted applications and
registered users can access our REST API.
In order to use OAuth2 in our project we need to understand first a few OAuth2 concepts:
Client - a third party application that is trying to get access to information on the user’s behalf
(usually a part of their account information)
Resource Owner - the end-user who grants or denies the Client access to the account
information
© coaxial.ro 2018 55
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Authorisation Server - this is the server that performs the authorization - it usually displays an
interface asking the Resource Owner (end-user) if they agree to provide the Client with the
requested access to their information. If the user accepts an an access token or authorisation
code is issued that can be used to query the Resource Server
Resource Server - the server that requires user authorisation; requests to the Resource Server
need to contain the access token issued by the Authorisation Server
For our project the OAuth2 roles are preserved but they are organized differently:
© coaxial.ro 2018 56
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Client - this is going to be our own mobile or SPA application that needs to access our Rentals
REST API on behalf of a user
Resource Owner - remains the end-user
Authorisation Server - this is the server that performs the authorization; it will be just a
component inside our own application
Resource Server - this is our Rentals REST API server/application
OAuth2 provides several grant types - for our own project, since the Client is an application that
we control we are going to use Resource Owner Password Credential Grant which allows
obtaining an access token using a username and password.
The Client (our mobile/SPA application) will ask for user (Resource Owner)’s credentials and
forward them to our Rentals REST API application. The Authorisation Server running inside it
takes the credentials, verifies them via Spring Security and generates an access token that is
returned to the Client. The Client then will use this access token when issuing requests against
our Resource Server (the Rentals REST API application).
© coaxial.ro 2018 57
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Note that for the OAuth2 flows to be secure all data exchanges must be done over secured
channels. That means we need to configure HTTPS for our Rentals REST API application.
SSL
We are going to secure the communication channel between the client and our Rentals REST
API only for the production environment.
© coaxial.ro 2018 58
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Start by creating a keystore and a self-signed certificate for the embedded Tomcat server
running in our application. Run the following command from the root of your project and provide
the required details as asked.
Because Tomcat cannot read certificates from the classpath we need to specify a file system
location for it.
© coaxial.ro 2018 59
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
To secure a method use the @PreAuthorize annotation as in the example below. The
@PreAuthorize annotation takes as parameter expressions that allows us to specify the
required authorities or roles that the current user has to have.
We can use either the hasAuthority() or hasRole() expressions. hasRole() has a peculiarity in
the sense that the roles need to have a ROLE_ prefix. That makes it a bit more difficult to use.
For this reason we prefer to use the newer hasAuthority() method.
In our case Spring security authority name is the same as the role name as defined in our
application database. Our course.a1.rentals.model
Role entity implements org.springframework.security.core.GrantedAuthority and it returns
the role name in the getAuthority method.
We allow only users with the ADMIN or CLERK authorities to search for users so the
@PreAuthorize annotation looks like this:
@Transactional
(readOnly=
true
)
@Override
@PreAuthorize
("hasAuthority('ADMIN') or hasAuthority(‘CLERK’)"
)
public
Page
<U
ser
> s
earchUser
(UserSearchFilter
filter
, P
ageablep
ageable
) {
if
(
filter==
null||
filter
.i
sEmpty
()) {
returnt
his
.userRepository
.findAll
(p
ageable
);
}
returnt
his
.userRepository
.findByEmailContainingIgnoreCase
(filter
.g
etEmail
(),
pageable
);
}
© coaxial.ro 2018 60
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Access to the actual REST API URLs is restricted to only authenticated users. A finer level of
control is possible - using user authorities/roles we can further restrict access to portions of our
API.
Implementation
Let’s go now through the implementation details of our OAuth2 based security for the Rentals
API project.
All of our code is located in package course.a1.rentals.security with OAuth2 specific code in
course.a1.rentals.security.oauth2.
We start by adding these two dependencies in our pom.xml file to enable Spring Security and
OAuth2 in our project:
© coaxial.ro 2018 61
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
We then need to write code to configure the OAuth2 authorization and resource servers running
now in our application and to integrate them with Spring Security so that we when we pass in
the user’s credentials via the Resource Owner Password Credential Grant flow the
authorization server can verify them and issue an access token.
RentalsUserDetails
This class extends Spring Security’s UserDetails class. It connects our application’s data and
Spring Security’s so that Spring Security can perform security operations using the user
information provided by our application.
© coaxial.ro 2018 62
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
importj
ava
.u
til
.C
ollection
;
import
org
.springframework
.security
.core
.GrantedAuthority
;
import
org
.springframework
.security
.core
.userdetails
.U
serDetails
;
import
course
.a1
.r
entals
.model
.User
;
// Class needed to implement Spring security. It is a wrapper over
// our own User entity
/**
* Spring security user details implementation.
*/
public
classR
entalsUserDetailsi
mplementsU
serDetails{
privates
tatic
finall
ong
serialVersionUID= 7
083570663075996165L
;
privateU
seru
ser
;
publicR
entalsUserDetails
(User
user
) {
t
his
.u
ser=
user
;
}
@Override
publicC
ollection
<?
extendsG
rantedAuthority
> g
etAuthorities
() {
r
eturnu
ser
.getRoles
();
}
@Override
publicS
tringg
etPassword
() {
r
eturnt
his
.user
.getPassword
();
}
@Override
publicS
tringg
etUsername
() {
r
eturnt
his
.user
.getEmail
();
}
@Override
publicb
oolean
isAccountNonExpired
() {
r
eturnt
rue
;
}
@Override
publicb
oolean
isAccountNonLocked
() {
r
eturnt
rue
;
}
@Override
© coaxial.ro 2018 63
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
publicb
oolean
isCredentialsNonExpired
() {
r
eturnt
rue
;
}
@Override
publicb
oolean
isEnabled
() {
r
eturnt
rue
;
}
}
RentalsUserDetailsService
This is a regular @Service class that implements Spring Security’s
org.springframework.security.core.userdetails.UserDetailsService interface. The role of
this class is to look up a UserDetails instance by username (or, in our case, email address).
/**
* Service class required by Spring security - it reads user details using
* an email address.
*/
@Service
@Transactional
(readOnly=
true
)
public
classR
entalsUserDetailsServicei
mplementsU
serDetailsService{
@Autowired
privateU
serRepositoryu
serRepository
;
@Override
publicU
serDetailsl
oadUserByUsername
(Stringe
mail
)
throws
UsernameNotFoundException{
U
seru
ser=
userRepository
.findByEmailIgnoreCase
(
email
).
orElseThrow
(
() -> n
ewU
sernameNotFoundException
("User not found"
));
/
/ initialize roles - we need to return a fully initialized User instance
u
ser
.g
etRoles
().
size
();
r
eturnn
ew
RentalsUserDetails
(u
ser
);
}
}
© coaxial.ro 2018 64
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
SecurityConfiguration
This class is used to setup Spring Security and integrate it with OAuth2. It configures the
authentication manager bean with the RentalsUserDetailsService so that user information can
be retrieved from the application’s database and it exposes the authentication manager as a
Spring bean so that OAuth2’s authorization server can find it and use for authenticating users
(see AuthorizationServerSecurityConfiguration).
@Configuration
// Spring Security annotation - it allows us to customize web security
@EnableWebSecurity
public
classS
ecurityConfiguration
extendsW
ebSecurityConfigurerAdapter{
@Autowired
privateR
entalsUserDetailsServiceu
serDetailsService
;
/**
* Configures the authentication manager with the user details service.
*/
@Override
protected
void
configure
(A
uthenticationManagerBuildera
uth
) t
hrowsE
xception{
auth
.userDetailsService
(userDetailsService
);
}
/**
* Exposes the authentication manager as a bean to be used by OAuth2. This allows
* OAuth2 to authenticate users when needed using our UserDetailsService.
*/
@Override
@Bean
(name
="authMgr"
)
publicA
uthenticationManagera
uthenticationManagerBean
() t
hrowsE
xception{
r
eturns
uper
.authenticationManagerBean
();
}
}
AuthorizationServerSecurityConfiguration
This class is used to configure OAuth2 authorization server running inside our application.
© coaxial.ro 2018 65
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
The class is annotated with @EnableAuthorizationServer which tells Spring Security OAuth2
to run an authorization server.
The authentication manager bean ("authMgr") created by our SecurityClass is passed to the
configurer for the authorization server. That allows the authorization server to check the validity
of user’s credentials before issuing an access token.
This class also defines the OAuth2 client details - some of the client details are read from the
application.properties file, some are provided directly. More than one client can be registered;
if you have, for instance, a mobile application and a web application and you’d like to configure
each with separate characteristics, such as different access token validity periods, then you can
register a second client with its own details.
In our case we only register one client with the following details:
client-id: rentals-client-id
This is the a client identifier - it can be any string that uniquely identifies a client
client-secret :
$2a$10$7wQl0vta4a5VL7eMSrKXm.LKps0zy/gzyDpZpiG5Lp23iTV.ljj/.
This is a secret associated with this client. In our case the string specified in this property is the
encoded value of the actual secret: ddkl44#SDsfg. The value is encoded using the encoder
specified in RentalsApplication - the startup configuration class for our application
(BCryptPasswordEncoder).
To obtain the encrypted password run the application in the dev profile (./mvnw
spring-boot:run -Pdev); the DevConfiguration class outputs the encoded value for this
secret - just search the output for “Client-secret encoded password”. Note that the
encoded value is different for every run - it does not matter; just pick an encoded value and
update application.properties file with it.
resource-id: rentals-api
The id of the protected resource
access-token-valid-for-seconds: 3600
The number of seconds for which an access token is valid
© coaxial.ro 2018 66
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
@Configuration
@EnableAuthorizationServer
public
classA
uthorizationServerSecurityConfiguratione
xtends
AuthorizationServerConfigurerAdapter{
@Autowired
// Spring annotation specifying the name of the exported authentication
// manager bean instance that we want to inject in this field
@Qualifier
("authMgr"
)
privateA
uthenticationManagera
uthenticationManager
;
// Spring annotation - allows specifying default values for fields;
// in this case we use the ${} notation which means that the default
// value is to be read from the application.properties files
@Value
("${rentals.security.oauth2.client1.client-id}"
)
privateS
tring
clientId
;
@Value
("${rentals.security.oauth2.client1.client-secret}"
)
privateS
tring
clientSecret
;
@Value
("${rentals.security.oauth2.resource-id}"
)
privateS
tring
resourceId
;
@Value
("${rentals.security.oauth2.access-token-valid-for-seconds}"
)
privatei
nta
ccessTokenValidForSeconds
;
@Override
publicv
oidc
onfigure
(AuthorizationServerEndpointsConfigurerc
fg
)
t
hrows
Exception{
/
/ configure the authorization server with the authentication manager
/
/ from Spring Security
c
fg
.a
uthenticationManager
(t
his
.authenticationManager
);
}
@Override
publicv
oidc
onfigure
(ClientDetailsServiceConfigurer
clients
) t
hrows
Exception{
© coaxial.ro 2018 67
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
/
/ configure our client
clients
.i
nMemory
()
.
withClient
(
this
.clientId
)
.
authorizedGrantTypes
("
password"
,
"refresh_token"
)
.
accessTokenValiditySeconds
(
this
.accessTokenValidForSeconds
)
.
secret
(t
his
.clientSecret
)
.
scopes
("
read"
, "
write"
)
.
resourceIds
(t
his
.resourceId
);
}
}
ResourceServerSecurityConfiguration
This class enables and configures the OAuth2 resource server.
The resource server is configured with the resource-id value from the application.properties
file - it must be the same value used when configuring the client in
AuthorizationServerSecurityConfiguration.
When Spring Security OAuth2 sees this annotation it installs a filter that authenticates requests
based on OAuth2 tokens - the actual requests to be authenticated are specified in the
configure(HttpSecurity) method. We only authenticate / api/rentals/** U RLs and leave free
for access the metrics end point, the Swagger URL for API documentation and the H2 console
that is used during development.
@Configuration
// Enable OAuth2 resource server
@EnableResourceServer
public
classR
esourceServerSecurityConfiguratione
xtends
ResourceServerConfigurerAdapter{
// set the resource id from application properties
@Value
("${rentals.security.oauth2.resource-id}"
)
privateS
tring
resourceId
;
@Override
publicv
oidc
onfigure
(HttpSecurityh
ttp
) t
hrows
Exception{
© coaxial.ro 2018 68
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
h
ttp
/
/ disable csrf protection as our REST API should be safe against
// it
.
csrf
().
disable
()
/
/ no HTTP session necessary
.
sessionManagement
().
sessionCreationPolicy
(
SessionCreationPolicy
.S
TATELESS
).
and
()
/
/ secure requests
.
authorizeRequests
()
/
/ allow access to the management API - this is running on a
// separate port and can be protected by disallowing network
// access to it allow access to the api docs and to the h2
// console (available only in dev profile)
.
antMatchers
("
/mng3/**"
,
"/v2/api-docs/**"
,
"/h2-console/**"
).
permitAll
()
// require secure access to the API
.
antMatchers
("
/api/rentals/**").
authenticated
();
/
/ required to get H2 consoles to work
h
ttp
.h
eaders
().
frameOptions
().
disable
();
}
@Override
publicv
oidc
onfigure
(ResourceServerSecurityConfigurerr
esources
)
throwsE
xception{
/
/ provide the resource id
r
esources
.r
esourceId
(this
.resourceId
);
}
}
© coaxial.ro 2018 69
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
package
course.a1.rentals.repository.converters
;
import
java.sql.Timestamp
;
import
java.time.LocalDateTime
;
import
javax.persistence.AttributeConverter
;
import
javax.persistence.Converter
;
/**
* JPA support for new date/time API in Java8
*/
// This JPA Converter annotation marks this class as a converter.
// The autoPlay set to true ensures that the persistence provider
// applies this converter for all entities and all fields
// of the provided types.
@Converter
(autoApply
=t
rue)
public
classL
ocalDateTimeAttributeConverteri
mplements
AttributeConverter
<LocalDateTime
,Timestamp
>{
@Override
publicTimestamp c
onvertToDatabaseColumn
(L
ocalDateTime locDateTime
)
{
return(
locDateTime =
=n
ull?
null
:Timestamp
.valueOf
(l
ocDateTime
));
}
@Override
publicLocalDateTime
convertToEntityAttribute
(T
imestamp sqlTimestamp
){
return(
sqlTimestamp =
=
null?
n
ull:
sqlTimestamp
.toLocalDateTime
());
}
}
© coaxial.ro 2018 70
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
● RentalApplication - responsible for starting up the application and creating other beans
required by the application
● JacksonConfiguration - used for customizing our JSON serialization mechanism
● SwaggerConfiguration - used for setting up the beans responsible for generating the
documentation for our REST API
● DevConfiguration - used for setting up the database during development and for
integration testing
● SecurityConfiguration - used for setting up Spring security. This class is in package
course.a1.rentals.security
It is helpful, in general, to use a separate configuration class for each type of functionality being
configured, as opposed to using a monolithic configuration class.
That allows us to use a subset of these configuration classes for different contexts (e.g. during
development or when running different types of tests)
RentalApplication
Our application startup class is RentalApplication.java.
A simple Java class with a couple of annotations is all it takes to bootstrap our application.
Because we have a dependency in our pom.xml file on spring-boot-starter-web the application
starts an embedded web server. The application is packaged in a self sufficient jar file so the
application can be run as a regular java application (java -jar
rentals-0.0.1-SNAPSHOT.jar). During development the application is started using
maven (./mvnw spring-boot:run -Pdev), specifying the dev Maven profile.
@SpringBootApplication - this Spring annotation marks this class as the entry point for
our application
@EnableCaching - Spring annotation - it enables annotation based caching (it detects
the use of @Cacheable annotations)
@EnableGlobalMethodSecurity(prePostEnabled = true) - Spring annotation - it allows us to
specify method level security annotation
@SpringBootApplication
@EnableCaching
@EnableGlobalMethodSecurity
(p
rePostEnabled= t
rue
)
© coaxial.ro 2018 71
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
public
classR
entalsApplication{
// Main method that starts up the application...
publics
taticv
oidm
ain
(String
[]
args
) {
// ...using the Spring provided utility method
S
pringApplication
.run
(RentalsApplication
.c
lass
,
args
);
}
// Class used for encoding user passwords
@Bean
publicP
asswordEncoderp
asswordEncoder
() {
returnn
ew
BCryptPasswordEncoder
();
}
}
This class also creates the bean used for encoding user passwords in the application.
JacksonConfiguration
This class is used for customizing our JSON serialization mechanism provided by the Jackson
library. Some Jackson configuration properties can be tweaked using the standard Spring Boot
mechanism for third party libraries - using spring.jackson.*
properties defined in the
application*.properties file. However not all Jackson configuration parameters are available
via this mechanism - for maximum flexibility we need create a customized
com.fasterxml.jackson.databind.ObjectMapper bean - and this bean will be used by Spring
for JSON serialization operations.
© coaxial.ro 2018 72
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
/
/ make Jackson aware of new Java time classes (e.g. LocalDateTime)
m
apper
.registerModule
(n
ewJ
avaTimeModule
());
/
/ make Jackson aware of Hibernate proxies - we want lazy initialization
/
/ proxies loaded before the object is serialized
m
apper
.registerModule
(n
ewH
ibernate5Module
().
configure
(
H
ibernate5Module
.Feature
.F
ORCE_LAZY_LOADING
,
true
));
/
/ represent dates in ISO8601 format (e.g. 2018-07-02T10:49:39.884398)
m
apper
.setDateFormat
(n
ewS
tdDateFormat
());
r
eturn
mapper
;
}
}
SwaggerConfiguration
This class is used for setting up the bean responsible for generating our API documentation.
The class is also marked with the @Profile annotation to only be active during the dev and
prod profiles. That means that the Swagger UI is not available for instance for integration tests.
Spring’s @Profile annotation can be used on any @Configuration class to specify the profiles
for which the configuration class is loaded - that makes it easy to use slightly different versions
of the application for each profile as needed.
© coaxial.ro 2018 73
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
DevConfiguration
This class is used for setting up the data to be used during development. The same data is used
in our case for running the integration tests.
© coaxial.ro 2018 74
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
@
SuppressWarnings
("unused"
)
U
sera
dminUser= u
serService
.addUser
(
new U
ser
("[email protected]"
, "
pass"
,
adminRole
,
clerkRole
,
clientRole
));
U
serc
lerkUser= u
serService
.addUser
(
newU
ser
("[email protected]"
,
"pass"
,
c
lerkRole
));
U
serc
lientUser=
userService
.a
ddUser
(
newU
ser
("[email protected]"
, "
pass"
,
c
lientRole
));
/
/ add application configuration data - asset prices
a
ssetPriceRepo
.save
(newA
ssetPrice
(Asset
.Type
.NEW
,
BigDecimal
.valueOf
(10
), 0
));
a
ssetPriceRepo
.save
(newA
ssetPrice
(Asset
.Type
.REGULAR
,
BigDecimal
.valueOf
(5)
, 3
)
);
a
ssetPriceRepo
.save
(newA
ssetPrice
(Asset
.Type
.OLD
,
BigDecimal
.valueOf
(5)
, 5
)
);
/
/ add a few assets to the database (films in this case)
A
ssetm
atrix11= a
ssetRepo
.save
(newA
sset
("Matrix 11"
,
Asset
.Type
.NEW
));
A
ssets
piderMan=
assetRepo
.save
(n
ewA
sset
("Spider Man"
,
Asset
.
Type
.REGULAR
));
A
ssets
piderMan2=
assetRepo
.save
(newA
sset
("Spider Man 2"
,
Asset
.
Type
.REGULAR
));
A
sseto
utOfAfrica=
assetRepo
.save
(newA
sset
("Out of Africa"
,
Asset
.
Type
.OLD
));
...
SecurityConfiguration
This class configures Spring Security and links it with OAuth2. See the SecurityConfiguration
section in Security for more details.
Application Configuration
The application configuration refers to the set of parameters that drive our application from a
business perspective.
© coaxial.ro 2018 75
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Spring provides facilities to manage the business level application configuration. We create a
class AppConfiguration in package course.a1.rentals.config and annotate it with Spring
annotations @Component, @ConfigurationProperties and @PropertySource.
© coaxial.ro 2018 76
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
r
eturnc
urrency
;
}
// note that setters are required to allow Spring to set the values based
// on the specified source (application-configuration.properties in our
// case)
publicv
oids
etUserBonusPointsForAnyRentals
(intu
serBonusPointsForAnyRentals
) {
t
his
.u
serBonusPointsForAnyRentals= u
serBonusPointsForAnyRentals
;
}
publicv
oids
etUserBonusPointsForNewRentals
(intu
serBonusPointsForNewRentals
) {
t
his
.u
serBonusPointsForNewRentals= u
serBonusPointsForNewRentals
;
}
publicv
oids
etCurrency
(S
tring
curr
) {
t
his
.c
urrency=
curr
;
}
}
@Service
@Transactional
public
classR
entalServiceImpli
mplementsR
entalService{
... code excluded for readability ...
@Autowired
privateA
ppConfigurationa
ppConfig
;
... code excluded for readability ...
if
(
rentedFilmType==
Asset
.Type
.NEW
) {
b
onusPoints+=
this
.appConfig
.getUserBonusPointsForNewRentals
();
} e
lse{
b
onusPoints+=
this
.appConfig
.getUserBonusPointsForAnyRentals
();
}
© coaxial.ro 2018 77
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Note that we used the optional prefix attribute on the @ConfigurationProperties annotation so
that our properties names can start with the rentals prefix in the
application-configuration.properties file. That allows you to define a clear naming convention
for your properties which is especially useful when you have multiple application configuration
classes (e.g. one per application module - for larger applications).
JPA Entities
The Java classes for our data model are build using standard/lightweight Java classes marked
with annotations defined in Java Persistence API (Keep this link handy for reference:
https://docs.oracle.com/javaee/7/api/toc.htm).
A couple of java classes for entities (Asset.java, AssetItem.java) are heavily commented to
explain all the commonly used annotations used. Other entities have comments like this for
certain sections not covered by these two classes.
© coaxial.ro 2018 78
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Handling correctly entity associations in JPA is not an easy skill to master. When dealing with
entity associations I prefer to keep things as simple as possible by having the associations
handled only by the child entity. For instance the Asset entity does not keep a list of AssetItem
instances and it does not list this association using a @OneToMany JPA annotation. I prefer to
have the child entity declare the association with a @ManyToOne annotation.
That means that the persistence of child entities (AssetItem) needs to be handled explicitly.
This approach is fine for most cases and it’s the preferred option for handling associations
where there can be a very large number of child entities for a given parent.
Let’s have a closer look at just two of the entities in our model Asset and A
ssetItem.
Asset.java
© coaxial.ro 2018 79
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
// sequences.
@GeneratedValue
privateI
ntegeri
d
;
// JPA annotation that provides information about this column; in this
// case we want a maximum length of 100 characters and we do not allow
// null values
@Column
(l
ength
=100
,
nullable=
false
)
// These are are a set of validation annotations defined in Java Bean
// Validation API (JSR-380). These annotations are used by Spring Boot to
// validate objects
// The size of this string field is between 1 and 100...
@Size
(min
=1,
m
ax
=100
)
// ...and it cannot be null
@NotNull
privateS
tring
name
;
// JPA annotation that marks this field as an enumeration; the
// persistence engine will store the values for this field as the string
// values (that is what EnumType.STRING does) of the underlying
// enumeration(Type in this case)
@Enumerated
(EnumType
.STRING
)
// JPA annotation to mark this field as non null
@Column
(n
ullable= f
alse
)
// Java Bean Validation API annotation to mark this field as non null
@NotNull
privateT
ypet
ype
;
// An empty constructor is required by the persistence engine
// (Hibernate, the JPA provider used by Spring in this project)
publicA
sset
() {
s
uper
();
}
publicA
sset
(String
name
,
Type
type
) {
s
uper
();
t
his
.n
ame=
name
;
© coaxial.ro 2018 80
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
t
his
.t
ype=
type
;
}
publicA
sset
(Integeri
d
) {
s
uper
();
t
his
.i
d= i
d
;
}
... getters and setters excluded for brevity ...
}
Associations
As mentioned in the earlier paragraph on handling association you notice that there’s no
@OneToMany association defined in this parent entity.
Primary Key
As mentioned in the comment for the @GeneratedValue annotation we are using the default
mechanism for generating the primary keys which in our case (Hibernate + MySQL) results in a
sequence being used.
When you start the application (./mvnw spring-boot:run) you can see in the logs the
following fragment with DDL for creating the hibernate_sequence table which will be used by
Hibernate for generating unique values for the primary keys.
...other DDL
© coaxial.ro 2018 81
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Validation
For validation of entity fields we use two type of annotations that handle different aspects: JPA
annotations that provide constraints for data persistence (e.g. @Column(length=100,nullable =
false) and a set of validation rules that apply at various application layers in the application.
)
For the latter see the Validation section.
Note that not using a validation group for the @NotNull annotation we specify that this field
should be not null regardless of the validation group provided in the @Validated annotation (i.e.
in all circumstances).
AssetItem.java
© coaxial.ro 2018 82
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
@Column
(n
ullable= f
alse
)
// Java Bean Validation API annotation to mark this field as non null
// for the persistence layer - what this means is that when an instance
// of this class is validated for the Persistence group an error will be
// thrown if this value is null
@NotNull
(g
roups
={
Persistence
.c
lass
})
privateS
tates
tate
;
// JPA annotation mapping timeAdded Java field to time_added database
// column and marking this column as non null
@Column
(n
ame
="time_added"
,
nullable
=false
)
// Java Bean Validation API annotation to mark this field as non null
// for the persistence layer - what this means is that when an instance
// of this class is validated for the Persistence group an error will be
// thrown if this value is null
@NotNull
(g
roups
={
Persistence
.c
lass
})
privateL
ocalDateTimet
imeAdded
;
// JPA annotation that establishes a relationship between this entity and
// the Asset entity. In this case there are many AssetItem instances for
// every Asset
// FetchType.LAZY instructs the persistence provider to load the Asset
// for an AssetItem lazily
@ManyToOne
(optional=
false
, f
etch=
FetchType
.LAZY
)
// JPA annotation that specify the column in the AssetItem table to be
// used for this association
@JoinColumn
(name= "
asset_id"
)
// Java Bean Validation API annotation to mark this field as non null
@NotNull
privateA
sseta
sset
;
/**
* Keep track of number of times each item was rented.
* This allows for optimal wear and tear of stocked items.
*/
@Column
(n
ame
="rental_count"
, n
ullable
=false
)
@NotNull
privateL
ongr
entalCount
;
© coaxial.ro 2018 83
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
// An empty constructor is required by the persistence engine
// (Hibernate, the JPA provider used by Spring in this project)
publicA
ssetItem
() {
s
uper
();
}
publicA
ssetItem
(inti
d
) {
s
uper
();
t
his
.i
d= i
d
;
}
publicA
ssetItem
(Asseta
sset
) {
s
uper
();
t
his
.a
sset=
asset
;
}
publicA
ssetItem
(Asseta
sset
, A
ssetItem
.States
tate
) {
s
uper
();
t
his
.a
sset=
asset
;
t
his
.s
tate=
state
;
}
// JPA annotation - the methods with this annotation are invoked
// just before the instance of this entity is persisted
// In this case we want to set some default values for fields when
// the instance is persisted
@PrePersist
protected
void
onCreate
() {
i
f
(this
.state==
null
) {
t
his
.state=
State
.AVAILABLE
;
}
i
f
(this
.timeAdded== n
ull
) {
t
his
.timeAdded= L
ocalDateTime
.now
();
}
i
f
(this
.rentalCount==
null
) {
t
his
.rentalCount=
0L
;
}
}
... getters and setters excluded for brevity ...
}
© coaxial.ro 2018 84
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Associations
The entity association between AssetItem and Asset is maintain by AssetItem - which is the
child entity.
The association is represented by the asset field and the @ManyToOne JPA annotation. This
annotation states that:
● the field is not optional (so every AssetItem has an Asset parent entity associated with
it)
● the fetch strategy is LAZY, which means that the Asset instance is not eagerly read from
the database at the same time as the AssetItem instance
● the column asset_id in the AssetItem table maintains the association between these
two entities
Validation
In this entity class we have two fields that must be not null when validated against the
Persistence validation group. While we do not explicitly validate instances for this validation
group, specifying it in the validation annotation allows us to ignore this constraint when
validating against the Rest validation group. That means that when AssetItem instances are
validated in the Rest controller using @Validated(Rest.class) the objects will pass validation as
we expect these fields to be empty at that point in time.
These three fields that cannot be null (state, rentalCount and timeAdded) are provided the
default values in the AssetItem entity just before the instance is persisted to the database. We
do this by using the JPA @PrePersisted annotation which is invoked when an entity is
inserted/persisted into the database.
While creating DTO objects just to aggregate request parameters might not be a good idea in
general, when using Spring Boot Rest controllers it makes life easier so we are doing this in our
project.
© coaxial.ro 2018 85
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
AddRentalItem - request parameter aggregator to hold all parameters required for adding a
rental item
AssetItemInventory - holds calculated data on an asset item inventory
AssetItemSearchFilter - defines a filter used for searching asset items
RentalCost - holds calculated rental cost details
…
Repositories
Reading and writing data from the database is implemented in our project using Spring Data
JPA. This project removes the need for boilerplate code and provides general facilities needed
when working with data (pagination, auditing, type safe queries, validation of queries, etc).
This section in the Maven’s project file lists Spring Data JPA as a dependency for our project:
Repositories are declared as interfaces annotated with @Repository and are located in the
course.a1.rentals.repository package. They also extend Spring’s JpaRepository interface.
Without any of your own method declarations it provides out of the box the ability to do CRUD
(Create, Read, Update, Delete) operations, it has support for pagination and sorting and for
searching for entities using examples.
Spring Data allows you to define more complex read operations by using method names - it is
able to derive the actual query from the method name; this mechanism supports a large
combination of properties, logical operands and SQL clauses - Spring Data JPA documentation
is very useful and it provides plenty of examples (see, in particular, section 2.4.2. Query
creation):
https://docs.spring.io/spring-data/data-commons/docs/2.0.6.RELEASE/reference/html/.
Let’s have a look at the code of one of our repository interfaces, AssetItemRepository.
© coaxial.ro 2018 86
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
© coaxial.ro 2018 87
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
List
<AssetItem
> l
st= f
indAllByAssetInStateAndLock
(asset
,
state
,
PageRequest
.o
f
(0,
1
));
if
(CollectionUtils
.
isEmpty
(l
st
)) {
returnO
ptional
.empty
();
}
returnO
ptional
.of
(lst
.g
et
(0)
);
}
}
Components
Components are classes marked with Spring’s @Component annotation and they are used
within a single application layer.
In our application we have a couple of components in the service layer:
● course.a1.rentals.component.AssetPriceComponentImpl - that is responsible for
providing asset prices. Because asset prices do not change often we also cache the
result of the getAssetPrice method in the assetPrice cache region. When a price
changes we can invoke the refreshAssetPrice method and that will refresh the cache
entry.
● course.a1.rentals.component.RentalsUtils - utilities used in the project
Note that rather than auto-wiring fields we define a constructor to initialize these objects. This
pure Java approach is recommended as it allows us to mark the fields final and it makes it
easier to provide instances of these classes for testing (there is no need to provide a Spring
configuration class to instantiate them).
@Component - Spring annotation - a generic marker for Spring managed classes; it marks this
class as a candidate for auto-detection of managed classes
@Cacheable("assetPrice") - Spring annotation that causes the result of the annotated method
to be cached in a cache with name “assetPrice”
@CachePut("assetPrice") - Spring annotation that causes the respective cache entry to be
replaced by the result of the annotated method
© coaxial.ro 2018 88
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Services
Services are classes handling the business/application logic. Services make use of Spring Boot
repository classes and component classes. These classes are invoked by the Rest controllers
via a clearly defined interface.
© coaxial.ro 2018 89
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
A service class is marked with Spring’s @Service annotation. In most cases service classes
operate in a transactional context so they are often marked with the @Transactional
annotation.
@Service - Spring annotation marks this class as a service. A service class holds
business logic and exposes an interface to other application layers
@Transactional - Spring annotation marks this class as providing methods that should
be executed in the context of a transaction (a transaction is started and provided to the
persistence provider every time a method of this class is executed; if an exception is
thrown the transaction will be rolled back)
Methods in this class are usually marked with annotations to override the transactional
characteristics (@Transactional) or to specify what roles are allowed to execute them
(@PreAuthorize).
// This Spring annotation marks this class as a service. A service class holds
// business logic and exposes an interface to other application layers
@Service
// This Spring annotation marks this class as providing methods that should
// be executed in the context of a transaction (a transaction is started and
// provided to the persistence provider every time a method of this class is
// executed; if an exception is thrown the transaction will be rolled back)
© coaxial.ro 2018 90
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
@Transactional
public
classA
ssetInventoryServiceImpli
mplementsA
ssetInventoryService{
privatef
inalA
ssetRepositorya
ssetRepository
;
privatef
inalA
ssetItemRepositorya
ssetItemRepository
;
// Constructor used by Spring to wire dependent object instances
publicA
ssetInventoryServiceImpl
(AssetRepositorya
ssetRepository
,
A
ssetItemRepositorya
ssetItemRepository
) {
s
uper
();
t
his
.a
ssetRepository=
assetRepository
;
t
his
.a
ssetItemRepository=
assetItemRepository
;
}
@Override
// We override here the default Transactional annotation (at the class
// level) to instruct Spring to execute this method in a read only
// transaction. Read only transactions can be used for processing that do
// not change the underlying persistence store; optimizations
// can be made for read only transactions so use them whenever possible.
@Transactional
(readOnly=
true
)
// Only users with ADMIN and CLERK roles have access to this method
@PreAuthorize
("
hasAuthority('ADMIN') or hasAuthority('CLERK') "
)
publicP
age
<Asset
> s
earchAssets
(AssetSearchFilterf
ilter
,
Pageablep
ageable
) {
... code excluded for brevity ...
}
@Override
@PreAuthorize
("
hasAuthority('ADMIN')"
)
publicA
sseta
ddAsset
(Assetf
ilm
) {
r
eturna
ssetRepository
.save
(film
);
}
@Override
@PreAuthorize
("
hasAuthority('ADMIN') or hasAuthority('CLERK') "
)
publicA
ssetu
pdateAsset
(A
sset
film
) {
r
eturna
ssetRepository
.save
(film
);
}
© coaxial.ro 2018 91
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
@Override
@PreAuthorize
("
hasAuthority('ADMIN')"
)
publicv
oidr
emoveAsset
(A
ssetf
ilm
) {
t
his
.a
ssetRepository
.delete
(film
);
}
@Override
@Transactional
(readOnly=
true
)
@PreAuthorize
("
hasAuthority('ADMIN') or hasAuthority('CLERK') "
)
publicP
age
<AssetItem
> s
earchAssetItems
(A
ssetItemSearchFilterf
ilter
,
Pageable
pageable
) {
... code excluded for brevity ...
}
@Override
@PreAuthorize
("
hasAuthority('ADMIN')"
)
publicA
ssetItema
ddAssetItem
(A
ssetItema
ssetItem
) {
r
eturna
ssetItemRepository
.save
(assetItem
);
}
@Override
@PreAuthorize
("
hasAuthority('ADMIN')"
)
publicA
ssetItemu
pdateAssetItem
(AssetItema
ssetItem
) {
r
eturna
ssetItemRepository
.save
(assetItem
);
}
@Override
@PreAuthorize
("
hasAuthority('ADMIN')"
)
publicv
oidr
emoveAssetItem
(AssetItema
ssetItem
) {
a
ssetItemRepository
.d
elete
(assetItem
);
}
@Override
@PreAuthorize
("
hasAuthority('ADMIN') or hasAuthority('CLERK') "
)
publicA
ssetg
etAsset
(Asseta
sset
) {
A
sset
ret=
assetRepository
.getOne
(a
sset
.getId
());
H
ibernate
.i
nitialize
(ret
);
r
eturnr
et
;
© coaxial.ro 2018 92
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
}
@Override
@PreAuthorize
("
hasAuthority('ADMIN') or hasAuthority('CLERK') "
)
publicA
ssetItemg
etAssetItem
(A
ssetItema
ssetItem
) {
A
ssetItemr
et=
assetItemRepository
.g
etOne
(assetItem
.g
etId
());
r
et
.s
etAsset
(newA
sset
(ret
.getAsset
().
getId
()));
H
ibernate
.i
nitialize
(ret
);
r
eturnr
et
;
}
@Override
@PreAuthorize
("
hasAuthority('ADMIN') or hasAuthority('CLERK') "
)
publicA
ssetItemInventory
calculateAssetItemInventory
(A
sseta
sset
) {
M
ap
<A
ssetItem
.State
, L
ong
>
inv=
newH
ashMap
<AssetItem
.State
, L
ong
>();
f
or
(A
ssetItem
.States
tate:
AssetItem
.State
.values
()) {
i
nv
.put
(state
, a
ssetItemRepository
.
countByAssetAndState
(asset
, s
tate
));
}
r
eturnn
ew
AssetItemInventory
(i
nv
);
}
}
© coaxial.ro 2018 93
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
REST Principles
The most important characteristics of a RESTful API are the following:
● Uniformity - all resources are interacted with in the same way (e.g using URLs to
identify resources and acting on them using HTTP verbs - mainly GET, PUT, POST,
DELETE)
● Lack of state - no state should be maintained between requests
● Cacheability - responses should be cacheable using the existing HTTP caching
infrastructure
● Layering - communication uses existing standards and messages are self-descriptive;
this allows any number of layers/intermediaries between the client and the server to
access and enhance the message exchange (e.g caching servers, security servers)
For our own Rentals API we are not going to implement full HATEOAS - the API is built to be
used by a custom mobile or web application, in this case the HATEOAS principles are not an
absolute requirement.
Best Practices
In terms of implementing our RESTful API we are going to respect the following common best
practices:
● URLs identifying resources to act upon contain plural nouns - the nouns are names of
the resources and subresources (e.g. /api/rentals/assets/12/items/10)
● Use kebab-case (dash separated) naming conventions for all the elements in our URL
path (e.g. it-looks-like-this)
● The basic operation to be performed on the resource is transmitted via HTTP verbs
(GET, POST, PUT, DELETE, etc). The usage of these HTTP verbs follow, in general,
these rules:
○ GET - used for reading resources
■ returns an HTTP status code of 200 most of the time (other status codes
are also acceptable, such as 204 (No Content))
© coaxial.ro 2018 94
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
■
must be idempotent (it leaves the system in the same state if invoked
multiple times)
○ POST - for creating a resource
■ returns an HTTP status code of 201 (Created) with the Location header
pointing to the newly created resource; if the server defers the creation of
the resource, the response should be 202 (Accepted)
■ when used for actions (e.g. search) a POST request can return a status
code of 200
○ PUT - for updating a resource
■ returns an HTTP status code of 204 (No Content) most of the time;
sometimes if the resource was updated in a significant way it can return a
200 status code with body
■ must be idempotent
○ DELETE - for deleting a resource
■ returns an HTTP status code of 204 (No Content) most of the time
■ must be idempotent
● Use ISO 8601 date times in our JSON documents
If you are developing a secured RESTful API (like Rentals API) you should also be familiar with
these two HTTP response status codes - and the difference between them:
GET
/api/rentals/users?search=%7B%22email%22%3A%22admin%40rentals.io%22%7
D%0A&page=2&size=5
The advantage of using a GET request is that the URL is bookmarkable and easily cacheable.
© coaxial.ro 2018 95
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Downsides are possible limits set in the server on the length of a URL and the relative difficulty
in working with URL parameters during development and testing.
A different approach, used in our Rentals API is to use POST requests, appending the search
token to the URL path identifying the resources to be searched, passing the search filter as a
JSON document in the body of the request and the pagination information as URL parameters.
POST /api/rentals/users/search?page=0&size=2
{
"email":"[email protected]"
}
The downside of this approach is that the search queries are not bookmarkable and the
requests are more difficult to cache (since POST requests are not considered to be idempotent,
they are not automatically cached by all layers in the communication channel).
When designing your own API choose the option that makes most sense in your particular case.
In the case of our Rentals API, considering the client is a custom mobile or web application the
downsides of the POST approach are not very important.
● Users
○ GET /api/rentals/users/{id} - gets user with id {id}, returns 200 with body
○ POST /api/rentals/users - creates a new user, returns 201 Created
○ POST /api/rentals/users/search - searches for users, returns 200 with body
○ DELETE /api/rentals/users/{id} - deletes user with id {id}, returns 204 No
Content
○ PUT /api/rentals/users/{id} - updates user with id {id}, returns 204 No Content
○ GET /api/rentals/users/{id}/bonus-points - gets bonus points for user with id
{id}
○ GET /api/rentals/users/{id}/bonus-points-total - gets the total number of bonus
points for user with id {id}
● Assets
○ GET /api/rentals/assets/{id} - gets asset with id {id}, returns 200 with body
○ POST /api/rentals/assets - creates a new asset, returns 201 Created
○ POST /api/rentals/assets/search - searches assets, returns 200 with body
○ DELETE /api/rentals/assets/{id} - deletes asset with id {id}, returns 204 No
Content
© coaxial.ro 2018 96
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
As you might have noticed when we are dealing with sub-resources (many to one relationships
in database terms) we chose to build URLs for them to reflect the hierarchy (e.g
/api/rentals/rentals/12/items/10). An alternative approach would have been to treat them as
standalone resources (e.g. /api/rentals/rental-items/10).
© coaxial.ro 2018 97
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
Versioning
Versioning is the ability to support multiple versions of an API and it is a very important aspect
of any API design, including RESTful APIs.
There are a few ways to handle versioning for a RESTful API:
● Use a different URL path for different API versions (e.g. /api/v1/rentals/users/search)
● Specify the version information in the HTTP request header and route the request to the
correct API server based on this value
● Use DNS/a separate domain name for each version (e.g. v1.rentals-api.io)
The last option (DNS based) seems to be the easiest, least obtrusive approach.
Rest Controllers
Rest controllers are classes responsible for implementing your API endpoints and exchanging
data between the client (JSON) and the business services (Java).
The only required annotation for a REST controller class is @RestController; other common
annotations are listed below:
@RestController - Spring annotation that marks this class as a REST controller; amongst other
things Spring knows to bind the return value from methods to the body of the HTTP response
@RequestMapping - Spring annotation used to provide information on how to handle HTTP
requests. We use it to set up the root URL path for all methods of this class (e.g.
/api/rentals/assets ) and to specify that our endpoints produce and consume JSON content
@Api - Swagger annotation; it marks all public methods of this class as being part of the REST
API. It’s used to provide information on the REST API documentation generator (Swagger)
All public methods in a REST controller that define an API endpoint should have the
@ApiOperation Swagger annotation to provide information for the documentation generator
about the respective endpoint.
@Api
(
consumes=
MediaType
.APPLICATION_JSON_VALUE
,
produces=
MediaType
.APPLICATION_JSON_VALUE
)
@RestController
@RequestMapping
(
p
ath=
"/api/rentals/assets"
,
© coaxial.ro 2018 98
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
c
onsumes=
MediaType
.A
PPLICATION_JSON_VALUE
,
p
roduces=
MediaType
.A
PPLICATION_JSON_VALUE
)
public
classA
ssetInventoryRestController{
...
Let’s look in more detail into each type of methods a REST controller usually needs to define.
The Spring @RequestMapping annotation applied at method level allows the mapping of this
method to a POST HTTP method. The path attribute specifies the URL mapped to this method
relative to the value specified in the @RequestMapping annotation at the class level.
In this case it is empty since we are using the same URL path as the one specified at class level
("/api/rentals/assets").
This addAsset method has a parameter that takes an entity instance and is marked with two
annotations:
● @RequestBody - specifies that the Asset object should be read from the JSON
document in the body of the HTTP requests
● @Validated(Rest.class) - validates the Asset instance based on the rules defined in the
Rest validation group
For Rentals API we chose to return just the location of the newly created resource rather then
the resource itself. For this reason the return type of the method is ResponseEntity
<
Void>
.
The actual value returned is build using:
ResponseEntity
<V
oid
> r
et = ResponseEntity
.
created
(
location
).
build
();
This will translate into an HTTP response with a status code of 201 Created, with no body and a
Location HTTP header containing the location of the newly created resource. The location is
calculated, relative to the current URL path using the following code:
URIl
ocation= S
ervletUriComponentsBuilder
.
fromCurrentRequest
()
.
path
(
"/{id}"
)
© coaxial.ro 2018 99
© coaxial.ro 2018 - Building RESTful APIs with Spring Boot
.
buildAndExpand
(
addedAsset
.
getId
()).
toUri
();
@ApiOperation
(v
alue=
"Adds a new asset"
)
@RequestMapping
(path= "
"
,
method=
RequestMethod
.P
OST
)
publicR
esponseEntity
<Void
> a
ddAsset
(
@
Validated
(Rest
.class
)
@RequestBodyA
sseta
sset
) {
A
sset
addedAsset= a
ssetInventoryService
.a
ddAsset
(asset
);
U
RIl
ocation= S
ervletUriComponentsBuilder
.
fromCurrentRequest
().
path
("/{id}"
)
.
buildAndExpand
(addedAsset
.getId
()).
toUri
();
R
esponseEntity
<V
oid
> r
et=
ResponseEntity
.c
reated
(location
).
build
();
r
eturnr
et
;
}
POST http://localhost:8080/api/rentals/rentals/25/items/
{
"timeExpectedReturn": "2018-07-06T16:50:23",
"asset": {
"id": 11
}
}
A POST request for search is usually mapped to a method that uses Spring’s paging facilities -
it takes as a parameter a Pageable instance specifying the page to be returned and it returns a
Page).
Spring handles the conversion of paging information which is passed as URL parameters (e.g.
page=0&size=5) into a Pageable instance.
/
/ POST /api/rentals/assets/search
@ApiOperation
(v
alue=
"Searches assets and returns paginated results"
)
@RequestMapping
(path= "
search"
, m
ethod=
RequestMethod
.P
OST
)
publicP
age
<Asset
> s
earchAssets
(
@
RequestBodyA
ssetSearchFilterf
ilter
,
Pageablep
ageable
) {
P
age
<A
sset
>
page= a
ssetInventoryService
.s
earchAssets
(filter
, p
ageable
);
r
eturnp
age
;
}
POST http://localhost:8080/api/rentals/assets/search?page=0&size=5
{
"types": [ "NEW" ],
"withItemsInState": [ "AVAILABLE" ]
}
The example below shows the method that calculates the cost for a rental.
In this method we extract the id of the rental for which the cost must be calculated from the URL
using the path attribute of @RequestMapping and the @PathVariable annotation of the id
method parameter.
This method also takes an optional argument from the request body - the CalculateCost DTO
instance that contains information on how to calculate the rental’s cost.
The fact that the request body is optional is marked by the required attribute of the
@RequestBody annotation that is set to false.
// POST /api/rentals/rentals/{id}/cost
@RequestMapping
(path= "
{id}/cost"
,
method= R
equestMethod
.POST
)
publicR
entalCostg
etRentalCost
(@PathVariable
("
id"
)
Integeri
d
,
@RequestBody
(r
equired
=false
) C
alculateCostc
c
) {
r
eturnt
his
.rentalService
.g
etRentalCost
(newR
ental
(i
d
),
c
c==
null? n
ull:
cc
.g
etDayOfCalculation
());
}
// DELETE /api/rentals/rentals/{id}
@RequestMapping
(path= "
{id}"
,
method= R
equestMethod
.DELETE
)
publicR
esponseEntity
<Void
> c
ancelOrCloseRental
(@PathVariable
("
id"
)
Integeri
d
) {
t
his
.r
entalService
.cancelOrCloseRental
(new
Rental
(i
d
));
r
eturnR
esponseEntity
.noContent
().
build
();
}
// PUT /api/rentals/assets/{assetId}/items/{id}
@ApiOperation
(v
alue=
"Updates the asset item with the given id"
)
@RequestMapping
(p
ath= "
{assetId}/items/{id}"
,
method=
RequestMethod
.P
UT
)
publicR
esponseEntity
<Void
> u
pdateAssetItem
(
@
PathVariable
("assetId"
) I
ntegera
ssetId
,
@
PathVariable
("id"
) I
nteger
id
,
@
Validated
(Rest
.class
)
@RequestBodyA
ssetItema
ssetItem
) {
a
ssetItem
.s
etId
(id
);
a
ssetItem
.s
etAsset
(newA
sset
(assetId
));
a
ssetInventoryService
.updateAssetItem
(
assetItem
);
r
eturnR
esponseEntity
.noContent
().
build
();
}
// GET /api/rentals/assets/{assetId}/items/{id}
@ApiOperation
(v
alue=
"Returns the asset item with the given id"
)
@RequestMapping
(path= "
{assetId}/items/{id}"
,
method=
RequestMethod
.G
ET
)
publicA
ssetItemg
etAssetItem
(@
PathVariable
("id"
) I
nteger
id
) {
r
eturna
ssetInventoryService
.g
etAssetItem
(n
ewA
ssetItem
(id
));
}
Testing
There are several levels of testing that are usually implemented for a web application; in this
section we’ll cover unit testing and integration testing.
As a strategy we run our unit tests and integration tests in dedicated profiles - that makes it
easier to understand the context under which they are run. The downside of this approach is it
makes it a bit more difficult to run the unit test as part of the maven repackage phase.
In order to be able to run the tests in their own profiles we need to configure the Surefire and
Failsafe Maven plugins and pass to the forked JVMs the name of the active Spring profile (see
pom.xml fragment below).
This configuration allows us to run the unit tests as part of the build command:
./mvnw package
Because we are not using the actual Maven profile when running this command we also have to
include test dependencies (H2 database) in the main dependecies section of the Maven
pom.xml file.
<!-- Get H2 jars added to the classpath when running the tests -->
<
dependency
>
<groupId
>com.h2database
</
groupId
>
<artifactId
>h2
</
artifactId
>
<scope
>test
</
scope
>
</
dependency
>
Unit Testing
Unit tests are targeted at very narrow areas of functionality, usually a method in a class. We
write unit tests to cover classes at every layer of the application. The more unit tests and bigger
the test coverage the better the application is protected against inadvertent errors introduced
during the course of development.
Because we are only interested in testing methods in isolation we are going to rely heavily on
mocking - all external interactions will be mocked. Spring also provides facilities for testing only
slices of your application - when test classes are annotated with specific annotations, Spring will
not load the entire application but only the minimum components required to test that particular
application slice.
The Spring annotations for slice testing used in this course are:
We write all the unit testing code in this pacakge: course.a1.rentals.tests in the s
rc/test/java
folder. All resources required for running the tests are stored in src/test/resources.
Note that the classpath used for testing includes first resources in src/test/resources and then
src/main/resources so we only need to provide for test only resources that are not available in
the main folder or that need to be overridden.
To have better control over the configuration used to run the unit test we create a test maven
and spring profile.
For Maven, we add the test profile to the pom.xml file as shown below.
To make it easier to troubleshoot issues we create a separate logging configuration for the test
profile; the logback configuration file is logback-test.xml i n src/test/resources.
To run a single unit test (e.g. UserServiceTest) use the following command:
An alternative way of running only the unit tests is shown below - that will run only tests
matching the Surefire’s Maven plugin naming strategy (our unit tests ending in *Test will match
that)
Starting with JUnit 4.8, JUnit provides a method of separating our unit tests into related
categories. We can take advantage of this feature and group our tests in three categories:
Repository, Service and Controller. The groups are defined as interfaces in package
course.a1.rentals.tests.groups. Test classes or methods can then be annotated with
@Category(Repository.class), @Category(Service.class) or @Category(Controller.class)
to be assigned the respective group.
To execute only unit tests belonging to a certain group use the following command:
To run the repository unit test we use a dedicated Spring configuration to ensure we only load
the minimum required components that allows us to focus only on testing the repository classes.
The configuration class is RepositoryConfiguration.
package
course
.a1
.r
entals
.tests
.repository
;
import
org
.springframework
.boot
.autoconfigure
.EnableAutoConfiguration
;
import
org
.springframework
.boot
.autoconfigure
.domain
.E
ntityScan
;
import
org
.springframework
.context
.annotation
.
Configuration
;
import
org
.springframework
.data
.jpa
.repository
.config
.E
nableJpaRepositories
;
// Spring configuration class to be used for JPA tests.
// Spring will not scan for the main application configuration
(RentalsApplication.class).
// We want Spring to load only the minimal amount of functionality required
// for testing JPA repositories
@Configuration
// Required in this version of Spring to get the JPA tests working
@EnableAutoConfiguration
// Tell Spring the location of entity classes
@EntityScan
({
"course.a1.rentals.model"})
// Tell Spring the location of the repository classes
@EnableJpaRepositories
({
"course.a1.rentals.repository"})
public
classR
epositoryConfiguration{
}
In this Spring configuration class we enable entity scanning in the model package and specify
the location of the repository classes with the @EnableJpaRepositories annotation.
We create one test class for each repository class that we need to test. Each test class has a
number of annotations - see example below from UserRepositoryTest used to test the
UserRepository class.
dependencies required to run these JPA tests we need to point Spring to our custom
configuration class
@RunWith
(SpringRunner
.c
lass
)
@DataJpaTest
@ContextConfiguration
(classes
=RepositoryConfiguration
.c
lass
)
public
classU
serRepositoryTest{
@Autowired
privateT
estEntityManagere
ntityManager
;
...
Spring provides us with a TestEntityManager class that we can wire into our unit test; this class
can be used to setup the data required for our unit tests. For instance in the setUp method of
our UserRepositoryTest class we use this entity manager to setup the role required by the test
class.
Before
@
publicv
oids
etUp
() {
r
oleAdmin=
newR
ole
(Role
.Name
.ADMIN
);
e
ntityManager
.persist
(r
oleAdmin
);
e
ntityManager
.flush
();
}
We are going to use as example the code for service unit test classes - they are very similar to
the component unit tests. Below is the code from the unit test for UserService class
(UserServiceTest).
// Run these tests with the Spring test runner rather than
// Junit's
@RunWith
(SpringRunner
.c
lass
)
// JUnit category assigned to this unit test
@Category
(Service
.c
lass
)
public
classU
serServiceTest{
privateU
serService
userService
;
// Spring annotation used for mocking the marked class;
// the beans of this class in the application context are
// replaced by a mocked version
@MockBean
privateU
serRepositoryu
serRepository
;
@MockBean
privateU
serBonusPointsRepositoryu
serBonusPointsRepository
;
// setup our mock ups required for these tests
@Before
publicv
oids
etUp
() {
t
his
.u
serService= n
ewU
serServiceImpl
(
userRepository
,
userBonusPointsRepository
,
newB
CryptPasswordEncoder
());
Usera
dmin= n
ew
User
(
"[email protected]"
,
"pass"
,
new
Role
(Role
.
Name
.
ADMIN
));
// mock the calls to user repository - note that you must use
// the same parameters when writing the unit test methods that make
// use of this mocked calls
Mockito
.when
(
userRepository
.findByEmailIgnoreCase
("[email protected]"
))
.
thenReturn
(Optional
.of
(
admin
));
Page
<
User
>
adminPage=
new
PageImpl
<User
>(
Arrays
.
asList
(
admin
),
Pageable
.unpaged
(),
1L
);
Mockito
.when
(
userRepository
.findByEmailContainingIgnoreCase
("admin@"
,
Pageable
.unpaged
()))
.
thenReturn
(adminPage
);
}
@Test
public
void
testSearchUserByPartialEmail
() {
// this will make use of the mocked user repository -
// we expect back just the one entry we specified in our mock
// configuration
Page
<
User
> f
ound= u
serService
.searchUsers
(
new
UserSearchFilter
("admin@"
),
Pageable
.unpaged
());
assertThat
(
found
.getTotalElements
(),
is
(1L
));
User
admin=
found
.
getContent
().
get
(
0);
assertThat
(
admin
.getEmail
(), i
s
("[email protected]"
));
assertThat
(
admin
.getRoles
(), h
asItem
(new
Role
(
Role
.
Name
.ADMIN
)))
}
}
When you run this command you should see the unit tests stopping and waiting for a connection
from the debugger on port 5005.
You can use the same remote java application configuration described in section Debugging to
launch the debugger.
Integration Testing
We define our integration tests in a dedicated package: course.a1.rentals.itests and run them
in a dedicated profile: itest.
We are using the same strategy as for our unit tests: create a different profile and all the
artefacts that allows us to customize and separate the configuration of the integration tests. We
have a dedicated application properties file (application-itest.properties), a dedicated log file
(logback-itest.xml).
The integration tests are run using the following command, passing the name of the profile
(itest):
The integration-test phase runs the unit tests as well - to only run the integration tests run the
command below. The Failsafe Maven plugin naming strategy will select our integration tests
(ending with *IT)
Integration tests, by definition, need to test all the layers of the application - no mocking is used
so we’ll be using the Spring test facilities to ensure the application is fully started and the
integration test are using a REST client to fire request to the application.
against our REST API application. We also specify via the webEnvironment attribute
that we want our application to be started on a random port for these tests. The port is
made available to the test class via the @LocalServerPort annotation - as shown in the
example below a field of the test class is marked with this annotation and is
automatically initialized with the current port number.
To re-use common functionality we built an abstract class RentalsIT that is used as the super
class of all integration test classes.
● Stores the port number on which the application is started - using the
@LocalServerPort annotation
● Provides access to the TestRestTemplate instance
● Provides a method to get the access token for a user - operation required before
accessing any of our REST API endpoints
@RunWith
(SpringRunner
.c
lass
)
// Spring annotation to be used for integration testing
// Note that if you have more than one SpringBootApplication class in your
// application you will need to specify it with the classes attribute -
// otherwise it will be automatically found for you
// The webEnvironment attribute specifies that the embedded web server should
// start on a random port - the actual port is available to the test class via
// the @LocalServerPort annotation
@SpringBootTest
(webEnvironment= W
ebEnvironment
.RANDOM_PORT
)
public
classU
serRestControllerITe
xtendsR
entalsIT{
// Test that we can get an access token from the application
@Test
publicv
oida
ccessToken
()
throwsE
xception{
S
tringt
oken= g
etAccessToken
("[email protected]"
);
a
ssertThat
(token
, i
s
(n
otNullValue
()));
}
// Test that if we try to access the REST API without a token we get a
// 401 HTTP status code returned
@Test
publicv
oidn
oAccessTokenExpectError
() t
hrowsE
xception{
H
ttpHeadersh
eaders=
newH
ttpHeaders
();
h
eaders
.setContentType
(MediaType
.APPLICATION_JSON
);
H
ttpEntity
<String
> r
equest=
n
ewH
ttpEntity
<String
>(
headers
);
H
ttpStatus
result= g
etRestTemplate
()
.
postForEntity
(createURL
("/api/rentals/users/search"
),
request
, S
tring
.class
).
getStatusCode
();
a
ssertThat
(result
.v
alue
(),
is
(4
01
));
}
// Test the user search functionality when searching by full email
// address
@Test
publicv
oids
earchUserWhenFullEmailIsProvided
()
throwsE
xception{
/
/ get an access token to use with the request to test
S
tringa
ccessToken= g
etAccessToken
("[email protected]"
);
H
ttpHeadersh
eaders=
newH
ttpHeaders
();
h
eaders
.setContentType
(MediaType
.APPLICATION_JSON
);
h
eaders
.set
("Authorization"
, "
Bearer "+ a
ccessToken
);
U
serSearchFilterf
ilter= n
ewU
serSearchFilter
("
[email protected]"
);
H
ttpEntity
<UserSearchFilter
> r
equest=
n
ewH
ttpEntity
<UserSearchFilter
>(
filter
,
headers
);
R
esponseEntity
<I
TPage
<User
>>
result=
getRestTemplate
()
.
exchange
(createURL
("/api/rentals/users/search"
),
HttpMethod
.POST
, r
equest
,
new
ParameterizedTypeReference
<
ITPage
<U
ser
>>() {});
a
ssertThat
(result
.g
etStatusCode
().
value
(),
is
(2
00
));
P
age
<U
ser
>
page=
result
.g
etBody
();
a
ssertThat
(page
.getContent
().
size
(), i
s
(1)
);
a
ssertThat
(page
.getContent
().
get
(0)
.
getEmail
(),
is
("
[email protected]"
));
}
}
When you run this command you should see the integration tests stopping and waiting for a
connection from the debugger on port 5005.
You can use the same remote java application configuration described in section Debugging to
launch the debugger.