Share
Spring Boot или новые методы решения новых проблем
«Магия - это наука, которую мы не успели осознать» А.Ч. Кларк
Чем отличается наука от магии - кажется, что логичностью и пониманием. Давайте попробуем еще раз посмотреть на механизмы Spring Boot, понять как это работает и что оно может нам дать.
Никакого хардкора, обычная логика и истории решения проблем, которые возникали на наших проектах при переходе на микросервисы. Как с этими проблемами справлялся Spring Boot, какие существуют альтернативные подходы и инструменты? Меняется ли Spring Boot, чтобы не отстать в вечно изменяющейся отрасли?
Все выводы субъективные, но можем обсудить :)
В докладе освещено следующее:
самостоятелен ли Spring Boot и что это значит для разработчиков;
как не захлебнуться при поддержке 100+ компонентов вашей системы
чем “умная” библиотека может быть лучше обычной и в каких случаях она может быть полезна;
зачем вообще в рамках типовой компании, использующей Spring Boot, могут понадобиться собственные стартеры;
что такое перетекающая сложность и как ей управлять с помощью Spring Boot, Gradle и других инструментов
Spring Boot, сколько можно тормозить?
Доклад рассчитан на практикующих Spring (а лучше Spring Boot) инженеров, которые уже сталкивались с различными трудностями поддержки увесистой модульной/микросервисной инфраструктуры и хотят это обсудить.
#springboot #gradle #microservices #architecture #expirience
Kirill Tolkachev
Transcript
376 slides
-
Цели Мы пошли в микросервисы, какие проблемы ожидать и как
их решать? Чем разработчику могут помочь Spring Boot и другие инструменты, и какие могут быть проблемы? -
Disclaimer Слушайте нас, но используйте свои мозги Писать код не
будем – есть доки Только опыт, логика и выводы -
План 1. Spring boot 2. Spring Boot 3. Что-то еще
4. Перерыв 5. Spring Boot 6. Выводы -
И как выбирают? Корпоративный стандарт 1. Только Java 2. Только
Java EE 3. Только Oracle 4. Только <ENTERPRISE_ПРОДУКТ> От компании -
И как выбирают? Корпоративный стандарт 1. Только Java 2. Только
Java EE 3. Только Oracle 4. Только <ENTERPRISE_ПРОДУКТ> Какой стандарт? 1. Go 2. Rust 3. Unicorn 4. Смузи От компании От разработчика -
Ну, а если серьезно? • Обладает нужной функциональностью • Вписывается
в нефункциональные требования • Поддержка и количество тем на stackoverflow • Знания и интересы команды -
Серьезно? • Обладает нужной функциональностью • Вписывается в нефункциональные требования
• Поддержка и количество тем на stackoverflow • Знания и интересы команды -
Что такое Spring Boot? Эта jar-ник c Tomcat внутри Java
interview 2014 -
Java interview 2017 Что такое Spring Boot? Эта хрень помогает
проще собрать микросервис -
@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 -
@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 { … -
@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 {}; } -
AutoConfigurationImportSelect or protected List<String> getCandidateConfigurations(...) { List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); ... return configurations; } -
Из чего состоит сервис? • API • Clients ◦ JSON
◦ SOAP • Cloud ◦ Discovery ◦ Config -
Из чего состоит сервис? • API • Clients ◦ JSON
◦ SOAP • Cloud ◦ Discovery ◦ Config • Metrics ◦ Healthcheck ◦ Logs -
Из чего состоит сервис? • API • Clients ◦ JSON
◦ SOAP • Cloud ◦ Discovery ◦ Config • Metrics ◦ Healthcheck ◦ Logs • Data • Stream -
Простое общение с @FeignClient("cards-api") public interface P2PCardService { @RequestMapping(value =
"/", method = RequestMethod.POST) CardDTO loadDetachedCard(String id); } -
Отфильтруем друзей public class ServiceAPIVersionServerListFilter extends ZoneAffinityServerListFilter<Server> { ... @Override
public List<Server> getFilteredListOfServers(List<Server> listOfServers) { if (listOfServers == null || listOfServers.isEmpty()) return listOfServers; List<ServiceInstance> infos = this.discoveryClient.getInstances(listOfServers.first() .getMetaInfo() .getServiceIdForDiscovery()); final List<ServiceInstance> 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)) -
Отфильтруем друзей public class ServiceAPIVersionServerListFilter extends ZoneAffinityServerListFilter<Server> { ... @Override
public List<Server> getFilteredListOfServers(List<Server> listOfServers) { if (listOfServers == null || listOfServers.isEmpty()) return listOfServers; List<ServiceInstance> infos = this.discoveryClient.getInstances(listOfServers.first() .getMetaInfo() .getServiceIdForDiscovery()); final List<ServiceInstance> 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)) -
Отфильтруем друзей public class ServiceAPIVersionServerListFilter extends ZoneAffinityServerListFilter<Server> { ... @Override
public List<Server> getFilteredListOfServers(List<Server> listOfServers) { if (listOfServers == null || listOfServers.isEmpty()) return listOfServers; List<ServiceInstance> infos = this.discoveryClient.getInstances(listOfServers.first() .getMetaInfo() .getServiceIdForDiscovery()); final List<ServiceInstance> 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)) -
Много странного кода public class ServiceAPIVersionServerListFilter extends ZoneAffinityServerListFilter<Server> { ...
@Override public List<Server> getFilteredListOfServers(List<Server> listOfServers) { if (listOfServers == null || listOfServers.isEmpty()) return listOfServers; List<ServiceInstance> infos = this.discoveryClient.getInstances(listOfServers.first() .getMetaInfo() .getServiceIdForDiscovery()); final List<ServiceInstance> 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)) -
Конфигурация фильтра 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", "2.0"] spring: discovery: filter: versions: someServiceId: 1.0 -
Конфигурация фильтра eureka: client: metadataMap: versions: ["1.0", "2.0"] spring: discovery:
filter: versions: someServiceId: 1.0 -
Конфигурация фильтра eureka: client: metadataMap: versions: ["1.0", "2.0"] spring: discovery:
filter: versions: someServiceId: 1.0 2.0 1.0 -
Конфигурация фильтра eureka: client: metadataMap: versions: ["1.0", "2.0"] spring: discovery:
filter: versions: someServiceId: 1.0 1.0 2.0 1.0 2.0 -
Конфигурация фильтра eureka: client: metadataMap: versions: ["1.0", "2.0"] spring: discovery:
filter: versions: someServiceId: 1.0 -
Хочется видеть что-то такое spring: discovery: filter: versions: someServiceId: 1.0
api: versions: ["1.0", "2.0"] -
Надо просто переложить spring: discovery: api: versions: ["1.0", "2.0"] eureka:
client: metadataMap: versions: ["1.0", "2.0"] -
spring: discovery: filter: versions: someServiceId: 1.0 api: versions: ["1.0", "2.0"]
Хочется видеть что-то такое И проверять!!! -
ERROR [-,,,] 27393 --- [main] o.s.boot.SpringApplication: Application startup failed Error
java.lang.IllegalStateException: Version of supported API should be specified, e.g.: spring: discovery: api: versions: ["1.0", "2.0"] Run Application -
EnvironmentPostProcessor public class PropertyTranslatorPostProcessor implements EnvironmentPostProcessor { @Override public void
postProcessEnvironment(ConfigurableEnvironment env, SpringApplication application) { } translateClientVersionProperty(env); translateZuulRoutes(env); } … } -
@Configuration public class DiscoveryVersionFilterConfiguration { @Bean public void propertyTranslator() {
} return new PropertyTranslatorPostProcessor(); } … } Создаём EPP -
EnvironmentPostProcessor`s Application ContextInitializer`s Application ReadyEvent Тут начинается Spring Ripper Application
StartingEvent Application EnvironmentPreparedEvent Application PreparedEvent Context RefreshedEvent EmbeddedServlet Container InitializedEvent -
EnvironmentPostProcessor`s Application ContextInitializer`s Application ReadyEvent Тут начинается Spring Ripper Application
StartingEvent Application EnvironmentPreparedEvent Application PreparedEvent Context RefreshedEvent EmbeddedServlet Container InitializedEvent -
EnvironmentPostProcessor`s Application ContextInitializer`s Application ReadyEvent Тут начинается Spring Ripper Application
StartingEvent Application EnvironmentPreparedEvent Application PreparedEvent Context RefreshedEvent EmbeddedServlet Container InitializedEvent -
запуск SpringApplicationInitializer Локальные бины приложения созданы ApplicationEnvironment PreparedEvent запуск EnvironmentPostPorcessors
Пользовательские бины создаются слишком поздно -
Условия @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); } ... } -
Конфигурация @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); } ... } -
Вся магия 1. @EnableAutoConfiguration 2. @ConditionalOn… 3. spring.factories Все не
так сложно, как кажется -
Подробнее – смотри доклад Boot Yourself, Spring is Coming https://habr.com/ru/company/jugru/blog/424503/
https://habr.com/ru/company/jugru/blog/425333/ -
Дело не в spring-cloud → Умное логирование → Web-фильтры с
доп. логикой → Любые другие расширения базовых механизмов -
Доработка и расширение механизмов Spring должны быть в стартерах используй
инструменты в соответствии с уже заложенными принципами -
SOAP Services and Apache CXF ServiceDefinition.wsdl <definitions name = "HelloService"
... xmlns:tns = "http://www.examples.com/wsdl/HelloService.w <message name = "SayHelloRequest"> <part name = "firstName" type = "xsd:string"/> </message> <message name = "SayHelloResponse"> <part name = "greeting" type = "xsd:string"/> </message> <portType name = "Hello_PortType"> <operation name = "sayHello"> <input message = "tns:SayHelloRequest"/> <output message = "tns:SayHelloResponse"/> </operation> </portType> ... <service name = "Hello_Service"> -
SOAP Services and Apache CXF ServiceDefinition.wsdl <definitions name = "HelloService"
... xmlns:tns = "http://www.examples.com/wsdl/HelloService.w <message name = "SayHelloRequest"> <part name = "firstName" type = "xsd:string"/> </message <message name = "SayHelloResponse"> <part name = "greeting" type = "xsd:string"/> </message> <portType name = "Hello_PortType"> <operation name = "sayHello"> <input message = "tns:SayHelloRequest"/> <output message = "tns:SayHelloResponse"/> </operation> </portType> ... <service name = "Hello_Service"> Java Stub wsdl2java tool -
SOAP Services and Apache CXF ServiceDefinition.wsdl Java Stub wsdl2java tool
WS Stub in Spring Context Configure bean -
SOAP Services and Apache CXF ServiceDefinition.wsdl Java Stub wsdl2java tool
WS Stub in Spring Context Configure bean <beans> <jaxws:client id="codeClickDynamicFieldsWS" name="codeDynami serviceClass="ru.alfalab...wscodedynamicfields address="${lb.address.base}/WSCodeClickDynamic <jaxws:client id="clickPaymentPasswordWS" name="clickPayment serviceClass="ru.alfalab...wsclickpaymentpassw address="${lb.address.base}/WSClickPaymentPass ... <jaxws:client id="customerAddressCompleteWS" name="customerA serviceClass="ru.alfalab...wscustomeraddress22 address="${ws.address.base}/WSCustomerAddress/ <jaxws:client id="customerBaseInfoWS" name="customerInfo" serviceClass="ru.alfalab...wscustomerinfo9.WSC address="${lb.address.base}/WSCustomerInfo/WSC </beans> -
SOAP Services and Apache CXF ServiceDefinition.wsdl Java Stub wsdl2java tool
WS Stub in Spring Context Configure bean <beans> <jaxws:client id="codeClickDynamicFieldsWS" name="codeDynami serviceClass="ru.alfalab...wscodedynamicfields address="${lb.address.base}/WSCodeClickDynamic <jaxws:client id="clickPaymentPasswordWS" name="clickPayment serviceClass="ru.alfalab...wsclickpaymentpassw address="${lb.address.base}/WSClickPaymentPass ... <jaxws:client id="customerAddressCompleteWS" name="customerA serviceClass="ru.alfalab...wscustomeraddress22 address="${ws.address.base}/WSCustomerAddress/ <jaxws:client id="customerBaseInfoWS" name="customerInfo" serviceClass="ru.alfalab...wscustomerinfo9.WSC address="${lb.address.base}/WSCustomerInfo/WSC </beans> Call Bean Inject ws bean -
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 -
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 -
CXF Stub в библиотеки ws-stub transfers-api cards-api ...-api dependencies {
compile 'ru.alfabank.ws:currency-stub:1.0.0' } -
<beans> <jaxws:client id="codeClickDynamicFieldsWS" name="codeDynamicFields" serviceClass="ru.alfalab...wscodedynamicfields32.WSCodeDynamicFields31PortType" address="${lb.address.base}/WSCodeClickDynamicFields/WSCodeDynamicFields11"/> <jaxws:client id="clickPaymentPasswordWS" name="clickPaymentPassword" serviceClass="ru.alfalab...wsclickpaymentpassword10.WSPaymentPassword13PortType"
address="${lb.address.base}/WSClickPaymentPassword/WSPaymentPassword13"/> ... <jaxws:client id="customerAddressCompleteWS" name="customerAddressComplete" serviceClass="ru.alfalab...wscustomeraddress22.WSCustomerAddressCompletePortType" address="${ws.address.base}/WSCustomerAddress/WSCustomerAddressComplete22"/> <jaxws:client id="customerBaseInfoWS" name="customerInfo" serviceClass="ru.alfalab...wscustomerinfo9.WSCustomerInfo9PortType" address="${lb.address.base}/WSCustomerInfo/WSCustomerInfo9"/> </beans> WS – ты кто такой -
<beans> <jaxws:client id="codeClickDynamicFieldsWS" name="codeDynamicFields" serviceClass="ru.alfalab...wscodedynamicfields32.WSCodeDynamicFields31PortType" address="${lb.address.base}/WSCodeClickDynamicFields/WSCodeDynamicFields11"/> <jaxws:client id="clickPaymentPasswordWS" name="clickPaymentPassword" serviceClass="ru.alfalab...wsclickpaymentpassword10.WSPaymentPassword13PortType"
address="${lb.address.base}/WSClickPaymentPassword/WSPaymentPassword13"/> ... <jaxws:client id="customerAddressCompleteWS" name="customerAddressComplete" serviceClass="ru.alfalab...wscustomeraddress22.WSCustomerAddressCompletePortType" address="${ws.address.base}/WSCustomerAddress/WSCustomerAddressComplete22"/> <jaxws:client id="customerBaseInfoWS" name="customerInfo" serviceClass="ru.alfalab...wscustomerinfo9.WSCustomerInfo9PortType" address="${lb.address.base}/WSCustomerInfo/WSCustomerInfo9"/> </beans> WS – ты кто такой -
WS – ты кто такой transactions-api – ws.xml targeting-api –
ws.xml transfers-api – ws.xml ... -
1. Добавить зависимость (WSDL/WS-STUB) 2. Настроить бины в ws.xml 3.
Прописать адреса и настройки для добавленных сервисов ${lb.address.base} Как сделать новый RPC вызов -
Как сделать новый RPC вызов 1. Добавить зависимость (WSDL/WS-STUB) 2.
Настроить бины в ws.xml 3. Прописать адреса и настройки для добавленных сервисов ${lb.address.base} И как это делается? -
Как сделать новый RPC вызов 1. Добавить зависимость (WSDL/WS-STUB) 2.
Настроить бины в ws.xml 3. Прописать адреса и настройки для добавленных сервисов ${lb.address.base} И как это делается? Captain Copy-Paste -
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 -
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 -
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 -
Автоматизируем? Classpath @WebService WSAccountClickPayment13PortType @WebService WSCardsTransactions12PortType @Component MySuperService @Other ...
Find WS Classes @WebService WSAccountClickPayment13PortType @WebService WSCardsTransactions12PortType -
Автоматизируем? 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 -
Автоматизируем? Spring Context BeanDefinition + BeanFactory WSAccountClickPayment13PortType BeanDefinition + BeanFactory
WSCardsTransactions12PortType Inject Spring Context @Component MyService BeanInstance WSCardsTransactions12PortType Configure Properties and Endpoints -
Автоматизируем? 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 -
@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"; } } -
@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"; } } -
Конфигурация библиотек @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(); } -
Как все это подключать? • Чтение документации • Debug •
Найти того, кто это писал... -
Конфигурация библиотек Или, не дай бог : @ComponentScan( { "ru.alfa.cxf",
"ru.alfa.discovery", "ru...", "ru..." ... } ) -
Проблемы Толстый сервис 1. Необходимость знания кишков библиотек 2. Лишние
куски кода в самом сервисе 3. Случайное подхваченные бины -
Не в SOAP дело • Thrift/gRPC • Netty • Любая
сложная или устаревшая внешняя зависимость -
Сложность конфигурации библиотеки сопоставима со сложностью логики библиотеки Даешь инверсию
контроля не только для компонентов Spring! -
А какие ещё стартеры бывают? • Tracing • Logging •
Health indicators • Monitoring • Etc -
А какие ещё стартеры бывают? • Tracing • Logging •
Health indicators • Monitoring • Etc • Какие-то ваши стартеры -
Э не не не. Нам нужны свои 1. Не для
всего есть стартеры 2. Мы можем их тестировать -
• ApplicationContextRunner • @SpringBootTest • Всё остальное тестируется как обычный
spring код Spring Boot Test -
Структура +-- starter +--- autoconfigure +--- starter \--- examples +--
demo-spec-0 +-- demo-spec-1 \-- demo-spec-N -
Структура +-- starter +--- autoconfigure +--- starter \--- examples +--
demo-spec-0 +-- demo-spec-1 \-- demo-spec-N + spec code + tests -
Структура +-- starter +--- autoconfigure +--- starter \--- examples +--
demo-spec-0 +-- demo-spec-1 \-- demo-spec-N + spec code + tests -
Структура +-- starter +--- autoconfigure +--- starter \--- examples +--
demo-spec-0 +-- demo-spec-1 \-- demo-spec-N + spec code + tests -
Структура +-- starter +--- autoconfigure +--- starter \--- examples +--
demo-spec-0 +-- demo-spec-1 \-- demo-spec-N + spec code + tests -
Failed to load ApplicationContext Caused by: org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition
with name 'ru.some.pack.client.SomeClient' defined in null: Could not resolve placeholder some.url' in value "http://${some.url}"; nested exception is java.lang.IllegalArgumentException: Could not resolve placeholder some.url' in value "http://${some.url}" Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder some.url' in value "http://${some.url}" Первое использование -
starter/autoconfigure — tests Unit — per class Component — per
functionality/per spec -
Starter component tests Сформировать часть контекста нам помогут: 1. @SpringBootTest
2. @TestConfiguration 3. @ContextConfiguration 4. ApplicationContextRunner -
@SpringBootTest @SpringBootTest(classes = [ContextPartConfiguration]) class CxfConnectionTimeoutSpec extends Specification { @Requires({
!os.isMacOs() }) def 'should apply timeout to ASYNC requests'() { when: client.sayHelloAsync('foo', { res -> }).get() then: ExecutionException ex = thrown() ex.cause.class == SocketTimeoutException } ... -
@SpringBootTest @SpringBootTest(classes = [ContextPartConfiguration]) class CxfConnectionTimeoutSpec extends Specification { ...
@Requires({ os.isMacOs() }) def 'should apply timeout to ASYNC requests for OSX'() { when: client.sayHelloAsync('foo', { res -> }).get() then: ExecutionException ex = thrown() ex.cause.class == ConnectException } -
@ContextConfiguration @ContextConfiguration(classes = ValidConfiguration) class InterceptorConfigurerTest extends Specification { @Autowired
CxfInterceptorConfigurer interceptorConfigurer def "should add interceptors to JaxWsProxyFactoryBean"() { when: interceptorConfigurer.configure(...) then: ... } } -
Мы готовы писать свой стартер! Новичок #1 Новичок #2 Новичок
#3 Новичок #4 Странный новичок -
Есть три пути Далёкое Будущее Инфраструктурный путь Альтернативное Будущее Хакерский
путь Суровое настоящее CI/CD путь -
Далекое будущее... или не очень K U B E R
N E T E S S I D E C A R -
Kubernetes Sidecar POD Application container Sidecar container Могут быть: •
общая файловая система • общая сеть -
Не везде работает • Tracing покрывается не до конца -
коннекты к БД и MQ не обслуживаются -
Не везде работает • Tracing покрывается не до конца -
коннекты к БД и MQ не обслуживаются • Логи покрываются не до конца - приложение в любом случае должно выдавать структурированный лог -
Просто контейнеры Любой оркестратор: • Kubernetes • Mesos • Swarm
• Bash + puppet • Что угодно запускающее контейнер на хосте -
Добавляем стартеры transactions-api/build.gradle: dependencies { compile 'hazelcast-starter:1.0.0' compile 'redis-starter:0.2.0' compile
'mongodb-starter:0.2.0' … } // → 'hazelcast:3.6.9' // → 'redis:1.1.1' // → 'mongodb:3.6.0' Транзитивные зависимости -
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' … } -
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' } -
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' } -
Gradle transactions-api: dependencies { compile … compile … } offers-api:
dependencies { compile … compile … } ... -
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 … } ... -
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 -
'hazelcast:3.6.9' 'redis:1.1.1' Указываем версии 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0' 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0'
'hazelcast-starter:1.0.0' 'redis-starter:0.2.0' 'mongodb-starter:0.2.0' -
'hazelcast:3.6.9' 'redis:1.1.1' Указываем версии 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0' 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0'
'hazelcast-starter:1.0.0' 'redis-starter:0.2.0' 'mongodb-starter:0.2.0' Ну такое.. -
'hazelcast:3.6.9' 'redis:1.1.1' Указываем версии 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0' 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0'
'hazelcast-starter:1.0.0' 'redis-starter:0.2.0' 'mongodb-starter:0.2.0' Ну такое.. Буду складывать jar`ы в VCS -
Все немного разные 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0' 'hazelcast-starter:1.0.0' 'redis-starter:0.2.0' 'mongodb-starter:0.2.0' 'elasticsearch-starter:1.2.0
'mesos-starter:0.1.0' 'arangodb-starter:0.2.0' 'eureka-starter:1.8.3' 'sleuth-starter:1.0.8' 'zipkin-starter:3.1.1' 'jcache-starter:2.4.1' 'actuator-starter:0.1.0' 'aop-starter:0.2.0' 'logging-starter:1.8.3' 'freemarker-starter:1.0.8' 'validation-starter:3.1.1' 'tomcat-starter:3.1.1' 'etc-starter:0.1.0' -
Все немного разные 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0' 'hazelcast-starter:1.0.0' 'redis-starter:0.2.0' 'mongodb-starter:0.2.0' 'elasticsearch-starter:1.2.0
'mesos-starter:0.1.0' 'arangodb-starter:0.2.0' 'eureka-starter:1.8.3' 'sleuth-starter:1.0.8' 'zipkin-starter:3.1.1' 'jcache-starter:2.4.1' 'actuator-starter:0.1.0' 'aop-starter:0.2.0' 'logging-starter:1.8.3' 'freemarker-starter:1.0.8' 'validation-starter:3.1.1' 'tomcat-starter:3.1.1' 'etc-starter:0.1.0' -
Все немного разные 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0' 'hazelcast-starter:1.0.0' 'redis-starter:0.2.0' 'mongodb-starter:0.2.0' 'elasticsearch-starter:1.2.0
'mesos-starter:0.1.0' 'arangodb-starter:0.2.0' 'eureka-starter:1.8.3' 'sleuth-starter:1.0.8' 'zipkin-starter:3.1.1' 'jcache-starter:2.4.1' 'actuator-starter:0.1.0' 'aop-starter:0.2.0' 'logging-starter:1.8.3' 'freemarker-starter:1.0.8' 'validation-starter:3.1.1' 'tomcat-starter:3.1.1' 'etc-starter:0.1.0' -
Все немного разные 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0' 'hazelcast-starter:1.0.0' 'redis-starter:0.2.0' 'mongodb-starter:0.2.0' 'elasticsearch-starter:1.2.0
'mesos-starter:0.1.0' 'arangodb-starter:0.2.0' 'eureka-starter:1.8.3' 'sleuth-starter:1.0.8' 'zipkin-starter:3.1.1' 'jcache-starter:2.4.1' 'actuator-starter:0.1.0' 'aop-starter:0.2.0' 'logging-starter:1.8.3' 'freemarker-starter:1.0.8' 'validation-starter:3.1.1' 'tomcat-starter:3.1.1' 'etc-starter:0.1.0' -
Все немного разные 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0' 'hazelcast-starter:1.0.0' 'redis-starter:0.2.0' 'mongodb-starter:0.2.0' 'elasticsearch-starter:1.2.0
'mesos-starter:0.1.0' 'arangodb-starter:0.2.0' 'eureka-starter:1.8.3' 'sleuth-starter:1.0.8' 'zipkin-starter:3.1.1' 'jcache-starter:2.4.1' 'actuator-starter:0.1.0' 'aop-starter:0.2.0' 'logging-starter:1.8.3' 'freemarker-starter:1.0.8' 'validation-starter:3.1.1' 'tomcat-starter:3.1.1' 'etc-starter:0.1.0' -
Все немного разные 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0' 'hazelcast-starter:1.0.0' 'redis-starter:0.2.0' 'mongodb-starter:0.2.0' 'elasticsearch-starter:1.2.0
'mesos-starter:0.1.0' 'arangodb-starter:0.2.0' 'eureka-starter:1.8.3' 'sleuth-starter:1.0.8' 'zipkin-starter:3.1.1' 'jcache-starter:2.4.1' 'actuator-starter:0.1.0' 'aop-starter:0.2.0' 'logging-starter:1.8.3' 'freemarker-starter:1.0.8' 'validation-starter:3.1.1' 'tomcat-starter:3.1.1' 'etc-starter:0.1.0' -
Все немного разные 'hazelcast:3.6.9' 'redis:1.1.1' 'mongodb:3.6.0' 'hazelcast-starter:1.0.0' 'redis-starter:0.2.0' 'mongodb-starter:0.2.0' 'elasticsearch-starter:1.2.0
'mesos-starter:0.1.0' 'arangodb-starter:0.2.0' 'eureka-starter:1.8.3' 'sleuth-starter:1.0.8' 'zipkin-starter:3.1.1' 'jcache-starter:2.4.1' 'actuator-starter:0.1.0' 'aop-starter:0.2.0' 'logging-starter:1.8.3' 'freemarker-starter:1.0.8' 'validation-starter:3.1.1' 'tomcat-starter:3.1.1' 'etc-starter:0.1.0' -
Выход №1 – latest version dependencies { implementation 'some-starter:0.1.0' //
→ 'some-starter:+' implementation 'marathon-starter:0.1.0' // → 'marathon-starter:+' ... } -
Выход №1 – latest version + locking dependencyLocking { lockAllConfigurations()
} dependencies { implementation 'some-starter:0.1.0' // → 'some-starter:+' implementation 'marathon-starter:0.1.0' // → 'marathon-starter:+' ... } -
Выход №1 – latest version + locking $ ./gradlew build
--write-locks -
$ ./gradlew build --write-locks $ ./gradlew build --update-locks ru.company:starter Выход
№1 – latest version + locking -
Выход №1 – latest version + locking $ ./gradlew build
--write-locks $ ./gradlew build --update-locks ru.company:starter $ git status modified: gradle/dependency-locks/annotationProcessor.lockfile modified: gradle/dependency-locks/buildscript-classpath.lockfile modified: gradle/dependency-locks/compileClasspath.lockfile modified: gradle/dependency-locks/runtimeClasspath.lockfile modified: gradle/dependency-locks/testAnnotationProcessor.lockfile modified: gradle/dependency-locks/testCompileClasspath.lockfile modified: gradle/dependency-locks/testRuntimeClasspath.lockfile -
Выход №1 – latest version + locking diff ./gradle/dependency-locks/buildscript-classpath.lockfile: -com.netflix.nebula:nebula-publishing-plugin:11.0.0
-com.netflix.nebula:nebula-release-plugin:10.1.1 +com.netflix.nebula:nebula-publishing-plugin:12.0.0 +com.netflix.nebula:nebula-release-plugin:10.1.2 -
rebuild rebuild rebuild rebuild rebuild rebuild Выход №1 – latest
version + locking update-lock -
rebuild rebuild rebuild rebuild rebuild rebuild Выход №1 – latest
version + locking update-lock -
rebuild rebuild rebuild rebuild rebuild rebuild Выход №1 – latest
version + locking update-lock -
Хей, у нас новые новая система метрик! Пример Инф раструктура
Но я не умею экспортировать метрики в таком формате -
Хей, у нас новые новая система метрик! Пример Но я
не умею экспортировать метрики в таком формате Инф раструктура Ща мы тебя пересоберём -
Ща мы тебя пересобирём Пример Инф раструктура Теперь я умею
экспортироавть Метрики в нужном формате -
Ща мы тебя пересобирём Теперь я умею экспортироавть Метрики в
нужном формате Пример Инф раструктура Ща мы тебя задеплоим! -
Ща мы тебя пересобирём Теперь я умею экспортироавть Метрики в
нужном формате Пример Инф раструктура Ща мы тебя задеплоим! -
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" 290 Декларативный подход -
• class MyPlugin implements Plugin<Project> { void apply(Project project) {
project.configure { dependencies { compile … compile … } } } } Сила в композиции 291 -
Почему не хватает BOM? dependencies { gems 'asciidoctor-diagram:1.5.4.1' } asciidoctorj
{ version = '1.5.6' } task cleanTempDirs(type: Delete) { delete fileTree(dir: docsDir) } artifactoryPublish { properties = [artifactType: 'DOC'] } asciidoctor { finalizedBy tasks.withType(Zip) sources { include fileTree(dir: 'src/docs', includ include fileTree(dir: snippetsDir, inclu } dependsOn jrubyPrepare, project(':app').test gemPath = jrubyPrepare.outputDir requires = ['asciidoctor-diagram'] attributes 'source-highlighter': 'prettify', 'imagesdir': 'images', 'toc': 'left', 'icons': 'font', ...~200 lines -
Почему не хватает BOM? dependencies { gems 'asciidoctor-diagram:1.5.4.1' } asciidoctorj
{ version = '1.5.6' } task cleanTempDirs(type: Delete) { delete fileTree(dir: docsDir) } artifactoryPublish { properties = [artifactType: 'DOC'] } asciidoctor { finalizedBy tasks.withType(Zip) sources { include fileTree(dir: 'src/docs', includ include fileTree(dir: snippetsDir, inclu } dependsOn jrubyPrepare, project(':app').test gemPath = jrubyPrepare.outputDir requires = ['asciidoctor-diagram'] attributes 'source-highlighter': 'prettify', 'imagesdir': 'images', 'toc': 'left', 'icons': 'font', ...~200 lines ugin -
• class MyPlugin implements Plugin<Project> { void apply(Project project) {
project.configure { dependencies { compile … compile … } } project.plugins.apply(AddGitTagPlugin) project.plugins.apply(UserInfoPlugin) } } Сила в композиции 300 -
• class MyPlugin implements Plugin<Project> { void apply(Project project) {
project.configure { dependencies { compile … compile … } } project.plugins.apply(AddGitTagPlugin) project.plugins.apply(UserInfoPlugin) project.tasks.withType(SomeTaskType) { //configure } } } 301 Сила в композиции -
Сила в композиции apply plugin: "your.plugin.all" //2.1.+ 303 Автоапдейт минорных
версий Путь героев – но это уже другая история -
Сила в композиции apply plugin: "your.plugin.all" //2.1.+ 304 Автоапдейт минорных
версий Путь героев – но это уже другая история И dependency locking тоже работает! -
На больших проектах нужна инверсия контроля для сборки и зависимостей
Идея Gradle plugin -
Как это работает starter Gradle plugin Любое изменение – повод
пересобрать всё и передеплоить dependency -
Как это работает build rebuild rebuild rebuild rebuild rebuild rebuild
Обновление приводит к сборке новой версии сервисов dependency -
rebuild rebuild rebuild rebuild rebuild rebuild Как это работает Deploy
ALL production И последующему деплою -
rebuild rebuild rebuild rebuild rebuild rebuild Как это работает Deploy
ALL production И последующему деплою -
rebuild rebuild rebuild rebuild rebuild rebuild Как это работает Deploy
ALL test,dev,prod И последующему деплою и тестированию -
Актуализация версий Ручное + понятно + стабильно - геморойно -
сложно масштабировать - нет контроля запуска - ... CI/CD + единый процесс для всех + стабильно - много работы - цементирует процессы - СI/CD — 24/7 - ... Runtime + Всё из CI/CD + без пересборки + быстрый запуск + полный контроль - очень много работы - опасно - сложно тестировать - ... -
Update strategy • Скачать и поднять новый инстанс • Дождаться
пока инстанс запустится • Проверить что он жив • Переключить балансировку • Потушить старый инстанс -
Update strategy • Скачать и поднять новый инстанс • Дождаться
пока инстанс запустится • Проверить что он жив • Переключить балансировку • Потушить старый инстанс -
На что тратится время при старте? • Старт jvm •
Загрузка классов • ??? -
Просто добавь зависимость 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 ... -
Просто добавь зависимость 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`а -
Просто добавь зависимость 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 ... Чем проаннотировано -
Не там копаем indexer Я старался, но у тебя бинов
не так много -
Радикальный путь К черту ваш Spring, у меня новый друг
TEAM LEAD MICRONAUT Я сделан по новым принципам -
Новый подход MICRONAUT Micronaut делает IoC/DI в во время компиляции
Я быстрее, потому что делаю все заранее -
Новый подход на Spring Spring Fu val app = application(WebApplicationType.SERVLET)
{ logging { level = LogLevel.DEBUG } beans { bean<SampleService>() } webMvc { router { val service = ref<SampleService>() GET("/") { ok().body(service.generateMessage()) } } converters { string() jackson { indentOutput = true -
название →__ условие →__ -Xmx/mb время старта/ms -Xmx=enough micronaut 6
950 +- 0.04 spring boot 11 1500 +- 0.1 spark 3 350 +- 0.01 spring Fu 5 1041 +- 0.08 Сравним время старта -
Не похоже на реальную жизнь Зачем мне голый сервис, где
БД/MQ/Cloud??? TEAM LEAD -
название →__ условие →__ -Xmx/mb время старта/ms -Xmx=enough micronaut 19
2400 spring boot 15 3400 Если с mongo -
название →__ условие →__ -Xmx/mb время старта/ms -Xmx=enough micronaut 15
3000 spring boot 24 8000 Если с DB/MQ/Cloud -
GraalVM - в погоне за скоростью - Собираем долго -
Запускаем быстро -
Где оптимизировать • Не забывать обновляться • Следить за лишними
зависимостями • Следить за тем, что и как инициализируются в приложениях -
Где оптимизировать • Не забывать обновляться • Следить за лишними
зависимостями • Следить за тем, что и как инициализируются в приложениях • Если много бинов, использовать Context Indexer -
Где оптимизировать • Не забывать обновляться • Следить за лишними
зависимостями • Следить за тем, что и как инициализируются в приложениях • Если много бинов, использовать Context Indexer • Использовать spring.main.lazy-initialization на свой страх [spring boot 2.2+] -
Где оптимизировать • Не забывать обновляться • Следить за лишними
зависимостями • Следить за тем, что и как инициализируются в приложениях • Если много бинов, использовать Context Indexer • Использовать spring.main.lazy-initialization на свой страх [spring boot 2.2+] • Отказаться от actuator -
Решения на одном уровне порождают проблемы на другом Нельзя закрыть
проблемы всего стека на одном уровне