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

Spring Boot Starter — как и зачем?

Spring Boot Starter — как и зачем?

Spring — уже не магия (спасибо «Spring-потрошителю» и Евгению Борисову), а вот Spring Boot довольно часто клеймят магической поделкой. Но многим нравится, особенно новичкам!

В докладе мы осветим следующее:

зачем вообще в рамках типовой компании, использующей Spring Boot, могут понадобиться собственные стартеры;
как скоро инквизиция приходит за новичками, если они бездумно используют готовые стартеры;
насколько Spring Boot самостоятелен и что это значит для разработчиков.
Доклад рассчитан на практикующих Spring (а лучше Spring Boot) инженеров, которые уже сталкивались с различными трудностями поддержки увесистой инфраструктуры, разрабатываемой с использованием Spring.

Kirill Tolkachev

March 04, 2018
Tweet

More Decks by Kirill Tolkachev

Other Decks in Technology

Transcript

  1. Spring Boot
    Starter City
    Как??? Зачем?!
    магический лагерь
    для похудания

    View Slide

  2. @tolkv
    @lavcraft
    @gorelikoff
    @gorelikov

    View Slide

  3. Цели
    ● Развеять миф о магии в Spring Boot

    View Slide

  4. View Slide

  5. Цели
    ● Развеять миф о магии в Spring Boot
    ● Понять принципы работы экосистемы стартеров

    View Slide

  6. Цели
    ● Развеять миф о магии в Spring Boot
    ● Понять принципы работы экосистемы стартеров
    ● Понять прикладной смысл Spring Boot Starter

    View Slide

  7. Spring уже не магия

    View Slide

  8. Spring Boot все еще магия

    View Slide

  9. Что такое
    Spring Boot?
    Эта jar-ник c
    Tomcat внутри
    Java interview 2014

    View Slide

  10. Java interview 2017
    Что такое
    Spring Boot?
    Эта хрень
    помогает
    проще собрать
    микросервис

    View Slide

  11. @Getter
    @Setter
    @Aspect
    @ToString
    @EnableWs
    @Endpoint
    @EnableWebMvc
    @EnableCaching
    @Configuration
    @RestController
    @XmlRootElement
    @EnableWebSocket
    @RedisHash("cat")
    @EnableScheduling
    @EnableWebSecurity
    @NoArgsConstructor
    @ContextConfiguration
    @SpringBootApplication
    @Accessors(chain = true)
    @EnableAspectJAutoProxy
    @EnableAutoConfiguration
    @EnableRedisRepositories
    @EnableWebSocketMessageBroker
    // generate getters
    // generate setters
    // we are an aspect
    // generate toString()
    // SOAP is so enterprisy, we definitely need it
    // Seriously, just read above
    // we want MVC
    // and we want to cache stuff
    // this class can configure itself
    // we want some REST
    // this component is marshallable
    // we want web socket, it's so new-generation
    // this class is an entity saved in redis
    // we want scheduled tasks
    // and some built-in security
    // generate no args constructor
    // we want context configuration for unit testing
    // this is a Sprint Boot application
    // getters/setters are chained (ala jQuery)
    // we want AspectJ auto proxy
    // and auto configuration
    // since it is an entity we want to enable spring data repositories for redis
    // we want a broker for web socket messages

    View Slide

  12. Но новичкам-то нравится

    View Slide

  13. Пока не приходит
    Spring Boot инквизиция

    View Slide

  14. Spring Boot
    слишком
    самостоятелен

    View Slide

  15. Откуда 436 spring beans?

    View Slide

  16. Откуда 436 spring beans?

    View Slide

  17. Давайте разберёмся в “магии”

    View Slide

  18. Смотрим код Spring Boot’a

    View Slide

  19. @SpringBootApplication

    View Slide

  20. @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 {

    View Slide

  21. @SpringBootApplication
    @EnableAutoConfiguration

    View Slide

  22. @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 {};
    }

    View Slide

  23. @SpringBootApplication
    @EnableAutoConfiguration
    ImportSelector

    View Slide

  24. AutoConfigurationImportSelector
    protected List getCandidateConfigurations(...) {
    List configurations = SpringFactoriesLoader.loadFactoryNames(
    getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
    ...
    return configurations;
    }

    View Slide

  25. @SpringBootApplication
    @EnableAutoConfiguration
    ImportSelector
    SpringFactoriesLoader

    View Slide

  26. spring-boot-autoconfigure.jar/spring.factories
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
    org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
    org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
    org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.\

    129 lines

    View Slide

  27. @SpringBootApplication
    @EnableAutoConfiguration
    ImportSelector
    SpringFactoriesLoader
    Starter#1 Starter#2 Starter#3

    View Slide

  28. Ваше
    приложение

    View Slide

  29. @ConditionalOnClass
    @ConditionalOnBean
    @ConditionalOnExpression
    @ConditionalOnProperty
    @ConditionalOnResource
    Загрузка при условии

    View Slide

  30. Как устроен стартер

    View Slide

  31. Autoconfigure

    View Slide

  32. Условия
    @Configuration
    @ConditionalOnBean({Client.class, DiscoveryClient.class})
    @EnableConfigurationProperties(value = APIVersionFilterProperties.class)
    public class DiscoveryAPIVersionFilterAutoConfiguration {
    @Bean
    public ApiVersionServerListFilter versionedFilter(DiscoveryClient discoveryClient,
    APIVersionFilterProperties properties) {
    return new APIVersionServerListFilter(properties.getServiceVersions(), discoveryClient);
    }
    ...
    }

    View Slide

  33. Условия
    @Configuration
    @ConditionalOnBean({Client.class, DiscoveryClient.class})
    @EnableConfigurationProperties(value = APIVersionFilterProperties.class)
    public class DiscoveryAPIVersionFilterAutoConfiguration {
    @Bean
    public ApiVersionServerListFilter versionedFilter(DiscoveryClient discoveryClient,
    APIVersionFilterProperties properties) {
    return new APIVersionServerListFilter(properties.getServiceVersions(), discoveryClient);
    }
    ...
    }

    View Slide

  34. Starter

    View Slide

  35. spring.factories
    org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
    ru.alfalab.discovery.version.DiscoveryAPIVersionFilterAutoConfiguration

    View Slide

  36. Вся магия
    1. @EnableAutoConfiguration
    2. @ConditionalOn…
    3. spring.factories

    View Slide

  37. Выводы часть #1
    ● все не так сложно
    ● лишних бинов нет, грузится только то, что надо

    View Slide

  38. Мы готовы писать свой стартер!
    Новичок #1
    Новичок #2 Новичок #3
    Новичок #4
    Странный
    новичок

    View Slide

  39. Стандартного набора
    хватит всем!
    Зачем писать свое?
    Опытный тех. лид

    View Slide

  40. Из чего состоит
    сервис?

    View Slide

  41. Из чего состоит
    сервис?
    ● API

    View Slide

  42. Из чего состоит
    сервис?
    ● API
    ● Clients
    ○ JSON
    ○ SOAP

    View Slide

  43. Из чего состоит
    сервис?
    ● API
    ● Clients
    ○ JSON
    ○ SOAP
    ● Cloud
    ○ Discovery
    ○ Config

    View Slide

  44. Из чего состоит
    сервис?
    ● API
    ● Clients
    ○ JSON
    ○ SOAP
    ● Cloud
    ○ Discovery
    ○ Config
    ● Metrics
    ○ Healthcheck
    ○ Logs

    View Slide

  45. Из чего состоит
    сервис?
    ● API
    ● Clients
    ○ JSON
    ○ SOAP
    ● Cloud
    ○ Discovery
    ○ Config
    ● Metrics
    ○ Healthcheck
    ○ Logs
    ● Data
    ● Stream

    View Slide

  46. Смотрим код сервиса

    View Slide

  47. Так это выглядит изнутри...

    View Slide

  48. Так это выглядит изнутри...

    View Slide

  49. Так это выглядит изнутри...

    View Slide

  50. Так это выглядит изнутри...

    View Slide

  51. Так это выглядит изнутри...

    View Slide

  52. Так это выглядит изнутри...

    View Slide

  53. Так это выглядит изнутри...

    View Slide

  54. Этим сервисам стоило бы
    похудеть

    View Slide

  55. Я не толстый!
    у меня кость широкая

    View Slide

  56. Так это выглядит изнутри...

    View Slide

  57. Что в этих классах

    View Slide

  58. Service discovery

    View Slide

  59. Межсервисное общение

    View Slide

  60. Межсервисное общение

    View Slide

  61. Простое общение с
    @FeignClient("cards-api")
    public interface P2PCardService {
    @RequestMapping(value = "/", method = RequestMethod.POST)
    CardDTO loadDetachedCard(String id);
    }

    View Slide

  62. Межсервисное общение
    2.0
    1.0
    1.0

    View Slide

  63. Межсервисное общение

    View Slide

  64. Межсервисное общение

    View Slide

  65. Отфильтруем друзей
    public class ServiceAPIVersionServerListFilter extends ZoneAffinityServerListFilter {
    ...
    @Override
    public List getFilteredListOfServers(List listOfServers) {
    if (listOfServers == null || listOfServers.isEmpty()) return listOfServers;
    List infos = this.discoveryClient.getInstances(listOfServers.first()
    .getMetaInfo()
    .getServiceIdForDiscovery());
    final List versionedInstance = infos.stream()
    .filter(instanceInfo -> !versions.containsKey(instanceInfo.getServiceId().toLowerCase())
    || checkVersion(versions.get(instanceInfo.getServiceId().toLowerCase()),
    instanceInfo.getMetadata().get(VERSION_FIELD)))
    .collect(Collectors.toList());
    return listOfServers.stream()
    .filter(server -> checkServiceInstance(server, versionedInstance))

    View Slide

  66. Отфильтруем друзей
    public class ServiceAPIVersionServerListFilter extends ZoneAffinityServerListFilter {
    ...
    @Override
    public List getFilteredListOfServers(List listOfServers) {
    if (listOfServers == null || listOfServers.isEmpty()) return listOfServers;
    List infos = this.discoveryClient.getInstances(listOfServers.first()
    .getMetaInfo()
    .getServiceIdForDiscovery());
    final List versionedInstance = infos.stream()
    .filter(instanceInfo -> !versions.containsKey(instanceInfo.getServiceId().toLowerCase())
    || checkVersion(versions.get(instanceInfo.getServiceId().toLowerCase()),
    instanceInfo.getMetadata().get(VERSION_FIELD)))
    .collect(Collectors.toList());
    return listOfServers.stream()
    .filter(server -> checkServiceInstance(server, versionedInstance))

    View Slide

  67. Отфильтруем друзей
    public class ServiceAPIVersionServerListFilter extends ZoneAffinityServerListFilter {
    ...
    @Override
    public List getFilteredListOfServers(List listOfServers) {
    if (listOfServers == null || listOfServers.isEmpty()) return listOfServers;
    List infos = this.discoveryClient.getInstances(listOfServers.first()
    .getMetaInfo()
    .getServiceIdForDiscovery());
    final List versionedInstance = infos.stream()
    .filter(instanceInfo -> !versions.containsKey(instanceInfo.getServiceId().toLowerCase())
    || checkVersion(versions.get(instanceInfo.getServiceId().toLowerCase()),
    instanceInfo.getMetadata().get(VERSION_FIELD)))
    .collect(Collectors.toList());
    return listOfServers.stream()
    .filter(server -> checkServiceInstance(server, versionedInstance))

    View Slide

  68. 68
    Нахуа?!

    View Slide

  69. В нашем сервисе есть что-то инородное

    View Slide

  70. Надо переносить в
    библиотеки

    View Slide

  71. Конфигурация фильтра
    eureka:
    client:
    registryFetchIntervalSeconds: 5
    serviceUrl:
    defaultZone: ${EUREKA_SERV:http://127.0.0.1:8763/eureka/}
    instance:
    virtualHostName : ${spring.application.name}
    metadataMap:
    instanceId: ${spring.application.name}:${random.value}
    versions: 1.0
    spring:
    discovery:
    filter:
    versions:
    someServiceId: 2.0

    View Slide

  72. Конфигурация фильтра
    eureka:
    client:
    registryFetchIntervalSeconds: 5
    serviceUrl:
    defaultZone: ${EUREKA_SERV:http://127.0.0.1:8763/eureka/}
    instance:
    virtualHostName : ${spring.application.name}
    metadataMap:
    instanceId: ${spring.application.name}:${random.value}
    versions: 1.0
    spring:
    discovery:
    filter:
    versions:
    someServiceId: 2.0

    View Slide

  73. Хочется видеть что-то такое
    spring:
    discovery:
    filter:
    versions:
    someOtherService: 2.0
    api:
    versions: 1.0,2.0

    View Slide

  74. EnvironmentPostProcessor
    public class PropertyTranslatorPostProcessor implements EnvironmentPostProcessor {
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment env,
    SpringApplication application) { }
    translateClientVersionProperty(env);
    translateZuulRoutes(env);
    }

    }

    View Slide

  75. EnvironmentPostProcessor`s
    Application
    ContextInitializer`s
    Application
    ReadyEvent
    Тут начинается
    Spring Ripper
    Application
    StartingEvent
    Application
    EnvironmentPreparedEvent
    Application
    PreparedEvent
    Context
    RefreshedEvent
    EmbeddedServlet
    Container
    InitializedEvent

    View Slide

  76. EnvironmentPostProcessor`s
    Application
    ContextInitializer`s
    Application
    ReadyEvent
    Тут начинается
    Spring Ripper
    Application
    StartingEvent
    Application
    EnvironmentPreparedEvent
    Application
    PreparedEvent
    Context
    RefreshedEvent
    EmbeddedServlet
    Container
    InitializedEvent

    View Slide

  77. EnvironmentPostProcessor`s
    Application
    ContextInitializer`s
    Application
    ReadyEvent
    Тут начинается
    Spring Ripper
    Application
    StartingEvent
    Application
    EnvironmentPreparedEvent
    Application
    PreparedEvent
    Context
    RefreshedEvent
    EmbeddedServlet
    Container
    InitializedEvent

    View Slide

  78. запуск
    SpringApplicationInitializer
    Локальные бины
    приложения созданы
    ApplicationEnvironment
    PreparedEvent
    запуск
    EnvironmentPostPorcessors
    Пользовательские бины
    создаются слишком поздно

    View Slide

  79. Межсервисное общение
    2.0
    1.0
    1.0

    View Slide

  80. Выводы часть#2
    Живешь со Spring - живи по правилам Spring.
    Доработка и расширение механизмов Spring должны быть в
    стартерах

    View Slide

  81. Все еще много лишнего?

    View Slide

  82. Все равно толстый!

    View Slide

  83. Первый шаг к похуданию

    View Slide

  84. SOAP Services and Apache CXF
    ServiceDefinition.wsdl
    xmlns:tns = "http://www.examples.com/wsdl/HelloService.w












    ...

    View Slide

  85. SOAP Services and Apache CXF
    ServiceDefinition.wsdl
    xmlns:tns = "http://www.examples.com/wsdl/HelloService.w











    ...

    Java Stub
    wsdl2java tool

    View Slide

  86. SOAP Services and Apache CXF
    ServiceDefinition.wsdl
    Java Stub
    wsdl2java tool

    View Slide

  87. SOAP Services and Apache CXF
    ServiceDefinition.wsdl
    Java Stub
    wsdl2java tool
    WS Stub in Spring Context
    Configure bean

    View Slide

  88. SOAP Services and Apache CXF
    ServiceDefinition.wsdl
    Java Stub
    wsdl2java tool
    WS Stub in Spring Context
    Configure bean

    address="${lb.address.base}/WSCodeClickDynamic
    address="${lb.address.base}/WSClickPaymentPass
    ...
    address="${ws.address.base}/WSCustomerAddress/
    serviceClass="ru.alfalab...wscustomerinfo9.WSC
    address="${lb.address.base}/WSCustomerInfo/WSC

    View Slide

  89. SOAP Services and Apache CXF
    ServiceDefinition.wsdl
    Java Stub
    wsdl2java tool
    WS Stub in Spring Context
    Configure bean

    address="${lb.address.base}/WSCodeClickDynamic
    address="${lb.address.base}/WSClickPaymentPass
    ...
    address="${ws.address.base}/WSCustomerAddress/
    serviceClass="ru.alfalab...wscustomerinfo9.WSC
    address="${lb.address.base}/WSCustomerInfo/WSC

    Call Bean
    Inject ws bean

    View Slide

  90. SOAP Services and Apache CXF
    Apache CXF
    ServiceDefinition.wsdl
    Java Stub
    wsdl2java tool
    WS Stub in Spring Context
    Configure bean
    Call Bean
    Inject ws bean

    View Slide

  91. SOAP Services and Apache CXF
    Apache CXF
    ServiceDefinition.wsdl
    Java Stub
    wsdl2java tool
    WS Stub in Spring Context
    Configure bean
    Call Bean
    Inject ws bean

    View Slide

  92. CXF Stub в библиотеки

    View Slide

  93. CXF Stub в библиотеки
    dependencies {
    compile 'ru.alfabank.ws:currency-stub:1.0.0'
    }

    View Slide

  94. CXF Stub в библиотеки
    ws-stub
    transfers-api
    cards-api
    ...-api
    dependencies {
    compile 'ru.alfabank.ws:currency-stub:1.0.0'
    }

    View Slide


  95. serviceClass="ru.alfalab...wscodedynamicfields32.WSCodeDynamicFields31PortType"
    address="${lb.address.base}/WSCodeClickDynamicFields/WSCodeDynamicFields11"/>
    serviceClass="ru.alfalab...wsclickpaymentpassword10.WSPaymentPassword13PortType"
    address="${lb.address.base}/WSClickPaymentPassword/WSPaymentPassword13"/>
    ...
    serviceClass="ru.alfalab...wscustomeraddress22.WSCustomerAddressCompletePortType"
    address="${ws.address.base}/WSCustomerAddress/WSCustomerAddressComplete22"/>
    serviceClass="ru.alfalab...wscustomerinfo9.WSCustomerInfo9PortType"
    address="${lb.address.base}/WSCustomerInfo/WSCustomerInfo9"/>

    WS – ты кто такой

    View Slide


  96. serviceClass="ru.alfalab...wscodedynamicfields32.WSCodeDynamicFields31PortType"
    address="${lb.address.base}/WSCodeClickDynamicFields/WSCodeDynamicFields11"/>
    serviceClass="ru.alfalab...wsclickpaymentpassword10.WSPaymentPassword13PortType"
    address="${lb.address.base}/WSClickPaymentPassword/WSPaymentPassword13"/>
    ...
    serviceClass="ru.alfalab...wscustomeraddress22.WSCustomerAddressCompletePortType"
    address="${ws.address.base}/WSCustomerAddress/WSCustomerAddressComplete22"/>
    serviceClass="ru.alfalab...wscustomerinfo9.WSCustomerInfo9PortType"
    address="${lb.address.base}/WSCustomerInfo/WSCustomerInfo9"/>

    WS – ты кто такой

    View Slide

  97. WS – ты кто такой
    transactions-api
    – ws.xml

    View Slide

  98. WS – ты кто такой
    transactions-api
    – ws.xml
    targeting-api
    – ws.xml

    View Slide

  99. WS – ты кто такой
    transactions-api
    – ws.xml
    targeting-api
    – ws.xml
    transfers-api
    – ws.xml
    ...

    View Slide

  100. 1. Добавить зависимость (WSDL/WS-STUB)
    2. Настроить бины в ws.xml
    3. Прописать адреса и настройки для добавленных сервисов
    ${lb.address.base}
    Как сделать новый RPC вызов

    View Slide

  101. Как сделать новый RPC вызов
    1. Добавить зависимость (WSDL/WS-STUB)
    2. Настроить бины в ws.xml
    3. Прописать адреса и настройки для добавленных сервисов
    ${lb.address.base}
    И как это делается?

    View Slide

  102. Как сделать новый RPC вызов
    1. Добавить зависимость (WSDL/WS-STUB)
    2. Настроить бины в ws.xml
    3. Прописать адреса и настройки для добавленных сервисов
    ${lb.address.base}
    И как это делается?
    Captain Copy-Paste

    View Slide

  103. А что нужно разработчикам?

    View Slide

  104. Сервис!

    View Slide

  105. Run Application
    ERROR [-,,,] 27393 --- [main] o.s.boot.SpringApplication: Application startup failed
    Error creating bean with name 'ru.alfabank...WSAccountClickPayment13PortType' defined in
    Apache CXF starter autoscan package: Add next properties to your application.yml file:
    spring.cxf:
    clients:
    -
    endpoint: http://SOME_HOST/SOME_PATH_TO_WS
    className: ru.alfabank.ws.cs.eq.wsaccountclickpayment13.WSAccountClickPayment13PortType

    View Slide

  106. ERROR [-,,,] 27393 --- [main] o.s.boot.SpringApplication: Application startup failed
    Error creating bean with name 'ru.alfabank...WSAccountClickPayment13PortType' defined in
    Apache CXF starter autoscan package: Add next properties to your application.yml file:
    spring.cxf:
    clients:
    -
    endpoint: http://SOME_HOST/SOME_PATH_TO_WS
    className: ru.alfabank.ws.cs.eq.wsaccountclickpayment13.WSAccountClickPayment13PortType
    Run Application

    View Slide

  107. ERROR [-,,,] 27393 --- [main] o.s.boot.SpringApplication: Application startup failed
    Error creating bean with name 'ru.alfabank...WSAccountClickPayment13PortType' defined in
    Apache CXF starter autoscan package: Add next properties to your application.yml file:
    spring.cxf:
    clients:
    -
    endpoint: http://SOME_HOST/SOME_PATH_TO_WS
    className: ru.alfabank.ws.cs.eq.wsaccountclickpayment13.WSAccountClickPayment13PortType
    Process finished with exit code 1
    Run Application

    View Slide

  108. Автоматизируем?
    Classpath
    @WebService
    WSAccountClickPayment13PortType
    @WebService
    WSCardsTransactions12PortType
    @Component
    MySuperService
    @Other
    ...

    View Slide

  109. Автоматизируем?
    Classpath
    @WebService
    WSAccountClickPayment13PortType
    @WebService
    WSCardsTransactions12PortType
    @Component
    MySuperService
    @Other
    ...
    Find
    WS Classes
    @WebService
    WSAccountClickPayment13PortType
    @WebService
    WSCardsTransactions12PortType

    View Slide

  110. Автоматизируем?
    Classpath
    @WebService
    WSAccountClickPayment13PortType
    @WebService
    WSCardsTransactions12PortType
    @Component
    MySuperService
    @Other
    ...
    Find
    WS Classes
    @WebService
    WSAccountClickPayment13PortType
    @WebService
    WSCardsTransactions12PortType
    Spring Context
    BeanDefinition + BeanFactory
    WSAccountClickPayment13PortType
    BeanDefinition + BeanFactory
    WSCardsTransactions12PortType
    Configure Context

    View Slide

  111. Автоматизируем?
    Spring Context
    BeanDefinition + BeanFactory
    WSAccountClickPayment13PortType
    BeanDefinition + BeanFactory
    WSCardsTransactions12PortType
    Inject
    Spring Context
    @Component
    MyService
    BeanInstance
    WSCardsTransactions12PortType
    Configure Properties and Endpoints

    View Slide

  112. Автоматизируем?
    Spring Context
    BeanDefinition + BeanFactory
    WSAccountClickPayment13PortType
    BeanDefinition + BeanFactory
    WSCardsTransactions12PortType
    Inject
    Spring Context
    @Component
    MyService
    BeanInstance
    WSCardsTransactions12PortType
    Configure Properties and Endpoints
    Или падает с ошибкой
    если не настроен endpoint
    FAIL-FAST
    Error creating bean with name
    'ru.alfabank...WSSomeServicet13PortType' defined in Apache CXF
    starter autoscan package: Add next properties to your
    application.yml file:
    spring.cxf:
    clients:
    -
    endpoint: http://SOME_HOST/SOME_PATH_TO_WS
    className: ru.alfabank....WSSomeServicet13PortType

    View Slide

  113. EPP нет, можно в обычную
    библиотеку?

    View Slide

  114. @Slf4j
    @Configuration
    @EnableConfigurationProperties
    @ConditionalOnProperty(name = "spring.cxf.client.enabled", matchIfMissing = true)
    public class CxfClientConfiguration {
    @Bean
    public CxfBeanDefinitionPostProcessor cxfBeanDefinitionPP(Environment environment) {
    return new CxfBeanDefinitionPostProcessor(environment);
    }
    @Bean
    public static BusWiringBeanFactoryPostProcessor jsr250BeanPostProcessor() {
    return new BusWiringBeanFactoryPostProcessor();
    }
    @Bean
    public static BusExtensionPostProcessor busExtensionPostProcessor() {
    return new BusExtensionPostProcessor();
    }
    @Slf4j
    @Configuration
    @ConditionalOnClass({SpringBus.class, JaxWsClientFactoryBean.class, ConfigurationPropertiesBindingPostProcessor.class})
    @EnableConfigurationProperties({CxfClientsProperties.class, WSConfiguration.class})
    public static class CxfClientFactoryAutoConfiguration {
    @Bean(name = CXF_WS_CLIENT_PROXY_FACTORY_DEFAULT_NAME)
    @ConditionalOnMissingBean(name = {CXF_WS_CLIENT_PROXY_FACTORY_DEFAULT_NAME})
    CxfWsStubBeanFactory proxyWsBeanFactory(
    CxfClientsProperties cxfClientsProperties,
    Bus bus,
    CxfInterceptorConfigurer interceptorConfigurer
    ) {
    return new CxfWsStubBeanFactory(
    cxfClientsProperties,
    bus,
    interceptorConfigurer
    );
    }
    Конфигурация библиотек
    @Bean
    @ConditionalOnMissingBean(CxfBusConfigurer.class)
    public CxfBusConfigurer cxfBusConfigurer(CxfClientsProperties cxfClientsProperties) {
    return new DefaultCxfBusConfigurer(cxfClientsProperties);
    }
    @Bean(destroyMethod = "shutdown")
    public Bus cxf(CxfBusConfigurer cxfBusConfigurer) {
    SpringBus bus = new SpringBus();
    cxfBusConfigurer.configure(bus);
    return bus;
    }
    @Bean
    @ConditionalOnMissingBean(CxfInterceptorConfigurer.class)
    public CxfInterceptorConfigurer cxfInterceptorConfigurer(
    CxfInterceptorAnnotationProcessor cxfInterceptorAnnotationProcessor,
    BeanFactory beanFactory
    ) {
    return new CxfInterceptorConfigurer(
    beanFactory,
    cxfInterceptorAnnotationProcessor.getGlobalInterceptors(),
    cxfInterceptorAnnotationProcessor.getSpecificInterceptors()
    );
    }
    @Bean
    @ConditionalOnMissingBean(CxfInterceptorAnnotationProcessor.class)
    public static CxfInterceptorAnnotationProcessor cxfInterceptorBFPP() {
    return new CxfInterceptorAnnotationProcessor();
    }
    static final String CXF_WS_CLIENT_PROXY_FACTORY_DEFAULT_NAME = "CxfWsClientProxyFactory";
    }
    }

    View Slide

  115. Конфигурация библиотек
    @Bean
    public CxfBeanDefinitionPostProcessor cxfBeanDefinitionPP(Environment env) {
    return new CxfBeanDefinitionPostProcessor(env);
    }
    @Bean
    public static BusWiringBeanFactoryPostProcessor jsr250BeanPostProcessor() {
    return new BusWiringBeanFactoryPostProcessor();
    }
    @Bean
    public static BusExtensionPostProcessor busExtensionPostProcessor() {
    return new BusExtensionPostProcessor();
    }

    View Slide

  116. Конфигурация библиотек
    Или, не дай бог :
    @ComponentScan(
    {
    "ru.alfa.cxf",
    "ru.alfa.discovery",
    "ru...",
    "ru..."
    ...
    }
    )

    View Slide

  117. Проблемы
    1. Необходимость знания кишков библиотек
    Капитан сложность
    Долой
    инверсию
    контроля!

    View Slide

  118. Проблемы
    Толстый сервис
    1. Необходимость знания кишков библиотек
    2. Лишние куски кода в самом сервисе

    View Slide

  119. Конфигурация стартеров внутри
    приложения

    View Slide

  120. Вывод часть #3
    Сложность конфигурации библиотеки иногда сопоставима со
    сложностью логики библиотеки

    View Slide

  121. Вывод часть #3
    Сложность конфигурации библиотеки иногда сопоставима со
    сложностью логики библиотеки
    Ее логичнее выносить в стартер

    View Slide

  122. Вывод часть #3
    Даешь инверсию контроля для компонентов Spring!

    View Slide

  123. Умным сервисам - умные библиотеки

    View Slide

  124. Похудел и накачался, но тормоз

    View Slide

  125. Бонусная секция

    View Slide

  126. Что нового в Spring 5?
    Context Indexer

    View Slide

  127. Просто добавь зависимость
    dependencies {
    compileOnly 'org.springframework:spring-context-indexer:5.0.1.RELEASE'
    }

    View Slide

  128. Просто добавь зависимость
    dependencies {
    compileOnly 'org.springframework:spring-context-indexer:5.0.1.RELEASE'
    }
    # META-INF/spring.components
    o.s.t.f.APIVersionServerFilter=org.springframework.stereotype.Component
    o.s.t.f.APIVersionFilterHelper=org.springframework.stereotype.Component
    ...

    View Slide

  129. Просто добавь зависимость
    dependencies {
    compileOnly 'org.springframework:spring-context-indexer:5.0.1.RELEASE'
    }
    # META-INF/spring.components
    o.s.t.f.APIVersionServerFilter=org.springframework.stereotype.Component
    o.s.t.f.APIVersionFilterHelper=org.springframework.stereotype.Component
    ...
    Классы связанный проаннотированный
    аннотацией Spring`а

    View Slide

  130. Просто добавь зависимость
    dependencies {
    compileOnly 'org.springframework:spring-context-indexer:5.0.1.RELEASE'
    }
    # META-INF/spring.components
    o.s.t.f.APIVersionServerFilter=org.springframework.stereotype.Component
    o.s.t.f.APIVersionFilterHelper=org.springframework.stereotype.Component
    ...
    Чем проаннотировано

    View Slide

  131. Выводы
    1. Не магия, а скрытые возможности
    2. Создавать стартеры просто и не грешно
    3. Ничего лишнего там не грузится и множество стартеров -
    не обязательно медленный старт
    4. Сложность перетекает постепенно и ее надо выносить из
    сервиса с бизнес-логикой по максимуму

    View Slide

  132. Выводы
    1. Не магия, а скрытые возможности
    2. Создавать стартеры просто и не грешно
    3. Ничего лишнего там не грузится и множество стартеров -
    не обязательно медленный старт
    4. Сложность перетекает постепенно и ее надо выносить из
    сервиса с бизнес-логикой по максимуму

    View Slide

  133. Выводы
    1. Не магия, а скрытые возможности
    2. Создавать стартеры просто и не грешно
    3. Ничего лишнего там не грузится и множество стартеров -
    не обязательно медленный старт
    4. Сложность перетекает постепенно и ее надо выносить из
    сервиса с бизнес-логикой по максимуму
    Все стало
    проще?
    Да ладно?!

    View Slide

  134. Это еще не конец
    Но он близко!

    View Slide

  135. Стартеры тоже бывают разные
    Starter#1 Starter#2 Starter#3 spring-boot-autoconfigure

    View Slide

  136. В чем разница?
    Starter#1
    Starter#2
    Starter#3

    View Slide

  137. В чем разница?
    Starter#1
    Starter#2
    Starter#3
    spring-boot-autoconfigure

    View Slide

  138. Gradle
    transactions-api:
    dependencies {
    compile 'hazelcast:3.6.9'
    compile 'redis:1.1.1'
    compile 'mongodb:3.6.0'
    compile 'hazelcast-starter:1.0.0'
    compile 'redis-starter:0.2.0'
    compile 'mongodb-starter:0.2.0'

    }
    task testReport(type: TestReport) { … }
    jacocoTestCoverageVerification { … }
    … ~200 lines

    View Slide

  139. Gradle
    transactions-api:
    dependencies {
    compile 'hazelcast:3.6.9'
    compile 'redis:1.1.1'
    compile 'mongodb:3.6.0'
    compile 'hazelcast-starter:1.0.0'
    compile 'redis-starter:0.2.0'
    compile 'mongodb-starter:0.2.0'

    }
    transactions-api:
    dependencies {
    compile 'hazelcast:3.6.9'
    compile 'redis:1.1.1'
    compile 'mongodb:3.6.0'
    compile 'fat-starter:100.50.0'
    }
    task testReport(type: TestReport) { … }
    jacocoTestCoverageVerification { … }
    … ~200 lines

    View Slide

  140. Толстеем

    View Slide

  141. Толстеем, Худеем

    View Slide

  142. Толстеем, Худеем, Толстеем

    View Slide

  143. Gradle
    transactions-api:
    dependencies {
    compile …
    compile …
    }
    offers-api:
    dependencies {
    compile …
    compile …
    }
    ...

    View Slide

  144. Gradle
    transactions-api:
    dependencies {
    compile …
    compile …
    }
    offers-api:
    dependencies {
    compile …
    compile …
    }
    ...
    accounts-api:
    dependencies {
    compile …
    compile …
    }
    settings-api:
    dependencies {
    compile …
    compile …
    }
    ...
    transfer-api:
    dependencies {
    compile …
    compile …
    }
    payment-api:
    dependencies {
    compile …
    compile …
    }
    ...

    View Slide

  145. Gradle
    transactions-api:
    dependencies {
    compile …
    compile …
    }
    offers-api:
    dependencies {
    compile …
    compile …
    }
    ...
    accounts-api:
    dependencies {
    compile …
    compile …
    }
    settings-api:
    dependencies {
    compile …
    compile …
    }
    ...
    transfer-api:
    dependencies {
    compile …
    compile …
    }
    payment-api:
    dependencies {
    compile …
    compile …
    }
    ...
    Captain
    Copy-Paste

    View Slide

  146. Сложно обновлять зависимости

    View Slide

  147. Сложно обновлять зависимости
    Все немного разные

    View Slide

  148. Управляем
    зависимостями

    View Slide

  149. build.gradle:
    apply plugin: "version.plugin"
    apply plugin: "maven.plugin"
    apply plugin: "check.plugin"
    apply plugin: "findbugs.plugin"
    apply plugin: "verify.plugin"
    apply plugin: "publish.plugin"
    apply plugin: "awesome.plugin"
    149
    Декларативный подход

    View Slide


  150. class MyPlugin implements Plugin {
    void apply(Project project) {
    project.configure {
    dependencies {
    compile …
    compile …
    }
    }
    }
    }
    Сила в композиции
    150

    View Slide


  151. class MyPlugin implements Plugin {
    void apply(Project project) {
    project.configure {
    dependencies {
    compile …
    compile …
    }
    }
    project.plugins.apply(AddGitTagPlugin)
    project.plugins.apply(UserInfoPlugin)
    }
    }
    Сила в композиции
    151

    View Slide


  152. class MyPlugin implements Plugin {
    void apply(Project project) {
    project.configure {
    dependencies {
    compile …
    compile …
    }
    }
    project.plugins.apply(AddGitTagPlugin)
    project.plugins.apply(UserInfoPlugin)
    project.tasks.withType(SomeTaskType) { //configure
    }
    }
    }
    152
    Сила в композиции

    View Slide

  153. Сила в композиции
    apply plugin: "your.plugin.all" //2.1.+
    153

    View Slide

  154. Сила в композиции
    apply plugin: "your.plugin.all" //2.1.+
    154
    Автоапдейт минорных версий
    Путь героев – но это уже другая история

    View Slide

  155. Вывод ...последний
    На больших проектах даже умными зависимостями надо
    управлять

    View Slide

  156. Вопросы?

    View Slide