The goal for this lab is to gain a deeper understanding of how Spring Boot manages a project’s build, and how it simplifies build management.
1. Inspect the build
Review the build file:
buildscript {
ext {
springBootVersion = '2.0.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter'
testCompile 'org.springframework.boot:spring-boot-starter-test'
}
This file would be easier to read if we were to use gradle’s new plugins DSL:
plugins {
id 'java'
id 'org.springframework.boot' version '2.0.3.RELEASE'
id 'io.spring.dependency-management' version '1.0.6.RELEASE'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter'
testCompile 'org.springframework.boot:spring-boot-starter-test'
}
➜ Revise your build to match the above.
Observe that:
-
In addition to the Java plugin, two additional plugins are applied: Spring Boot, and Dependency Management
-
The dependencies list is practically empty
-
The dependencies are cited with no version information
2. Spring Boot plugin
Drop to the command line, open a terminal, navigate (cd
) to the demo/
project, and run the command: gradle tasks
:
➜ gradle tasks > Task :tasks ------------------------------------------------------------ All tasks runnable from root project ------------------------------------------------------------ Application tasks ----------------- bootRun - Runs this project as a Spring Boot application. Build tasks ----------- assemble - Assembles the outputs of this project. bootJar - Assembles an executable jar archive containing the main classes and their dependencies. build - Assembles and tests this project. ... BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
Note the tasks: bootJar
and bootRun
. These are contributed by the Spring Boot gradle plugin.
One of the main responsibilities of the plugin is to repackage the project’s build artifact (jar) as a self-contained artifact, that bundles not only your class files and resources, but all dependencies.
This task can be invoked directly, but is also wired into the Java plugin’s build tasks.
Let’s explore this. Run:
➜ gradle clean build BUILD SUCCESSFUL in 3s 6 actionable tasks: 6 executed
Navigate to the build output directory and run a directory listing:
➜ cd build/libs ➜ ls -lF total 13984 -rw-r--r-- 1 eitansuez staff 7156857 Mar 6 20:17 demo-0.0.1-SNAPSHOT.jar
2.1. The "fat" jar
Spring Boot builds what we call a "fat" jar. Other than a copy of Java, this jar file contains everything necessary to run our application: class files, resources, and dependencies.
From the build/libs
folder, create a temporary subfolder, navigate to it, and unpack the jar file into it:
$ mkdir tmp $ cd tmp $ jar -xf ../demo-0.0.1-SNAPSHOT.jar
Inspect the contents of the unpacked jar file:
. ├── BOOT-INF │ ├── classes │ │ ├── application.yml │ │ ├── banner.txt │ │ └── com │ │ └── example │ │ └── demo │ │ ├── DemoApplication.class │ │ ├── HelloProperties.class │ │ ├── HelloService.class │ │ ├── LoggingHelloService.class │ │ └── Runner.class │ └── lib │ ├── javax.annotation-api-1.3.2.jar │ ├── jul-to-slf4j-1.7.25.jar │ ├── log4j-api-2.10.0.jar │ ├── log4j-to-slf4j-2.10.0.jar │ ├── logback-classic-1.2.3.jar │ ├── logback-core-1.2.3.jar │ ├── slf4j-api-1.7.25.jar │ ├── snakeyaml-1.19.jar │ ├── spring-aop-5.0.7.RELEASE.jar │ ├── spring-beans-5.0.7.RELEASE.jar │ ├── spring-boot-2.0.3.RELEASE.jar │ ├── spring-boot-autoconfigure-2.0.3.RELEASE.jar │ ├── spring-boot-starter-2.0.3.RELEASE.jar │ ├── spring-boot-starter-logging-2.0.3.RELEASE.jar │ ├── spring-context-5.0.7.RELEASE.jar │ ├── spring-core-5.0.7.RELEASE.jar │ ├── spring-expression-5.0.7.RELEASE.jar │ └── spring-jcl-5.0.7.RELEASE.jar ├── META-INF │ └── MANIFEST.MF └── org └── springframework └── boot └── loader ├── ... 15 directories, 76 files
We see our class files under BOOT-INF/classes
, dependencies under BOOT-INF/lib
, the jar manifest, and code that Spring Boot uses to load our application, under org.springframework.boot.loader
.
Look at this jar’s manifest file:
Manifest-Version: 1.0 Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: com.example.demo.DemoApplication
Notice how the manifest specifies the boot loader’s JarLauncher
as the main class, and that it’s already detected and set DemoApplication
as the "Start class". This is a runnable jar. Run it:
$ cd .. $ java -jar demo-0.0.1-SNAPSHOT.jar
This is one of the big advantages of Spring Boot: it produces a self-contained, runnable artifact.
3. Spring Boot starter dependencies
Spring Boot defines the concept of a starter dependency, a higher-order dependency that maps directly to a specific type of application or nature. For example, Spring Boot provides spring-boot-starter-web
for web applications, spring-boot-starter-jdbc
for jdbc-type applications, and so on. The list of Spring Boot starters is long. Starter dependencies get the developer out of the business of micro-managing the set of dependencies needed for a given type of application.
The only compile-time dependency specified in this project is spring-boot-starter
. This starter is the most basic type of Spring Boot starter.
Run the following command and observe the output to understand the transitive dependencies of spring-boot-starter
:
➜ gradle dependencies --configuration compile > Task :dependencies ------------------------------------------------------------ Root project ------------------------------------------------------------ compile - Dependencies for source set 'main' (deprecated, use 'implementation ' instead). \--- org.springframework.boot:spring-boot-starter -> 2.0.3.RELEASE +--- org.springframework.boot:spring-boot:2.0.3.RELEASE | +--- org.springframework:spring-core:5.0.7.RELEASE | | \--- org.springframework:spring-jcl:5.0.7.RELEASE | \--- org.springframework:spring-context:5.0.7.RELEASE | +--- org.springframework:spring-aop:5.0.7.RELEASE | | +--- org.springframework:spring-beans:5.0.7.RELEASE | | | \--- org.springframework:spring-core:5.0.7.RELEASE (*) | | \--- org.springframework:spring-core:5.0.7.RELEASE (*) | +--- org.springframework:spring-beans:5.0.7.RELEASE (*) | +--- org.springframework:spring-core:5.0.7.RELEASE (*) | \--- org.springframework:spring-expression:5.0.7.RELEASE | \--- org.springframework:spring-core:5.0.7.RELEASE (*) +--- org.springframework.boot:spring-boot-autoconfigure:2.0.3.RELEASE | \--- org.springframework.boot:spring-boot:2.0.3.RELEASE (*) +--- org.springframework.boot:spring-boot-starter-logging:2.0.3.RELEASE | +--- ch.qos.logback:logback-classic:1.2.3 | | +--- ch.qos.logback:logback-core:1.2.3 | | \--- org.slf4j:slf4j-api:1.7.25 | +--- org.apache.logging.log4j:log4j-to-slf4j:2.10.0 | | +--- org.slf4j:slf4j-api:1.7.25 | | \--- org.apache.logging.log4j:log4j-api:2.10.0 | \--- org.slf4j:jul-to-slf4j:1.7.25 | \--- org.slf4j:slf4j-api:1.7.25 +--- javax.annotation:javax.annotation-api:1.3.2 +--- org.springframework:spring-core:5.0.7.RELEASE (*) \--- org.yaml:snakeyaml:1.19 (*) - dependencies omitted (listed previously) A web-based, searchable dependency report is available by adding the --scan option. BUILD SUCCESSFUL in 0s 1 actionable task: 1 executed
So this basic starter consists of:
-
Spring framework 5.0 libraries
-
logging libraries: logback (the default, can be changed), and slf4j as the logging façade.
-
a yaml parser
-
Spring boot autoconfiguration capabilities (we’ll explore this in a later lab)
By specifying additional starters, we expand the application’s list of capabilities.
4. No version numbers
Inspect your build.gradle
file’s dependencies
block. Does it look like something is missing? Normally we have to specify depdencies using a triplet: the group id, the artifact id, and the version number. Yet, in our build file both the compile
and testCompile
dependencies do not specify a version number.
Aside from Spring Boot plugin, the build file applies another plugin named the dependency management plugin. This plugin is responsible for managing library versions on our behalf, via the use of a Bill of Materials (BOM): a list of libraries and corresponding versions. This eliminates the need for us to specify library versions, and again delegates this responsibility to Spring Boot, which maintains a list of compatible library versions for each release of Spring Boot.
So, for example, we can see that version 2.0.3.RELEASE of Spring Boot implies version 1.7.25 of slf4j.
Overriding the default version is a simple matter of actually specifying a version in the dependencies block of the build file.
5. bootRun
Explore the bootRun
gradle task:
-
Navigate to the base directory of your project
-
Invoke the command
gradle bootRun
-
Watch your application start up
This task runs your application in "exploded form" (i.e. it does not need to package your application as a jar file). This is very convenient while developing.
In a subsequent lab, we’ll explore bootRun
in conjunction with another Spring Boot starter, named devtools which allows for the automatic reloading of the running application each time we change and recompile the code.
6. Summary
In this lab you’ve explored:
-
A basic Spring Boot project’s build file,
-
The Spring Boot plugin, how it packages your app as a "fat" jar, and the bootRun task
-
The notion of Spring Boot starters
-
The use of a Bill of Materials, providing default versions for dependencies, out of the box
Next, let’s turn our attention to how Spring Boot simplifies the development and running of web applications.