This is a demo app to show features of Spring Boot.
In META-INF/MANIFEST.MF
of the Spring Boot fat jar, we can see the following content:
Manifest-Version: 1.0
Implementation-Title: spring-boot-demo
Implementation-Version: 0.0.1-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: yanglifan
Implementation-Vendor-Id: com.github.yanglifan
Spring-Boot-Version: 1.4.3.RELEASE
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.github.yanglifan.demo.modernspring.SpringBootDemoApplic
ation
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_65
Implementation-URL: http://projects.spring.io/spring-boot/spring-boot-
demo/
We know that the value of Main-Class
segment will be the entry class when execute java -jar app.jar
command.
org.springframework.boot.loader.JarLauncher
locates in spring-boot-loader module of spring-boot-tools project
JarLauncher
just invokes the class which is specified by Start-Class
in MANIFEST.MF
. In this demo, Start-Class
is ModernSpringDemoApplication
. Like most of Spring Boot applications, in main
method of ModernSpringDemoApplication
, SpringApplication.run(ModernSpringDemoApplication.class, args)
is invoked.
The first important method is initialize(Object[] sources)
. This method is invoked by SpringApplication
constructor.
public class SpringApplication {
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
}
This method will decide whether the current application is a web application or not.
The second important method of SpringApplication
is createApplicationContext()
. This method will be according to the variable webEnvironment
to decide to use which class to create the ApplicationContext. If webEnvironment
is true, this method will use AnnotationConfigEmbeddedWebApplicationContext
:
public class SpringApplication {
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
}
This method will be invoked by SpringApplication.run(String... args)
.
After SpringApplication
to create an ApplicationContext
, the process will like another Spring applications. Then I will introduce the internal process of AnnotationConfigEmbeddedWebApplicationContext
.
Spring Boot use AnnotationConfigEmbeddedWebApplicationContext
as the default web application context. AnnotationConfigEmbeddedWebApplicationContext
will create an embedded web container. By default, it will use TomcatEmbeddedServletContainerFactory
to create an embedded Tomcat instance.
public class EmbeddedWebApplicationContext extends GenericWebApplicationContext {
protected EmbeddedServletContainerFactory getEmbeddedServletContainerFactory() {
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory()
.getBeanNamesForType(EmbeddedServletContainerFactory.class);
if (beanNames.length == 0) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to missing "
+ "EmbeddedServletContainerFactory bean.");
}
if (beanNames.length > 1) {
throw new ApplicationContextException(
"Unable to start EmbeddedWebApplicationContext due to multiple "
+ "EmbeddedServletContainerFactory beans : "
+ StringUtils.arrayToCommaDelimitedString(beanNames));
}
return getBeanFactory().getBean(beanNames[0],
EmbeddedServletContainerFactory.class);
}
}
There is a question. Why Spring Boot chooses Tomcat by default. The answer is related with Spring Boot auto config mechanism. spring-boot-autoconfigure
module is very important, it provides the auto config mechanism to Spring Boot. You can find all 3rd party technologies' configuration which supported by Spring Boot officially. Of course, you can find Tomcat configuration in it:
public class EmbeddedServletContainerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
}
Annotations @ConditionalOnClass
and @ConditionalOnMissingBean
will make a magic. When the ApplicationContext
startup, Spring will load classes with @Configuration
and parse annotations like @ConditionalOnClass
and @ConditionalOnMissingBean
. If the condition is matched, then the corresponding configuration will be effective.
@Order(Ordered.HIGHEST_PRECEDENCE)
class OnClassCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
// ...
}
}
Since by default, spring-boot-starter-web
module depends on spring-boot-starter-tomcat
module, so embedded Tomcat classes is on the classpath by default. So Spring Boot will use Tomcat be default.
Underlying, Spring uses Java byte code technology to read the annotation data into metadata objects. See AnnotationMetadataReadingVisitor
.
There is still a question. EmbeddedServletContainerAutoConfiguration
defines the web container configuration. Then AnnotationConfigEmbeddedWebApplicationContext
will use this bean definition. But how EmbeddedServletContainerAutoConfiguration
to be loaded?
See the following call flow. When a Spring application starts, ApplicationContext.refresh()
will be invoked. refresh()
method is very important for understanding Spring application lifecycle.
ApplicationContext.refresh() -> ApplicationContext.invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) -> ConfigurationClassPostProcessor.processConfigBeanDefinitions(BeanDefinitionRegistry registry) -> ConfigurationClassParser.processDeferredImportSelectors()
In this call flow, ConfigurationClassPostProcessor
is one of BeanPostProcessor
. Its function is to load other classes with @Configuration
. In our demo, SpingBootDemoApplication
is also a Configuration
class. So after it create and refresh a Spring Application Context, this Spring Application Context will treat SpingBootDemoApplication
as a Spring configuration class.
SpingBootDemoApplication
has SpringBootApplication
annotation, SpringBootApplication
is also @EnableAutoConfiguration
. And @EnableAutoConfiguration
imports EnableAutoConfigurationImportSelector
. So EnableAutoConfigurationImportSelector
will be invoked and it will use load more Configuration
classes according to spring.factories
file:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration
# ...
class EnableAutoConfigurationImportSelector {
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
}
So EmbeddedServletContainerAutoConfiguration
will be loaded. Then in ApplicationContext.onRefresh()
method, the web container will be created.
This also explains the function of spring.factories
in Spring Boot.
Normally you would add @EnableWebMvc for a Spring MVC app, but Spring Boot adds it automatically when it sees spring-webmvc on the classpath.
If you add H2 database library into the classpath. Spring Boot will use H2 embedded database. And if you add spring-boot-devtools into the classpath, you can also use H2 web console. Open http://localhost:8080/h2-console, you can see it. By default, the jdbc url is jdbc:h2:mem:testdb
. Username is sa, no password.
When create a JPA entity, @Table
is necessary
@Table(name = "t_orders")
@Entity
public class Order {
}
When you write UT related with database operations, in general you will initialize the database in setUp
method. But setUp
method will run before every test case run. You must aware that. For example, when you save some data into the database in setUp
method. And you have two test cases, then the data save operation will do twice. That may fail your UT, since your data is duplicated. You can clean your database in tearDown
method.
http://www.network-science.de/ascii/
Actuator is a very powerful production tools. With it, you can have functions like monitor, health check.
Add the following snippet to enable it:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
After that, you will get a lot of system and application level information via following links:
- Health check
- Configuration and Environments
- URL Mappings
- Spring Beans
- Metrics: Memory, thread, classes, HTTP counters
- Thread Dump
- Trace: Displays trace information (by default the last 100 HTTP requests).
You can provide a custom health indicator very easy:
@org.springframework.context.annotation.Configuration
class Application {
@Bean
HealthIndicator helloWorldHealthIndicator() {
return () -> {
Health.Builder builder = new Health.Builder();
return builder
.up()
.withDetail("hello", "world")
.build();
};
}
}
With this configuration, you will get the following response when you invoke the health check API:
{
"status": "UP",
"helloWorld": {
"status": "UP",
"hello": "world"
}
}
I believe you can find the relationship between Java code and JSON response.
You can expose latest Git commit id in /info
interface by adding git-commit-id-plugin
maven plugin:
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
</plugin>