jim stafford
The student will learn:
foundational build concepts for simple, Spring Boot Application
At the conclusion of this lecture and related exercises, the student will be able to:
extend the standard Maven jar
module packaging type to include
core Spring Boot dependencies
construct a basic Spring Boot application
build and execute an executable Spring Boot JAR
define a simple Spring component and inject that into the Spring Boot application
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
"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)
A development project will likey 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
# Place this declaration in an inherited parent pom
<properties>
<springboot.version>2.7.0</springboot.version> (1)
</properties>
1 | default 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)" |
# 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>
1 | import is within examples-root for class examples, which is a grandparent of this example |
# 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
# 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>
1 | parent 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#
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 final void main(String...args) { (1)
System.out.println("Running SpringApplication");
SpringApplication.run(SpringBootApp.class, args); (2)
System.out.println("Done SpringApplication");
}
}
1 | Define a class with a static main() method |
2 | Initiate Spring applicaton bootstrap by invoking SpringApplication.run()
and passing a) application class and b) args passed into main() |
3 | Annotate the class with @SpringBootApplication |
Startup can, of course be customized (e.g., change the printed banner, registering event listeners) |
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
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 thru 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 auto-configuration 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. |
Automated with Maven
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
<properties>
<spring-boot.classifier>bootexec</spring-boot.classifier>
</properties>
...
<build>
<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>
</build>
1 | id used to describe execution and required when having more than one |
2 | phase identifies the maven goal in which this plugin runs |
3 | repackage identifies the goal to execute within the spring-boot-maven-plugin |
4 | adds 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)
$ mvn clean package
[INFO] Scanning for projects...
...
[INFO] --- maven-jar-plugin:3.2.2:jar (default-jar) @ springboot-app-example ---
[INFO] Building jar: .../target/springboot-app-example-6.0.1-SNAPSHOT.jar (1)
[INFO]
[INFO] --- spring-boot-maven-plugin:2.7.0:repackage (build-app) @ springboot-app-example ---
[INFO] Attaching repackaged archive .../target/springboot-app-example-6.0.1-SNAPSHOT-bootexec.jar with classifier bootexec (2)
1 | standard Java JAR is built by the maven-jar-plugin |
2 | standard Java JAR is augmented by the spring-boot-maven-plugin |
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.0.1-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.JarLauncher
Start-Class: info.ejava.examples.app.build.springboot.SpringBootApp
Spring-Boot-Version: 2.7.0
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
1 | Main-Class was set to a Spring Boot launcher |
2 | Start-Class was set to the class we defined with @SpringBootApplication |
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
8.4M Aug 28 15:19 target/springboot-app-example-6.0.1-SNAPSHOT-bootexec.jar (2)
4.1K Aug 28 15:19 target/springboot-app-example-6.0.1-SNAPSHOT.jar (1)
1 | The original Java JAR with Spring Boot annotations was 4.1KB |
2 | The Spring Boot JAR is 8.4MB |
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
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-1.3.2.jar (2)
...
BOOT-INF/lib/spring-boot-2.7.0.jar
BOOT-INF/lib/spring-context-5.3.20.jar
BOOT-INF/lib/spring-beans-5.3.20.jar
BOOT-INF/lib/spring-core-5.3.20.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/ExecutableArchiveLauncher.class (1)
org/springframework/boot/loader/JarLauncher.class
...
org/springframework/boot/loader/util/SystemPropertyUtils.class
1 | Spring Boot loader classes hosted at the root / |
2 | Local application classes hosted in /BOOT-INF/classes |
3 | Dependency JARs hosted in /BOOT-INF/lib |
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
springboot-app-example$ java -jar target/springboot-app-example-6.0.1-SNAPSHOT-bootexec.jar (1)
Running SpringApplication (2)
. ____ _ __ _ _ (3)
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.0})
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.0.1-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)
1 | Execute the JAR using the java -jar command |
2 | Main executes and passes control to SpringApplication |
3 | Spring Boot bootstrap is started |
4 | SpringApplication terminates and returns control to our main() |
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));
}
}
1 | Add a @Component annotation on the class |
2 | Place the class in a Java package configured to be scanned |
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 then 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.
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. |
@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
$ java -jar target/springboot-app-example-6.0.1-SNAPSHOT-bootexec.jar
Running SpringApplication (1)
. ____ _ __ _ _ (2)
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.0)
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.0.1-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)
1 | Our SpringBootApp.main() is called and logs Running SpringApplication |
2 | SpringApplication.run() is called to execute the Spring Boot application |
3 | Our AppCommand component is found within the classpath at or under the package declaring @SpringBootApplication |
4 | The AppCommand component run() method is called and it prints out a message |
5 | The Spring Boot application terminates |
6 | Our SpringBootApp.main() logs Done SpringApplication an exits |
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. |
<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>
1 | new execution of the run goal to be performed during the Maven integration-test phase |
2 | command line arguments passed to main |
$ mvn clean verify
[INFO] Scanning for projects...
...
[INFO] --- spring-boot-maven-plugin:2.7.0:run (run-application) @ springboot-app-example ---
[INFO] Attaching agents: [] (1)
Running SpringApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.0)
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
1 | Our plugin is executing |
2 | Our application was executed and the results displayed |
As a part of this material, the student has learned how to:
Add Spring Boot constructs and artifact dependencies to the Maven POM
Define Application class with a main() method
Annotate the application class with @SpringBootApplication (and optionally use lower-level annotations)
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
Add component classes that are core to your application to your Maven module
Typically define components in a Java package that is at or below the Java package for the SpringBootApplication
Annotate components with @Component (or other special-purpose annotations used by Spring)
Execute application like a normal executable JAR