Simple Spring Boot Application

jim stafford

Introduction

Goals

The student will learn:

  • foundational build concepts for simple, Spring Boot Application

Objectives

At the conclusion of this lecture and related exercises, the student will be able to:

  1. extend the standard Maven jar module packaging type to include core Spring Boot dependencies

  2. construct a basic Spring Boot application

  3. build and execute an executable Spring Boot JAR

  4. define a simple Spring component and inject that into the Spring Boot application

Spring Boot Maven Dependencies

  • spring-boot-starter-parent

    • meant to be a parent pom with an opinionated view of how to build child modules

    • defines plugins and dependencies

    • inherits from spring-boot-dependencies

  • spring-boot-dependencies

    • "Bill of Materials" (BOM)

    • defines plugin and dependency versions in a non-opinionated manner

    • may be used as a parent to inherit dependencyManagement and pluginManagement definitions

    • may be imported to leverage just dependencyManagement (this approach taken in class examples)

Parent POM

boot app parent poms
Figure 2. Parent/Child Pom Relationship and Responsibilities
  • A development project will likely create multiple modules

  • Locally maintained parent poms can help keep

    • child modules consistent

    • child modules thin

  • Maven poms

    • can only have a single, inherited parent

    • but can import many pom dependency definitions

Define Version for Spring Boot artifacts

Explicit Property Definition
# Place this declaration in an inherited parent pom
<properties>
    <springboot.version>3.3.2</springboot.version> (1)
</properties>
1default value has been declared in imported ejava-build-bom

Property values can be overruled at build time by supplying a system property on the command line "-D(name)=(value)"

Import springboot-dependencies-plugin

# Place this declaration in an inherited parent pom
<dependencyManagement> (1)
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>${springboot.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
1import is within examples-root for class examples, which is a grandparent of this example

Local Child/Leaf Module POM

Declare pom inheritance in the child pom.xml

# Place this declaration in the child/leaf pom building the JAR archive
<parent>
    <groupId>(parent groupId)</groupId>
    <artifactId>(parent artifactId)</artifactId>
    <version>(parent version)</version>
</parent>
  • pulls in definitions from parent

SpringBoot App Example POM Tree

app build parent exampletree
Figure 3. SpringBoot App Example POM Tree

Declare dependency on artifacts used

# Place this declaration in the child/leaf pom building the JAR archive
<dependencies>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <!--version --> (1)
  </dependency>
</dependencies>
1parent has defined (using import in this case) the version for all children to consistently use


  • enacts (parent-configured) dependencies through declarations

  • imported spring-boot-dependencies will take care of declaring the version#

Class Examples Dependency Management

app build parent dependencies
Figure 4. Class Examples dependencyManagement

Simple Spring Boot Application Java Class

package info.ejava.springboot.examples.app.build.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication (3)
public class SpringBootApp {
    public static void main(String... args) { (1)
        System.out.println("Running SpringApplication");

        SpringApplication.run(SpringBootApp.class, args); (2)

        System.out.println("Done SpringApplication");
    }
}
1Define a class with a static main() method
2Initiate Spring application bootstrap by invoking SpringApplication.run() and passing a) application class and b) args passed into main()
3Annotate the class with @SpringBootApplication
Startup can, of course be customized (e.g., change the printed banner, registering event listeners)

Module Source Tree

The source tree will look similar to our previous Java main example.

