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:

build.gradle
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:

  1. In addition to the Java plugin, two additional plugins are applied: Spring Boot, and Dependency Management

  2. The dependencies list is practically empty

  3. 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:

Contents of "fat" jar
.
├── 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:

META-INF/MANIFEST.MF
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
java jar boot

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

boot run

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.