Talk about Spring Boot Internals
* Spring Ripper retrospective
* how to build spring boot project. Maven/Gradle plugins
** DependencyManagement in gradle and maven
** Executable artifacts (war or jar)
** War and Jar anatomy
** Embeded tomcat and standalone tomcat integration – SPI. WebApplicationInitializer and ServletContainerInitializer
** Tomcat in Tomcat like a Crank
** Executable jar anatomy
** Main Class and Start-Class. Java MANIFEST.MF anatomy
** Jar like a War, but Jar – JarCraft
** Runtime ClassPath in spring boot applications
* SpringApplication.run ...
** Arguments
** Sources types
** Two general context type in spring boot app
** Starters and autoconfiguration
** spring.factories and SpringFactoriesLoader
** Merge app sources
** Environment and EnvironmentPostProcessor
** ConfigFileApplicationListener mistakes
** Spring Events vs Spring Boot Events
** Application Initializers
** Context prepare
** BeanDefinitionLoader
* @SpringBootApplication anatomy
** @Import – Three types of arguments
*** ImportSelector
*** @Configuration
*** ImportBeanDefinitionRegistrar
** Who is your boss starters? @EnableAutoConfiguration anatomy
** Ugly internal spring boot starter
** ConfigurationClassParser
*** Configuration and Lite Configuration
** Conditional and shouldSkip in different context initialisation steps
19. Как выглядит наш pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
</parent>
29. Как выглядит наш build.gradle
plugins {
id "org.springframework.boot" version "1.5.3.RELEASE"
}
Пакует
приложение
30. Как выглядит наш build.gradle
plugins {
id "org.springframework.boot" version "1.5.3.RELEASE"
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
}
Добавляем
зависимости
31. Как выглядит наш build.gradle
plugins {
id "org.springframework.boot" version "1.5.3.RELEASE"
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
}
Automatic
Dependency
Management
32. Как выглядит наш build.gradle
plugins {
id "org.springframework.boot" version "1.5.3.RELEASE"
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter-web'
}
dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE'
}
}
44. § 8.2.4 Shared libraries / runtimes pluggability
The ServletContainerInitializer class is looked up via
the jar services API. For each application, an instance
of the ServletContainerInitializer is created by the
container at application startup time. The framework
providing an implementation of the
ServletContainerInitializer MUST bundle in the
META-INF/services directory of the jar file a file called
javax.servlet.ServletContainerInitializer, as per the jar
services API, that points to the implementation class of
the ServletContainerInitializer.
3.+
46. § 8.2.4 Shared libraries / runtimes pluggability
The ServletContainerInitializer’s onStartup method get's
a Set of Classes that either extend / implement the
classes that the initializer expressed interest in or if
it is annotated with any of the classes specified via the
@HandlesTypes annotation.
3.+
47. Как? SPI
tomcat
|→ get javax.servlet.ServletContainerInitializer impl via SPI
|→ get all MyInitializer classes (from @HandlesTypes)
48. Как? SPI
tomcat
|→ get javax.servlet.ServletContainerInitializer impl via SPI
|→ get all WebApplicationInitializer classes (from @HandlesTypes)
org.springframework.web.WebApplicationInitializer
49. Как? SPI
tomcat
|→ get javax.servlet.ServletContainerInitializer impl via SPI
|→ get all WebApplicationInitializer classes (from @HandlesTypes)
|→ call ServletContainerInitializer.onStartup(classes,servletCtx)
according to @Order order
65. public class WebStarter implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
SpringApplication.run(RipperApplication.class);
}
}
66. public class WebStarter implements WebApplicationInitializer{
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
SpringApplication.run(RipperApplication.class);
}
}
Tomcat в Tomcat`е
68. § 85.1 Create a deployable war file
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder app) {
return app.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
69. § 85.1 Create a deployable war file
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder app) {
return app.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
70. § 85.1 Create a deployable war file
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder app) {
return app.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
Работает по
разному
71. § 85.1 Create a deployable war file
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder app) {
return app.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
72. § 85.1 Create a deployable war file
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder a) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
} Никогда не запустится в tomcat
Только java -jar
73. § 85.1 Create a deployable war file
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder a) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
Без main не соберется
см. maven/gradle плагины
Обязательно
74. § 85.1 Create a deployable war file
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder a) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
Опционально
75. § 85.1 Create a deployable war file
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder a) {
return application.sources(Application.class);
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
} Никогда не запустится в embedded режиме
Только в Tomcat контейнере
Опционально
77. А как убрать tomcat из tomcat?
If you are using a version of Gradle that supports compile only dependencies
(2.12 or later), you should continue to use providedRuntime. Among other
limitations, compileOnly dependencies are not on the test classpath so any
web-based integration tests will fail.
78. Анатомия War
war
|- META-INF
|- WEB-INF
|- lib
|- lib-provided
|- classes
|- org/springframework/boot/loader
|- WarLauncher and friends
79. 2. Как запускать
1. tomcat war
2. Idea run
3. java -jar *.jar
4. java -jar *.war
93. Как выбирается main class
● Манифест
○ Main-Class
→ WarLauncher
→ JarLauncher
● Start-Class – как выбирается (gradle/maven)
○ сканирует проект – ищет мейны
○ если больше одного – смотрит на SpringBootApplication и выбирает
○ если SpringBootApplication нет или на каждому – ошибка о множественных main
98. Перед запуском main – сформируй classpath
Кто формирует classpath
java -jar app.jar → JarLauncher
java -jar app.war → WarLauncher
cp app.war $TOMCAT_HOME/webapps → tomcat
99. Как выбирается main class
● Манифест
○ Main-Class
→ WarLauncher
→ JarLauncher
● Start-Class – как выбирается (gradle/maven)
○ сканирует проект – ищет мейны
○ если больше одного – смотрит на SpringBootApplication и выбирает
○ если SpringBootApplication нет или на каждому – ошибка о множественных main
public static void main(String[] args) {
SpringApplication.run(RipperApplication.class, args);
…
101. Как выбирается main class
● Манифест
● WarLauncher
● JarLauncher
● Start-Class
○ сканирует проект – ищет мейны
○ если больше одного – смотрит на SpringBootApplication и выбирает
○ если SpringBootApplication нет или на каждому – ошибка о множественных main
public static void main(String[] args) {
SpringApplication.run(RipperApplication.class, args);
…
102. А что будет, если так?
public static void main(String[] args) {
SpringApplication.run(String.class, args);
…
111. Ну если всё так, то почему вот это падает?
$ java -jar spring-app.jar
public static void main(String[] args) {
SpringApplication.run(String.class, args);
}
112. Ну если всё так, то почему вот это падает?
$ java -jar spring-app.jar
public static void main(String[] args) {
SpringApplication.run(String.class, args);
}
Caused by: ...ApplicationContextException: Unable to start
EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory
bean.
113. Нет никакой связи
- Отключи web starter и всё будет
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
114. Давайте поговорим о контексте
Малыш знает про
ClassPathXmlApplicationContext
А какие контексты знаешь ТЫ?
122. Хочу напомнить, что мы говорили об этом
$ java -jar spring-app.jar
public static void main(String[] args) {
SpringApplication.run(String.class, args);
}
Caused by: ...ApplicationContextException: Unable to start
EmbeddedWebApplicationContext due to missing EmbeddedServletContainerFactory
bean.
123. Ну что ты сделал?! Ну что!?
SpringApplication.run(String.class,...)
125. Слушай, а можно в двух словах про стартеры?
§ 44.1 Understanding auto-configured beans
Under the hood, auto-configuration is implemented with standard @Configuration classes. Additional @Conditional annotations
are used to constrain when the auto-configuration should apply. Usually auto-configuration classes use @ConditionalOnClass and
@ConditionalOnMissingBean annotations. This ensures that auto-configuration only applies when relevant classes are found and
when you have not declared your own @Configuration.
You can browse the source code of spring-boot-autoconfigure to see the @Configuration classes that we provide (see
theMETA-INF/spring.factories file).
134. Можно
$ java -jar spring-app.jar --spring.main.sources=conf.ripper
public static void main(String[] args) {
SpringApplication.run(String.class, args);
}
135. Вот, оно работает даже не удаляя web starter
$ java -jar spring-app.jar --spring.main.sources=conf.ripper
public static void main(String[] args) {
SpringApplication.run(String.class, args);
}
…RipperApplication: Started RipperApplication in 0.254
seconds (JVM running for 0.634)
136. Who merge the sources?
$ java -jar spring-app.jar --spring.main.sources=conf.ripper
137. Who merge the sources?
$ java -jar spring-app.jar --spring.main.sources=conf.ripper
Тузик, фас!
151. § 24.1 Configuring random values - пример EPP
The RandomValuePropertySource is useful for injecting random values (e.g. into secrets or test cases). It can produce integers, longs,
uuids or strings, e.g.
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]} EPP
181. Где вылетит эксепшен
ctx.stop(); (1)
ctx.start(); (2)
ctx.close(); (3)
ctx.start(); (4)
1. на строчке (1)
2. на строчке (2)
3. на строчке (3)
4. на строчке (4)
182. Где вылетит эксепшен
ctx.stop(); (1)
ctx.start(); (2)
ctx.close(); (3)
ctx.start(); (4)
1. на строчке (1)
2. на строчке (2)
3. на строчке (3)
4. на строчке (4)
5. не вылетит вообще
183. Где вылетит эксепшен
ctx.stop(); (1)
ctx.start(); (2)
ctx.close(); (3)
ctx.start(); (4)
1. на строчке (1)
2. на строчке (2)
3. на строчке (3)
4. на строчке (4)
5. не вылетит вообще
185. Где вылетит эксепшен
ctx.stop(); (1)
ctx.start(); (2)
ctx.close(); (3)
ctx.start(); (4)
1. на строчке (1)
2. на строчке (2)
3. на строчке (3)
4. на строчке (4)
5. не вылетит вообще
186. Вся цепочка по порядку
@SpringBootApplication
public class RipperApplication {
public static void main(String[] args) {
SpringApplication.run(RipperConfiguration.class,args);
}
}
227. Где то внутри CacheAutoConfiguration
for (int i = 0; i < types.length; i++) {
imports[i] =
CacheConfigurations.getConfigurationClass(types[i]);
}
return imports;
233. ConfigurationClassParser
Он никто и создаётся через new
Его задача искать в тех конфигурациях,
которые ему дают:
@Import
@ImportResource
@Component
@ComponentScan
И вытягивать дополнительные конфигурации
234. ConfigurationClassParser
Он никто и создаётся через new
Его задача искать в тех конфигурациях,
которые ему дают:
@Import
@ImportResource
@Component
@ComponentScan
И вытягивать дополнительные конфигурации
Lite Configuration
235. Parser Puzzler
@ComponentScan
public class SuperConfig0 {
@ComponentScan
public static class SuperConfig1 {
@ComponentScan("rrr")
public static class SuperConfig2 {
}
}
}
236. Parser Puzzler
@ComponentScan
public class SuperConfig0 {
@ComponentScan
public static class SuperConfig1 {
@ComponentScan("rrr")
public static class SuperConfig2 {
}
}
}
237. Parser Puzzler
@ComponentScan
public class SuperConfig0 {
@ComponentScan
public static class SuperConfig1 {
@ComponentScan("rrr")
public static class SuperConfig2 {
}
}
}
239. А кто даёт шреку начальные конфигурации?
BeanDefinitionRegistryPostProcessor
ConfigurationClassPostProcessor
240. А что делает ConfigurationClassPostProcessor
BeanDefinitionRegistryPostProcessor
ConfigurationClassPostProcessor
● его задача развернуть из sources
остальные конфигурации/бины
241.
242. Два вопроса
1. Откуда взялся ConfigurationClassPostProcessor?
2. Какие у него начальные конфигурации и где он их взял?
244. А что на данном этапе есть в registry?
Только наши sources из SpringApplication
245. А что на данном этапе есть в registry?
Только наши sources из SpringApplication
ConfigurationClassParser
246. ConfigurationClassParser
● метод parse рекурсивно сканирует полученный список конфигураций
● добавляет и парсит BeanDefinition из
ImportSelector/ImportBeanDefinitionRegistrar/ComponentSca
n/Import
● фильтрует BeanDefintion с помощью ConditionEvaluator
● возращает все BeanDefinition найденных конфигураций, которые
передаются в BeanDefinitionReader
256. Хотите автокомплит на application.yml?
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
257. Немного выводов
● Spring Boot вышел за пределы Spring Context
● Starter от команды Spring отличаются от public convention
○ не всегда в лучшую сторону
● Три этапа построения контексты
○ #1 Загрузка исходны классов/конфигураций – sources
○ #2 Загрузка BeanDefinition с помощью ImportSelector
○ #3 Загрузка @Bean и прочего внутри BeanDefinition`ов из #2
● в Spring Boot это космос, не магия
258. Немного выводов
● Spring Boot вышел за пределы Spring Context
● Starter от команды Spring отличаются от public convention
○ не всегда в лучшую сторону
● Три этапа построения контексты
○ #1 Загрузка исходны классов/конфигураций – sources
○ #2 Загрузка BeanDefinition с помощью ImportSelector
○ #3 Загрузка @Bean и прочего внутри BeanDefinition`ов из #2
● в Spring Boot это космос, не магия