|-- pom.xml
`-- src
    |-- main
    |   |-- java
    |   |   `-- info
    |   |       `-- ejava
    |   |           `-- examples
    |   |               `-- app
    |   |                   `-- build
    |   |                       `-- springboot
    |   |                           `-- SpringBootApp.java
    |   `-- resources
    `-- test
        |-- java
        `-- resources

@SpringBootApplication Aggregate Annotation

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringBootApp {
}

The @SpringBootApplication annotation is a compound class-level annotation aggregating the following annotations.

  • @ComponentScan - legacy Spring annotation that configures component scanning to include or exclude looking through various packages for classes with component annotations

    • By default, scanning will start with the package declaring the annotation and work its way down from there

  • @SpringBootConfiguration - like legacy Spring @Configuration annotation, it signifies the class can provide configuration information.

    • Classes annotated with @Configuration contain factory @Bean definitions.

  • @EnableAutoConfiguration - Allows Spring to perform autoconfiguration based on the classpath, beans defined by the application, and property settings.

The class annotated with @SpringBootApplication is commonly located in a Java package that is above all other Java packages containing components for the application.

Spring Boot Executable JAR

Automated with Maven

Building the Spring Boot Executable JAR

  • Previous example used maven-jar-plugin to build standard executable JAR

    • no construct to house dependencies

  • Spring Boot requires dependencies that bootstrap the application

    • Spring Boot defines a special JAR format to host dependencies and start application

    • spring-boot-maven-plugin automates building Spring Boot Executable JAR

Declare spring-boot-maven-plugin

<properties>
    <spring-boot.classifier>bootexec</spring-boot.classifier>
</properties>
...
<build>
  <pluginManagement>
    <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <configuration>
              <classifier>${spring-boot.classifier}</classifier> (4)
          </configuration>
          <executions>
              <execution>
                  <id>build-app</id> (1)
                  <phase>package</phase> (2)
                  <goals>
                    <goal>repackage</goal> (3)
                  </goals>
              </execution>
          </executions>
        </plugin>
        ...
    </plugins>
  </pluginManagement>
</build>
1id used to describe execution and required when having more than one
2phase identifies the maven goal in which this plugin runs
3repackage identifies the goal to execute within the spring-boot-maven-plugin
4adds a -bootexec to the executable JAR’s name
  • Modifies the Maven-built JAR with constructs enabling Spring Boot to operate

  • We can do more with spring-boot-maven-plugin on a per-module basis (e.g., run)

Build the JAR

$ mvn clean package

[INFO] Scanning for projects...
...
[INFO] --- maven-jar-plugin:3.4.2:jar (default-jar) @ springboot-app-example ---
[INFO] Building jar: .../target/springboot-app-example-6.1.0-SNAPSHOT.jar (1)
[INFO]

[INFO] --- spring-boot-maven-plugin:3.3.2:repackage (build-app) @ springboot-app-example ---
[INFO] Attaching repackaged archive .../target/springboot-app-example-6.1.0-SNAPSHOT-bootexec.jar with classifier bootexec (2)
1standard Java JAR is built by the maven-jar-plugin
2standard Java JAR is augmented by the spring-boot-maven-plugin

Java MANIFEST.MF properties

The spring-boot-maven-plugin augmented the standard JAR by adding a few properties to the MANIFEST.MF file

$ unzip -qc target/springboot-app-example-6.1.0-SNAPSHOT-bootexec.jar META-INF/MANIFEST.MF
Manifest-Version: 1.0
Created-By: Maven JAR Plugin 3.2.2
Build-Jdk-Spec: 17
Main-Class: org.springframework.boot.loader.launch.JarLauncher (1)
Start-Class: info.ejava.examples.app.build.springboot.SpringBootApp (2)
Spring-Boot-Version: 3.3.2
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
Spring-Boot-Layers-Index: BOOT-INF/layers.idx
1Main-Class was set to a Spring Boot launcher
2Start-Class was set to the class we defined with @SpringBootApplication

JAR size

Notice that the size of the Spring Boot executable JAR is significantly larger the original standard JAR.

$ ls -lh target/*jar* | grep -v sources | cut -d\  -f9-99
10M Aug 28 15:19 target/springboot-app-example-6.1.0-SNAPSHOT-bootexec.jar (2)
4.3K Aug 28 15:19 target/springboot-app-example-6.1.0-SNAPSHOT.jar  (1)
1The original Java JAR with Spring Boot annotations was 4.3KB
2The Spring Boot JAR is 10MB

JAR Contents

  • Unlike WARs, a standard Java JAR does not provide a standard way to embed dependency JARs

  • Common approaches include a "shaded" JAR

    • unwind all JARs and packaged as a single "uber" JAR

    • positives

      • works

      • follows standard Java JAR constructs

    • negatives

      • obscures contents of the application

      • problem if multiple source JARs use files with same path/name

  • Spring Boot creates a custom WAR-like structure

Spring Boot Custom WAR-like JAR Structure

BOOT-INF/classes/info/ejava/examples/app/build/springboot/AppCommand.class
BOOT-INF/classes/info/ejava/examples/app/build/springboot/SpringBootApp.class (3)
BOOT-INF/lib/javax.annotation-api-2.1.1.jar (2)
...
BOOT-INF/lib/spring-boot-3.3.2.jar
BOOT-INF/lib/spring-context-6.1.11.jar
BOOT-INF/lib/spring-beans-6.1.11.jar
BOOT-INF/lib/spring-core-6.1.11.jar
...
META-INF/MANIFEST.MF
META-INF/maven/info.ejava.examples.app/springboot-app-example/pom.properties
META-INF/maven/info.ejava.examples.app/springboot-app-example/pom.xml
org/springframework/boot/loader/launch/ExecutableArchiveLauncher.class (1)
org/springframework/boot/loader/launch/JarLauncher.class
...
org/springframework/boot/loader/util/SystemPropertyUtils.class
1Spring Boot loader classes hosted at the root /
2Local application classes hosted in /BOOT-INF/classes
3Dependency JARs hosted in /BOOT-INF/lib

Spring Boot can also use Standard WAR Structure

  • 99% of it is a standard WAR

    • /WEB-INF/classes

    • /WEB-INF/lib

  • Spring Boot loader classes hosted at the root /

  • Special directory for dependencies only used for non-container deployment

    • /WEB-INF/lib-provided

Execute Command Line

springboot-app-example$ java -jar target/springboot-app-example-6.1.0-SNAPSHOT-bootexec.jar (1)
Running SpringApplication (2)

  .   ____          _            __ _ _ (3)
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v3.3.2)

2019-12-04 09:01:03.014  INFO 1287 --- [main] i.e.e.a.build.springboot.SpringBootApp: \
  Starting SpringBootApp on Jamess-MBP with PID 1287 (.../springboot-app-example/target/springboot-app-example-6.1.0-SNAPSHOT.jar \
  started by jim in .../springboot-app-example)
2019-12-04 09:01:03.017  INFO 1287 --- [main] i.e.e.a.build.springboot.SpringBootApp: \
  No active profile set, falling back to default profiles: default
2019-12-04 09:01:03.416  INFO 1287 --- [main] i.e.e.a.build.springboot.SpringBootApp: \
  Started SpringBootApp in 0.745 seconds (JVM running for 1.13)
Done SpringApplication (4)
1Execute the JAR using the java -jar command
2Main executes and passes control to SpringApplication
3Spring Boot bootstrap is started
4SpringApplication terminates and returns control to our main()

Add a Component to Output Message and Args

  • Class: AppCommand

  • Responsibility: Print message with program args

We want this class found by Spring’s application startup processing, so we will:

// AppCommand.java
package info.ejava.examples.app.build.springboot; (2)

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.List;

@Component (1)
public class AppCommand implements CommandLineRunner {
    public void run(String... args) throws Exception {
        System.out.println("Component code says Hello " + List.of(args));
    }
}
1Add a @Component annotation on the class
2Place the class in a Java package configured to be scanned

@Component Annotation

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class AppCommand implements CommandLineRunner {
  • Class instances can be configured to be managed by Spring

  • Classes can be annotated to express their purpose and have Spring manage them in specific ways

  • @Component annotation is the most generic form of Spring service annotation

  • Classes annotated directly with @Component or more specialized annotations can be directly instantiated by Spring without the assistance of a factory @Bean method.

Interface: CommandLineRunner

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class AppCommand implements CommandLineRunner {
    public void run(String... args) throws Exception {
    }
}
  • Components implementing CommandLineRunner interface get called after application initialization

  • Program arguments are passed to the run() method

  • Can be used to perform one-time initialization at start-up

  • Alternative Interface: ApplicationRunner

    • Components implementing ApplicationRunner are also called after application initialization

    • Program arguments are passed to its run() method have been wrapped in ApplicationArguments convenience class

Component startup can be ordered with the @Ordered Annotation.

@ComponentScan Tree

@SpringBootApplication
//  @ComponentScan
//  @SpringBootConfiguration
//  @EnableAutoConfiguration
public class SpringBootApp {
}
src/main/java
`-- info
    `-- ejava
        `-- springboot
            `-- examples
                `-- app
                    |-- AppCommand.java
                    `-- SpringBootApp.java
  • By default, the @SpringBootApplication annotation configured Spring to look at and below the Java package for our SpringBootApp class

  • Component class happen to be placed in same Java package

Running the Spring Boot Application

$ java -jar target/springboot-app-example-6.1.0-SNAPSHOT-bootexec.jar

Running SpringApplication    (1)

  .   ____          _            __ _ _ (2)
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v3.3.2)

2019-09-06 15:56:45.666  INFO 11480 --- [           main] i.e.s.examples.app.SpringBootApp
  : Starting SpringBootApp on Jamess-MacBook-Pro.local with PID 11480 (.../target/springboot-app-example-6.1.0-SNAPSHOT.jar ...)
2019-09-06 15:56:45.668  INFO 11480 --- [           main] i.e.s.examples.app.SpringBootApp
  : No active profile set, falling back to default profiles: default
2019-09-06 15:56:46.146  INFO 11480 --- [           main] i.e.s.examples.app.SpringBootApp
  : Started SpringBootApp in 5.791 seconds (JVM running for 6.161) (3)
Hello []                     (4) (5)
Done SpringApplication       (6)
1Our SpringBootApp.main() is called and logs Running SpringApplication
2SpringApplication.run() is called to execute the Spring Boot application
3Our AppCommand component is found within the classpath at or under the package declaring @SpringBootApplication
4The AppCommand component run() method is called, and it prints out a message
5The Spring Boot application terminates
6Our SpringBootApp.main() logs Done SpringApplication an exits

Implementation Note

I added print statements directly in the Spring Boot Application’s main() method to help illustrate when calls were made. This output could have been packaged into listener callbacks to leave the main() method implementation free — except to register the callbacks. If you happen to need more complex behavior to fire before the Spring context begins initialization, then look to add listeners of the SpringApplication instead.

Configure pom.xml to Test

<build>
   ...
  <plugins>
      <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <executions>
              <execution>
                  <id>run-application</id> (1)
                  <phase>integration-test</phase>
                  <goals>
                      <goal>run</goal>
                  </goals>
                  <configuration> (2)
                      <arguments>Maven,plugin-supplied,args</arguments>
                  </configuration>
              </execution>
          </executions>
      </plugin>
  </plugins>
</build>
1new execution of the run goal to be performed during the Maven integration-test phase
2command line arguments passed to main

Execute JAR as part of the build

$ mvn clean verify
[INFO] Scanning for projects...
...
[INFO] --- spring-boot-maven-plugin:3.3.2:run (run-application) @ springboot-app-example ---
[INFO] Attaching agents: [] (1)
Running SpringApplication

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.3.2)

2022-07-02 14:11:46.110  INFO 48432 --- [           main] i.e.e.a.build.springboot.SpringBootApp   : Starting SpringBootApp using Java 17.0.3 on Jamess-MacBook-Pro.local with PID 48432 (.../springboot-app-example/target/classes started by jim in .../springboot-app-example)
2022-07-02 14:11:46.112  INFO 48432 --- [           main] i.e.e.a.build.springboot.SpringBootApp   : No active profile set, falling back to 1 default profile: "default"
2022-07-02 14:11:46.463  INFO 48432 --- [           main] i.e.e.a.build.springboot.SpringBootApp   : Started SpringBootApp in 0.611 seconds (JVM running for 0.87)
Component code says Hello [Maven, plugin-supplied, args] (2)
Done SpringApplication
1Our plugin is executing
2Our application was executed and the results displayed

Summary

As a part of this material, the student has learned how to:

  1. Add Spring Boot constructs and artifact dependencies to the Maven POM

  2. Define Application class with a main() method

  3. Annotate the application class with @SpringBootApplication (and optionally use lower-level annotations)

  4. Place the application class in a Java package that is at or above the Java packages with beans that will make up the core of your application

  5. Add component classes that are core to your application to your Maven module

  6. Typically, define components in a Java package that is at or below the Java package for the SpringBootApplication

  7. Annotate components with @Component (or other special-purpose annotations used by Spring)

  8. Execute application like a normal executable JAR