Released in 2003, Spring framework offerred a lightweight alternative to Java Enterprise Edition (JEE or J2EE) Servlets and moved away from EJBs (Enterprise Java Beans) to Java components like POJO Beans and provided Annotations.
Biggest features were - Dependency Injection (DI) (IoC) and Aspect-oriented programming (AOP).
But Spring was heavy in terms of configuration. There was just too much manually written XML configs. Spring 3.0 introduced Java code based configs as an alternative to XML based configs but still there was too much config and redundancy. This lead to development friction.
Released in 2014, Spring Boot is a framework based on Spring which differs in below core aspects:
Many beans are always created in all specific-kind of applications and we don’t need to create those in Spring Boot explicitly as it will look at the dependencies included and create those beans for us automatically e.g. jdbcTemplate
bean is included automatically if we have a SQL driver dependency added. All this happens at runtime (more specifically during application startup time).
If we include a dependency called spring-boot-starter-web
it will pull around 8-9 other dependencies that we need to build web apps. We also don’t have to care about their versions, compatibility, and updates in future because they work out-of-the-box.
Optional component. Provides a way to run a Spring Boot app without a conventional Java project structure, imports and even the need for compilation. Groovy (a Java compatible scripting language) can be used too.
$ spring --version
$ spring shell #for auto-completion using TAB
$ spring init
$ spring init -dweb,jpa,security --build gradle output_folder # default is Maven
$ spring init -dweb,jpa -p war # packaging as WAR instead of JAR default
We can inspect internals of a Spring Boot app during runtime in two ways - by exposing provided web API endpoints or by opening a secure shell (SSH) session into the application.
We have to include spring-boot-starter-actuator
dependency for this to work.
Build tool - Maven or Gradle (provides project structure and dependency management)
Maven is rigid and redundant but reliable.
Gradle is modern but debugging issues can take time.
Web Server - Tomcat or Jetty (to deploy the app onto)
A web app used to generate a demo project for Spring Boot.
Ways to access -
spring init
command (uses internet connection)pom.xml
src.main.java
com.example.demo
DemoApplication.java
controller/
dto/
repository/impl
service/impl
src.main.resources/
application.properties
static/
templates/
src.test.java
com.demo
DemoApplicationTest.java
The DemoApplication.java
scans only folders on its dir level as shown above. Manually specify package to scan using @ComponentScan(basePackages = "com.*")
in main application Java file.
The DemoApplication.java class has a main()
method and it is used for both configuration and bootstrapping.
@SpringBootApplication // configuration
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args); // bootstrapping; passing object of current class
}
@SpringBootApplication - Introduced in Spring Boot 1.2. Includes 3 other annotaions:
Spring Boot auto-configures beans based on - dependencies included (contents of classpath), properties defined, other beans created.
Where are all the configuration classes & default beans stored?
Ans: When you add Spring Boot to your application, there’s a JAR file named spring-boot-autoconfigure
that contains several configuration classes. Every one of these configuration classes is available on the application’s classpath and has the opportunity to contribute to the configuration of your application.
This JAR has a pacakage org.springframework.boot.autoconfigure
where all the @Configuration classes are stored. These configuration classes use @Conditional annotation to include beans.
@ConditionalOnBean(name={"dataSource"})
@ConditionalOnBean(type={DataSource.class})
@ConditionalOnMissingBean(DataSource.class)
@ConditionalOnProperty
@ConditionalOnMissingBean
@ConditionalOnMissingClass
We can define our own bean or use properties to override the default one. Ex - DataSource
bean below.
// 1. define our own DataSource Bean in any @Configuration class, Spring Boot will automatically register it
@Bean
public AnotherClass myDataSource() { // name doesn't matter; only return type does
return new DataSource();
}
# 2. customize default DataSource class using properties
# Connection settings
spring.datasource.url=
spring.datasource.username=
spring.datasource.password=
spring.datasource.driver-class-name=
# Connection pool settings
spring.datasource.initial-size=
spring.datasource.max-active=
spring.datasource.max-idle=
spring.datasource.min-idle=
# web embedded server configuration
server.port=9000
server.address=192.168.11.21
server.session-timeout=1800
# change base path for server
server.servlet.context-path=/vendormanagement
@Configuration
@EnableAutoConfiguration(exclude=DataSourceAutoConfiguration.class)
public class ApplicationConfiguration {
// code
}
We can set properties via command-line parameters, OS environment variables, application.properties
file (or .yml
) inorder of precedence.
If defined in application.properties
file, they are searched by name in below order:
/config
subdir of the current directoryconfig
package on the classpath
classpath
root (src/main/resources dir becomes JAR’s root)This means that the application.properties
in application classpath will be replaced by the one (if placed) inside /config
directory. Also, appliaction.yml
will take precedence over application.properties
if both are available alongside each other in the same directory.
# define any custom property and use it
foo.bar=1337
We can supply spring boot bean properties (like server.port
or spring.profiles.active
) without externalizing it from the properties file first, but custom properties created by us need to be specified in properties file first (see below section).
A common pattern I’ve seen in production is to have a separate config
module which contains all the properties files. We include this module as a dependency in the primary (service
) module and delete the application.properties
file from the primary module, and then Spring Boot uses the properties files from the config
module automatically since they’re on the classpath now.
foo.bar=${FOOBAR}
first.name=Abhishek
foo.bar=My name is ${first.name}
# supplying a default
foo.bar=${number:420}
# via command-line arg
java -jar demo-app.jar --FOOBAR=Abhishek
# via env variables
set FOOBAR=Abhishek
java -jar demo-app.jar
# we often configure helm charts in production to supply them
Can be done, but not recommended to divert from the standard name.
public static void main(String[] args) {
System.setProperty("spring.config.name", "foobar"); // file is automatically named "foobar.properties"
SpringApplication.run(MasteringSpringBootApplication.class, args);
}
// another way to do it via command line
$ java -jar myproject.jar --spring.config.name=foobar
This allows access with objects of this class, we can pass it as parameter to methods putting properties in a List
or a Map
and access it.
// POJO
@Component
@ConfigurationProperties(prefix="demo")
public class Demo {
private String foo;
private String bar;
public void setFoo(String foo) {
this.foo = foo;
}
public String getFoo() {
return foo;
}
public void setBar(String bar) {
this.bar = bar;
}
public String getBar() {
return bar;
}
}
// property
demo.foo="Lorem"
demo.bar="Ipsum"
// we can now use getter and setter in other classes to use this POJO
// we can also have List, Map, or other POJOs inside ConfigurationProperties classes
// we can also use @ConfigurationProperties on record instead of a class; makes the code much shorter
@ConfigurationProperties(prefix="demo")
public record Demo (String foo, String bar){ }
We need to have @EnableConfigurationProperties on any one configuration class inorder to use custom properties and that has already happened with Spring Boot’s many default auto-config classes getting imported by default so we don’t need to specify that explicitly.
Reference: https://www.baeldung.com/configuration-properties-in-spring-boot
If you set any properties in a profile-specific application-{profile}.properties
will override the same properties set in an application.properties
file.
We can load profiles as follows:
# 1. setting the property in application.properties (default)
spring.profiles.active=dev
# 2. or select a profile by executing the JAR with param
--spring.profiles.active=dev
<!-- 3. or activating profile with POM -->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<profiles>
<profile>dev</profile>
</profiles>
</configuration>
</plugin>
</plugins>
Notice that the default profile is always active. So when we run the app, it reads from application.properties
(default) and then sees spring.profiles.active=dev
and loads the dev profile too. So we can place common properties in default and dev exclusive properties in dev property file as both will be loaded on runtime. Any properties present in both will be overriden by dev’s version.
We can have multiple custom profiles active in Spring Boot (atop default profile). Use comma when specifying in default properties file or command-line:
spring.profiles.active=dev,qa
In the above example, any properties common in both the profiles will be overriden by the one which is loaded at the last i.e. qa
profile properties will override the same ones if they’re present in dev
profile.
When in production, we often specify profile from command-line when running the JAR and it has the same effect but no hardcoding in application.properties
. This will work the same as above i.e. loads both default and dev profiles.
$ java -jar foobar-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
The default profile (application.properties
) is always loaded. Any other active profile(s) is loaded on top of it.
We can also specify @Profile("dev")
or "!dev"
on classes and only if the specified profile is active, the class’s bean will be loaded otherwise it will be ignored.
// property
foo.bar = "FoobarStr"
// in class code
@Value("${foo.bar}")
private String foobar;
@Value("${foo.bar: defaultStr}") // with a default value
private String foobar;
// we can't use @Value on a static variable, if we use, variable will be null at runtime
// we can directly use "${foo.bar}" at some limited places in our code
Do note that the property that is being referred to in @Value
tag must exist (i.e corresponding profile loaded) and match data type of variable its transferred to (foobar
here), otherwise its a compiler-error. e.g. In the above case, foo.bar
must exist in properties file and its profile must be loaded, and its value must be of type string, otherwise compiler-error. We can provide default value to avoid compiler-error.
So, its bad to keep properties exclusive to non-default profiles in our code as they will all fail when we load with default profile in future.
Some property values are special and can be transferred to boolean types in code using @Value
even if they aren’t true
or false
.
This is becasue Spring Boot uses Relaxed Binding and doesn’t just transfer property values but rather processes them “smartly”.
# all of the below are equivalent
example.property=true
example.property=on
example.property=yes
example.property=1
If a property with value as on
is transferred to String
type it won’t cause any errors, and as we saw that if its transferred to boolean
it will be true
!
More examples of relaxed binding is that all of the below are equivalent and hence completely interchangeable with each other!:
# Dot Separated
foo.bar.baz=qux
# Camel Case
fooBarBaz=qux
# Kebab Case
foo-bar-baz=qux
# Snake Case
foo_bar_baz=qux
# Mixed Usage
foo.bar-baz=qux
Everything discussed above works the same way in YAML too.
We can specify which property file to take values from using the @PropertySource annotation.
@PropertySource("classpath:foobar.properties")
public class foobarService{ }
// note that we can't use YAML with this annotation out-of-the-box in Spring
# properties
database.host=localhost
database.user=admin
# yaml
database:
host: localhost
user: admin
Multiple profiles in a single YAML file:
# common to all profiles
database:
host: localhost
user: admin
---
# for dev profile
spring.profiles: dev
database:
host: 1.1.1.1
user: testuser
---
# for prod profile
spring.profiles: prod
database:
host: 192.1.1.9
user: user
Most applications are packaged and shipped as a JAR now, they are called Uber JAR or Fat JAR since they bundle every dependency inside them and can be deployed as standalone.
We can generate JAR using Maven as follows, packaging type can be set in pom.xml:
$ mvn package
$ java -jar demo-app.jar --server.port=9090
$ java -Dserver.port=9090 -jar demo-app.jar
# need to use -D flag in the second approach
# since we're specifying to "java" executable (where flags should've been) and not to our JAR
# they both mean the same thing
WAR isn’t commonly used nowadays as it bundles frontend (JavaScript) as well as backend code (Java) inside them and nowadays there is a separation amongst them. They are used in JBOSS server (WildFly) deployments.