Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Boot yourself, Spring is coming

Boot yourself, Spring is coming

Many years ago Java developers used "new" in order to create Java services. They had many handmade configurations mixed with business logic; they even were using copy-paste techniques. They wrote many lines of the same ugly code, which sometimes even worked.

Then Spring happened and things have changed… We received a lot of "magic" from Spring black box and our code became much cleaner and more simple.

And then Spring Boot happened… On the one hand, it solved thousands of problems: versions conflict, configuration issues, infrastructure's beans declarations, problems with environments, and even running or deploying applications including building jar/war… On the other hand, Spring Boot uses so much magic that mostly we come across two scenarios:

Everything is working without any effort even though nobody knows why.
Nothing is working and nobody knows why.
In this talk, we'll try to reveal at least a part of Spring Boot magic by discovering Spring Boot concepts, conventions and the way it works. And even though after this talk you will understand that there is no magic (mostly), you will enjoy Spring Boot even more, because you will be able to solve Spring Boot problems or conflicts without calling 911 (mostly).

Kirill Tolkachev

April 09, 2018
Tweet

More Decks by Kirill Tolkachev

Other Decks in Technology

Transcript

  1. За что мы не любим Spring Boot 2.0 в РФ

    private static boolean isAlpha(char ch) { return ch >= 'a' && ch <= 'z'; } .. @ConfigurationProperties
  2. Application Listener ClassPatBeanDefinitionScanner BPP BPP Bean Factory BPP Context WAI

    EPP SApp ENV system prop application. yml SpringFactoriesLoader RDB CFL Starter mn,, spring.factories
  3. Что не любит программист Думать о: 1. Зависимостях... 2. Настройках

    и конфигурациях Делать: 3. Деплоймент и запуск (тест/локально/прод)
  4. Что не любит программист Думать о: 1. Зависимостях... 2. Настройках

    и конфигурациях Делать: 3. Деплоймент и запуск (тест/локально/прод)
  5. Вот контекст! @SpringBootApplilcation class App { public static void main(String[]

    args) { ApplicationContext context = SpringApplication.run(App.class,args); } }
  6. Загадочные аргументы Дано: RipperApplication.class public… main(String[] args) { SpringApplication.run(?,args); }

    1. RipperApplication.class 2. String.class 3. "context.xml" 4. new ClassPathResource("context.xml") 5. Package.getPackage("conference.spring.boot.ripper")
  7. Загадочные аргументы Дано: RipperApplication.class public… main(String[] args) { SpringApplication.run(?,args); }

    1. RipperApplication.class 2. String.class 3. "context.xml" 4. new ClassPathResource("context.xml") 5. Package.getPackage("conference.spring.boot.ripper") Голосуем!
  8. SpringApplication.run(Object[] sources, String[] args) # APPLICATION SETTINGS (SpringApplication) spring.main.sources= #

    class name, package name, xml location spring.main.web-environment= # true/false spring.main.banner-mode=console # log/off
  9. Это я решаю какой контекст создать Я до создания контекста

    ещё много всего делаю, но об этом потом. SpringApplication
  10. Что за spring.factories ? § 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).
  11. @SpringBootApplication @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = {

    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { …
  12. @SpringBootApplication @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = {

    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { …
  13. @EnableAutoConfiguration @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration

    { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; } ImportSelector
  14. SpringFactoriesLoader static <T> List<T> loadFactories( Class<T> factoryClass, ClassLoader cl )

    static List<String> loadFactoryNames( Class<?> factoryClass, ClassLoader cl )
  15. Где то внутри CacheAutoConfiguration for (int i = 0; i

    < types.length; i++) { Imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports;
  16. CacheConfigurations private static final Map<CacheType, Class<?>> MAPPINGS; static { Map<CacheType,

    Class<?>> mappings = new HashMap<CacheType, Class<?>>(); mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class); mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class); mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class); mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class); addGuavaMapping(mappings); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); }
  17. Паззлер @Configuration @ConditionalOnСуроваяЗима public class UndeadArmyConfiguration { ... } @Configuration

    public class DragonIslandConfiguration { @Bean @ConditionalOnСуроваяЗима public DragonGlassFactory dragonGlassFactory() { return new DragonGlassFactory(); } ... }
  18. 1. 100 $ 2. 200 $ 3. 300 или 400

    $ 4. между 500 и 1000 $ 5. Больше 1000 $ * Спросить погоду стоит денег – 100 золотых драконов за раз * $ = золотой дракон ConditionalOnСуроваяЗима
  19. 1. 100 $ 2. 200 $ 3. 300 или 400

    $ 4. между 500 и 1000 $ 5. Больше 1000 $ * Спросить погоду стоит денег – 100 золотых драконов за раз * $ = золотой дракон @Configuration @ConditionalOnСуроваяЗима public class UndeadArmyConfiguration { ... } @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOnСуроваяЗима public DragonGlassFactory dragonGlassFactory() { return new DragonGlassFactory(); } ... } ConditionalOnСуроваяЗима
  20. Правильный ответ 1. 100 $ 2. 200 $ 3. 300

    или 400 $ 4. между 500 и 1000 $ 5. Больше 1000 $
  21. Правильный ответ 1. 100 $ 2. 200 $ 3. 300

    или 400 $ 4. между 500 и 1000 $ 5. Больше 1000 $ 300 – если UndeadArmyConfiguration в стартере 400 – если UndeadArmyConfiguration в коде приложения
  22. Железный закон №1.3 Отправлять ворона только если есть получатели 1.

    список получателей 2. без списка не летим 3. автокомплит Лететь то куда?
  23. Conditional и друзья @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ...

    @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean
  24. Железный закон №1.3 Отправлять ворона только если есть получатели 1.

    список получателей 2. без списка не летим 3. автокомплит Лететь то куда? ← application.yml ← @ConditionalOnProperty ← @ConfigurationProperty
  25. Conditional и друзья @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ...

    @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean
  26. ConditionalOnPuzzler @Configuration public class КонфигурацияКазни { @Bean @ConditionalOnClass ({Мыло.class, Веревка.class})

    @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни виселицы() { return new ФабрикаВиселиц( "..."); } }
  27. ConditionalOnPuzzler @Configuration public class КонфигурацияКазни { @Bean @ConditionalOnClass ({Мыло.class, Веревка.class})

    @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни виселицы() { return new ФабрикаВиселиц( "..."); } @Bean @ConditionalOnClass ({Стул.class, Ток.class}) @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни cтулья() { return new ФабрикаЭлектрическихСтульев( "вж вж"); } }
  28. ConditionalOnPuzzler @Configuration public class КонфигурацияКазни { @Bean @ConditionalOnClass ({Мыло.class, Веревка.class})

    @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни виселицы() { return new ФабрикаВиселиц( "..."); } @Bean @ConditionalOnClass ({Стул.class, Ток.class}) @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни cтулья() { return new ФабрикаЭлектрическихСтульев( "вж вж"); } @Bean @ConditionalOnClass ({Гильотина.class, ХорошееНастроение. class}) @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни гильотины() { return new ФабрикаГильотин( "хрусть хрусть"); } }
  29. ConditionalOnPuzzler @Configuration public class КонфигурацияКазни { @Bean @ConditionalOnClass ({Мыло.class, Веревка.class})

    @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни виселицы() { return new ФабрикаВиселиц( "..."); } @Bean @ConditionalOnClass ({Стул.class, Ток.class}) @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни cтулья() { return new ФабрикаЭлектрическихСтульев( "вж вж"); } @Bean @ConditionalOnClass ({Гильотина.class, ХорошееНастроение. class}) @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни гильотины() { return new ФабрикаГильотин( "хрусть хрусть"); } } 1. ClassDefNotFound ? 2. Так вообще нельзя, не компилируется 3. Будет работать 4. Будет отлично работать
  30. ConditionalOnPuzzler @Configuration public class КонфигурацияКазни { @Bean @ConditionalOnClass ({Мыло.class, Веревка.class})

    @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни виселицы() { return new ФабрикаВиселиц( "..."); } @Bean @ConditionalOnClass ({Стул.class, Ток.class}) @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни cтулья() { return new ФабрикаЭлектрическихСтульев( "вж вж"); } @Bean @ConditionalOnClass ({Гильотина.class, ХорошееНастроение. class}) @ConditionalOnMissingBean ({ФабрикаКазни. class}) public ФабрикаКазни гильотины() { return new ФабрикаГильотин( "хрусть хрусть"); } } 1. ClassDefNotFound ? 2. Так вообще нельзя, не компилируется 3. Будет работать 4. Будет отлично работать
  31. OnPropertyCondition @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { String[] value() default {};

    String prefix() default ""; String[] name() default {}; String havingValue() default ""; boolean matchIfMissing() default false; boolean relaxedNames() default true; } Что если нужно изменить?
  32. OnPropertyCondition @ConditionalOnProduction @ConditionalOnProperty( name = { "ворон.вкл", "зима.вкл", "старки.вкл" },

    havingValue = "true" ) public IronBankApplicationListener applicationListener() { ... } Не массив
  33. OnPropertyCondition @ConditionalOnProduction @ConditionalOnProperty(name = "ворон.вкл", havingValue="true") @ConditionalOnProperty(name = "зима.вкл", havingValue="true")

    @ConditionalOnProperty(name = "старки.вкл",havingValue="false") public IronBankApplicationListener applicationListener() { ... }
  34. OnPropertyCondition @ConditionalOnProduction @ConditionalOnProperty(name = "ворон.вкл", havingValue="true") @ConditionalOnProperty(name = "зима.вкл", havingValue="true")

    @ConditionalOnProperty(name = "старки.вкл",havingValue="false") public IronBankApplicationListener applicationListener() { ... } @ConditionalOnProperty Не @Repeatable
  35. OnPropertyCondition public class OnRavenCondition extends AllNestedConditions { @ConditionalOnProperty( name =

    "ворон.куда", havingValue = "false") public static class OnRavenProperty { } @ConditionalOnProperty( name = "ворон.вкл", havingValue = "true", matchIfMissing = true) public static class OnRavenEnabled { } ... }
  36. Environment Property sources: • systemProperties • system environment • random

    property • application.properties • … Active Profiles Default Profiles ENV system prop application.yml ...
  37. Где вылетит эксепшен ctx.stop(); (1) ctx.start(); (2) ctx.close(); (3) ctx.start();

    (4) 1. на строчке (1) 2. на строчке (2) 3. на строчке (3) 4. на строчке (4)
  38. Где вылетит эксепшен ctx.stop(); (1) ctx.start(); (2) ctx.close(); (3) ctx.start();

    (4) 1. на строчке (1) 2. на строчке (2) 3. на строчке (3) 4. на строчке (4) 5. не вылетит вообще
  39. EnvironmentPostProcessor`s Application ContextInitializer`s Application ReadyEvent Тут начинается Spring Ripper Application

    StartingEvent Application EnvironmentPreparedEvent Application PreparedEvent Context RefreshedEvent EmbeddedServlet Container InitializedEvent
  40. Анатомия SpringBoot Jar jar |- META-INF |- BOOT-INF |- libs

    |- classes |- org |- springframework |- boot
  41. Анатомия SpringBoot Jar jar |- META-INF |- BOOT-INF |- libs

    |- classes |- org |- springframework |- boot MANIFEST.MF ... Spring-Boot-Version: 1.5.3.RELEASE Implementation-Vendor: Pivotal Software, Inc. Main-Class: org.springframework.boot.loader.JarLauncher Start-Class:ru….ripper.OurMainClass Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ ...
  42. jar |- META-INF |- BOOT-INF |- libs |- classes |-

    org |- springframework |- boot Анатомия SpringBoot Jar MANIFEST.MF ... Spring-Boot-Version: 1.5.3.RELEASE Implementation-Vendor: Pivotal Software, Inc. Main-Class: org.springframework.boot.loader.JarLauncher Start-Class:ru….ripper.OurMainClass Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ ...
  43. Анатомия SpringBoot Jar jar |- META-INF |- BOOT-INF |- libs

    |- classes |- org |- springframework |- boot MANIFEST.MF ... Spring-Boot-Version: 1.5.3.RELEASE Implementation-Vendor: Pivotal Software, Inc. Main-Class: org.springframework.boot.loader.JarLauncher Start-Class:ru….ripper.OurMainClass Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ ... JarLauncher
  44. Как выбирается mainClass если его не указать Spring boot plugin

    сканирует проект – ищет мейны • если есть только один – то это он :) • если больше одного – смотрит где стоит @SpringBootApplication и выбирает его • если @SpringBootApplication нет или >1 – ошибка о множественных main при сборке
  45. Хочу executable jar: Maven <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration>

    <executable>true</executable> </configuration> </plugin> </plugins> </build> springBoot { executable = true } Gradle
  46. Внутренний мир executable Jar • Бабушка хочет чтобы жмя и

    работало • windows “click click” • $ ./app.jar • ...
  47. Как выглядит Jar? Script 0xf4ra -> а это ZIP! jar

    archive Начало файла Начало zip архива
  48. Немного выводов 1. Spring Boot вышел за пределы Spring Context

    2. Starter от команды Spring отличаются от public convention ◦ не всегда в лучшую сторону 3. в Spring Boot это целый мир, в котором нет магии