Evgeny EvgenyBorisov Borisov (NAYA Technologies) e Kirill tolkkv Tolkachev (Cyan.Finance, Twitter ) continuam falando sobre o uso do Spring Boot para resolver os problemas do imaginário Braavos Iron Bank. Na segunda parte, focaremos nos perfis e sutilezas do lançamento do aplicativo.

A primeira parte do artigo pode ser encontrada aqui .
Até recentemente, o cliente veio e apenas exigiu o envio de um corvo. Agora a situação mudou. O inverno chegou, o muro caiu.
Em primeiro lugar, o princípio da emissão de empréstimos está mudando. Se antes, com uma probabilidade de 50%, davam a todos, exceto Starks, agora agora pagavam apenas àqueles que pagam dívidas. Portanto, estamos alterando as regras para emissão de empréstimos em nossa lógica de negócios. Mas apenas para as agências do banco, localizadas onde o inverno já chegou, tudo permanece como antes. Lembro que este é um serviço que decide se concede ou não um empréstimo. Faremos apenas outro serviço que funcionará apenas no inverno.
Vamos à nossa lógica de negócios:
public class WhiteListBasedProphetService implements ProphetService { @Override public boolean willSurvive(String name) { return false; } }
Já temos uma lista daqueles que pagam dívidas.
spring: application.name: money-raven jpa.hibernate.ddl-auto: validate ironbank: ---: - : -: , : true
E há uma classe que já está associada à propriedade -
.
public class ProphetProperties { List<String> ; }
Como nos tempos anteriores, apenas injetamos aqui:
public class WhiteListBasedProphetService implements ProphetService { private final ProphetProperties prophetProperties; @Override public boolean willSurvive(String name) { return false; } }
Lembre-se da injeção de construtor (sobre anotações mágicas):
@Service @RequiredArgsConstructor public class WhiteListBasedProphetService implements ProphetService { private final ProphetProperties prophetProperties; @Override public boolean willSurvive(String name) { return false; } }
Quase pronto.
Agora devemos ceder apenas aos que pagam dívidas:
@Service @RequiredArgsConstructor public class WhiteListBasedProphetService implements ProphetService { private final ProphetProperties prophetProperties; @Override public boolean willSurvive(String name) { return prophetProperties.get().contains(name); } }
Mas aqui temos um pequeno problema. Agora, temos duas implementações: os antigos e os novos serviços.
Description Parameter 1 of constructor in com.ironbank.moneyraven.service.TransferMoneyProphecyBackend… - nameBasedProphetService: defined in file [/Users/tolkv/git/conferences/spring-boot-ripper… - WhileListBackendProphetService: defined in file [/Users/tolkv/git/conferences/spring-boot-ripper...
É lógico dividir esses beans em diferentes perfis. Perfil de
e perfil de
. Deixe nosso novo serviço ser executado apenas no perfil
:
@Service @Profile(ProfileConstants.) @RequiredArgsConstructor public class WhiteListBasedProphetService implements ProphetService { private final ProphetProperties prophetProperties; @Override public boolean willSurvive(String name) { return prophetProperties.get().contains(name); } }
E o serviço antigo é no
.
@Service @Profile(ProfileConstants.) public class NameBasedProphetService implements ProphetService { @Override public boolean willSurvive(String name) { return !name.contains("Stark") && ThreadLocalRandom.current().nextBoolean(); } }
Mas o inverno chega devagar. Nos reinos próximos ao muro quebrado, já é inverno. Mas em algum lugar no sul - ainda não. I.e. Os aplicativos localizados em diferentes ramificações e fusos horários devem funcionar de maneira diferente. De acordo com as condições de nossa tarefa, não podemos apagar a antiga implementação onde o inverno chegou e usar a nova classe. Queremos que os funcionários do banco não façam absolutamente nada: entregaremos a eles um aplicativo que funcionará no modo verão até o inverno chegar. E quando chega o inverno, eles simplesmente o reiniciam e é isso. Eles não precisarão alterar o código, apagar nenhuma classe. Portanto, inicialmente temos dois perfis: parte da lixeira é criada quando o verão e parte da lixeira é criada quando o inverno.
Mas outro problema aparece:

Agora, não temos um único bean, porque especificamos dois perfis e o aplicativo inicia no perfil padrão.
Portanto, temos uma nova exigência do cliente.
Lei de Ferro 2. Nenhum perfil é permitido

Não queremos elevar o contexto se o perfil não estiver ativado, porque o inverno já chegou, tudo ficou muito ruim. Há certas coisas que devem acontecer ou não, dependendo se o
ou se o
. Além disso, observe a exceção, cujo texto é fornecido acima. Ele não explica nada. Um perfil não está definido, portanto, não há implementação do ProphetService
. Ao mesmo tempo, ninguém disse que é necessário definir um perfil.
Portanto, agora queremos aparafusar uma peça adicional em nosso iniciador, que, ao criar o contexto, verificará se algum perfil está definido. Se não estiver definido, não avançaremos e lançaremos apenas uma exceção (e não uma exceção sobre a falta de uma lixeira).
Podemos fazer isso com nosso ouvinte de aplicativos? Não. E há três razões para isso:
- O ouvinte de responsabilidade única é responsável por fazer o corvo voar. O Ouvinte não deve verificar se um perfil foi ativado, porque a ativação de um perfil afeta não apenas o próprio ouvinte, mas também muito mais.
- Quando um contexto é construído, coisas diferentes acontecem. E não queremos que eles comecem a acontecer se um perfil não tiver sido definido.
- O ouvinte funciona no final quando o contexto é resolvido. E o fato de não haver perfil, sabemos muito antes. Por que esperar esses cinco minutos condicionais até o serviço quase aumentar e tudo cair.
Além disso, ainda não sei quais erros aparecerão devido ao fato de começarmos a crescer sem um perfil (suponha que eu não conheça a lógica de negócios). Portanto, na ausência de um perfil, você precisa trazer o contexto em um estágio muito inicial. A propósito, se você usa qualquer Spring Cloud, isso se torna ainda mais relevante para você, porque o aplicativo faz muitas coisas desde o início.
Para implementar o novo requisito, existe o ApplicationContextInitializer
. Essa é outra interface que nos permite estender algum ponto do Spring especificando-o em spring.factories.

Implementamos essa interface e temos um Context Initializer, que possui um ConfigurableApplicationContext
:
public class ProfileCheckAppInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { } }
Com isso, podemos obter o ambiente - o que a SpringApplication preparou para nós. Todas as propriedades que passamos para ele chegaram lá. Entre outras coisas, eles também contêm perfis.
Se não houver perfis lá, devemos lançar uma exceção dizendo que você não pode trabalhar assim.
public class ProfileCheckAppInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { if applicationContext.getEnvironment().getActiveProfiles().length == 0 { throw new RuntimeException(" !"); } } }
Agora você precisa registrar essas coisas em spring.factories.
org.springframework.boot.context.properties.EnableConfigurationProperties=com.ironbank.moneyraven.starter.IronConfiguration org.springframework.context.ApplicationContextInitializer=com.ironbank.moneyraven.starter.ProfileCheckAppInitializer
Pelo exposto, você pode adivinhar que ApplicationContextInitializer
é um ponto de extensão. ApplicationContextInitializer
funciona quando o contexto está apenas começando a ser criado, ainda não há caixas.
Surge a pergunta: se escrevemos ApplicationContextInitializer
, por que não deveria, como ouvinte, ser escrito em uma configuração que se estende de qualquer maneira? A resposta é simples: porque deve funcionar muito mais cedo quando não há contexto nem configurações. I.e. ainda não pode ser injetado. Portanto, prescrevemos como uma peça separada.
Uma tentativa de iniciar mostrou que tudo havia caído rápido o suficiente e relatou que estávamos começando sem um perfil. Agora vamos tentar especificar algum perfil, e tudo funciona - o corvo é enviado.
ApplicationContextInitializer
- atende quando o contexto já foi criado, mas não há mais nada além do ambiente.

Quem cria o ambiente? Carlson - SpringBootApplication
. Ele a preenche com várias meta-informações, que podem ser retiradas do contexto. A maioria das coisas pode ser injetada via @value
, algo pode ser obtido no ambiente, pois acabamos de obter perfis.
Por exemplo, propriedades diferentes vêm aqui:
- qual Spring Boot pode construir;
- que na inicialização são transmitidos através da linha de comando;
- sistêmico;
- enunciado como variáveis de ambiente;
- prescrito nas propriedades do aplicativo;
- registrado em alguns outros arquivos de propriedades.
Tudo isso é coletado e definido em um objeto de ambiente. Ele também contém informações sobre quais perfis estão ativos. O objeto de ambiente é a única coisa que existe no momento em que o Spring Boot começa a criar o contexto.
Gostaria de adivinhar automaticamente qual será o perfil se as pessoas se esquecerem de perguntar com as mãos (fazemos tudo para que os funcionários do banco, que estão desamparados o suficiente sem programadores, possam iniciar o aplicativo para que tudo funcione para eles, independentemente do que seja). Para fazer isso, adicionaremos ao nosso acionador de partida algo que adivinhe o perfil -
ou não - dependendo da temperatura na rua. E outra nova interface mágica nos ajudará a tudo isso - EnvironmentPostProcessor
, porque precisamos fazer isso antes que o ApplicationContextInitializer
funcione. E antes do ApplicationContextInitializer
existe apenas o EnvironmentPostProcessor
.
Estamos novamente implementando uma nova interface. Existe apenas um método, que da mesma maneira que ConfigurableEnvironment
lança no SpringApplication
, porque ainda não temos o ConfigurableContext
(ele já existe no SpringInitializer
não está aqui; existe apenas ambiente).
public class ResolveProfileEnvironmentPostProcessor implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { } }
Nesse ambiente, podemos definir o perfil. Mas primeiro você precisa verificar se ninguém o instalou antes. Portanto, getActiveProfiles
precisamos verificar getActiveProfiles
. Se as pessoas souberem o que estão fazendo e estabelecerem um perfil, não tentaremos adivinhar por elas. Mas se não houver perfil, tentaremos entender pelo clima.
E a segunda - precisamos entender se temos inverno ou verão agora. Voltaremos a temperatura de -300
.
public class ResolveProfileEnvironmentPostProcessor implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { if (environment.getActivePrifiles().length == 0 && getTemperature() < -272) { } } public int getTemperature() { return -300; } }
Sob essa condição, temos o inverno e podemos estabelecer um novo perfil. Lembramos que o perfil é chamado
:
public class ResolveProfileEnvironmentPostProcessor implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { if (environment.getActivePrifiles().length == 0 && getTemperature() < -272) { environment.setActiveProfiles(""); } else { environment.setActiveProfiles(""); } } public int getTemperature() { return -300; } }
Agora precisamos especificar o EnvironmentPostProcessor
em spring.factories.
org.springframework.boot.context.properties.EnableConfigurationProperties=com.ironbank.moneyraven.starter.IronConfiguration org.springframework.context.ApplicationContextInitializer=com.ironbank.moneyraven.starter.ProfileCheckAppInitializer org.springframework.boot.env.EnvironmentPostProcessor=com.ironbank.moneyraven.starter.ResolveProfileEnvironmentPostProcessor
Como resultado, o aplicativo inicia sem um perfil, dizemos que é produção e verificamos em qual perfil ele começou conosco. Magicamente, percebemos que nosso perfil é
. E o aplicativo não caiu, porque o ApplicationContextInitializer
, que verifica se há um perfil, vem a seguir.
O resultado:
public class ResolveProfileEnvironmentPostProcessor implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { if (getTemperature() < -272) { environment.setActiveProfiles(""); } else { environment.setActiveProfiles(""); } } private int getTemperature() { return -300; } }
Falamos sobre o EnvironmentPostProcessor
, que é executado antes do ApplicationContextInitializer
. Mas quem dirige?

Essa aberração inicia, que, aparentemente, é o filho ilegítimo de ApplicationListener
e EnvironmentPostProcessor
, porque é herdado de ApplicationListener
e EnvironmentPostProcessor
. É chamado ConfigFileApplicationListener
(por que "ConfigFile" - ninguém sabe).
Ele é o nosso Carlson, ou seja, O Spring Application fornece um ambiente preparado para ouvir dois eventos: ApplicationPreparedEvent
e ApplicationEnvironmentPreparedEvent
. Não analisaremos agora quem lança esses eventos. Existe outra camada (na minha opinião, já é completamente supérflua, pelo menos nesta fase do desenvolvimento do Spring), que lança um evento que o ambiente está começando a ser construído (Application.yml, propriedades, variáveis de ambiente são analisadas etc.) )
Após receber ApplicationEnvironmentPreparedEvent
, o ouvinte entende que você precisa configurar o ambiente - encontre todo o EnvironmentPostProcessor
e deixe-os funcionar.

Depois disso, ele diz ao SpringFactoriesLoader
para entregar tudo o que você pediu, ou seja, todo o EnvironmentPostProcessor
, para spring.factories. Em seguida, coloca todo o EnvironmentPostProcessor
em uma lista.

e entende que ele também é um EnvironmentPostProcessor
(simultaneamente), portanto, ele se empurra para lá,

ao mesmo tempo, classifica-os, acompanha-os e chama o postProcessEnvironment
cada método.
Dessa forma, todos os postProcessEnvironment
são iniciados antes do SpringApplicationInitializer
. Nesse caso, um EnvironmentPostProcessor
incompreensível chamado ConfigFileApplicationListener
também é iniciado.
Quando o ambiente é configurado, tudo volta para Carlson novamente.
Se o ambiente estiver pronto, você poderá criar um contexto. E Carlson começa a criar contexto com o ApplicationInitializer
. Aqui temos a nossa própria peça, que verifica se, no contexto, existe um ambiente em que existem perfis ativos. Caso contrário, estamos caindo, porque, caso contrário, ainda teremos problemas mais tarde. Então os iniciantes trabalham, com todas as configurações usuais já.
A imagem acima reflete que o Spring também não está indo bem. Esses alienígenas se reúnem periodicamente lá, a responsabilidade única não é respeitada e você precisa escalar com cuidado.
Agora, queremos falar um pouco sobre o outro lado dessa criatura estranha, que é ouvinte de um lado e EnvironmentPostProcessor
do outro.

Como o EnvironmentPostProcessor
ele pode carregar application.yml, propriedades do aplicativo, todos os tipos de variáveis de ambiente, argumentos de comando etc. E como ouvinte, ele pode ouvir dois eventos:
ApplicationPreparedEvent
ApplicationEnvironmentPreparedEvent
A questão é:

Todos esses eventos ocorreram na antiga primavera. E aqueles sobre os quais falamos acima são eventos do Spring Boot (eventos especiais que ele adicionou para seu ciclo de vida). E há um monte deles. Estes são os principais:
ApplicationStartingEvent
ApplicationEnvironmentPreparedEvent
ApplicationPreparedEvent
ContextRefreshedEvent
EmbeddedServletContainerInitializedEvent
ApplicationReadyEvent
ApplicationFailedEvent
Esta lista está longe de tudo. Mas é importante que alguns deles estejam relacionados ao Spring Boot e façam parte do Spring (bom e velho ContextRefreshedEvent
, etc.).
A ressalva é que nem todos esses eventos podem ser recebidos no aplicativo (meros mortais - avós diferentes - não podem apenas ouvir os eventos complexos que o Spring Boot lança). Mas se você conhece os mecanismos secretos do spring.factories e define o Application Listener no nível do spring.factories, esses eventos, desde o estágio inicial da inicialização do aplicativo, chegam até você.

Como resultado, você pode influenciar o início do seu aplicativo em um estágio bastante inicial. A piada, no entanto, é que parte desse trabalho é realizada em outras entidades - como EnvironmentPostProcessor
e ApplicationContextInitializer
.
Você poderia fazer tudo com os ouvintes, mas seria inconveniente e feio. Se você deseja ouvir todos os eventos que o Spring lança, e não apenas ContextRefreshedEvent
e ContextStartedEvent
, não é necessário definir o ouvinte, como um bean, da maneira usual (caso contrário, será criado tarde demais). Ele também deve ser registrado através do spring.factories, e será criado muito antes.
A propósito, quando analisamos esta lista, não estava claro para nós quando o ContextStartedEvent
e o ContextStoppedEvent
dispararam?

Acontece que esses eventos nunca funcionam. Ficamos intrigados por um longo tempo sobre quais eventos deveriam ser capturados para entender que o aplicativo realmente foi iniciado. E aconteceu que os eventos sobre os quais estávamos falando agora aparecem quando você puxa com força os métodos do contexto:
ctx.start();
-> ContextStartedEvent
ctx.stop();
-> ContextStoppedEvent
I.e. SpringApplication.run
virão somente se executarmos SpringApplication.run
, obtermos o contexto, extrair ctx.start();
ou ctx.stop();
. Não está muito claro por que isso é necessário. Mas, novamente, eles deram a você um ponto de extensão.
Spring tem algo a ver com isso? Nesse caso, em algum lugar deve haver uma exceção:
ctx.stop();
(1)ctx.start();
2)ctx.close();
(3)ctx.start();
4)
De fato, estará na última linha, porque depois de ctx.close();
nada pode ser feito com o contexto. Mas chame ctx.stop();
antes de ctx.start();
- você pode (o Spring simplesmente ignora esses eventos - eles são apenas para você).
Escreva para seus ouvintes, ouça você mesmo, ctx.stop();
suas leis, o que fazer em ctx.stop();
e o que fazer em ctx.start();
.
No total, o diagrama da interação e do ciclo de vida do aplicativo é mais ou menos assim:

As cores aqui mostram diferentes períodos da vida.
- Azul é Spring Boot, o aplicativo já foi iniciado. Isso significa que as solicitações de serviço do Tomcat enviadas pelos clientes são processadas, todo o contexto é definitivamente gerado, todos os beans estão funcionando, os bancos de dados estão conectados etc.
- Verde - um evento
ContextRefreshedEvent
chegou e o contexto foi criado. A partir deste momento, por exemplo, os ouvintes de aplicativos começam a funcionar, que você implementa definindo a anotação ApplicationListener ou através da interface de mesmo nome com um genérico que escuta determinados eventos. Se você deseja receber mais eventos, precisa escrever o mesmo ApplicationListener em spring.factories (o Spring usual funciona aqui). Uma barra indica onde o relatório do Spring Ripper começa. - Numa fase anterior, o SpringApplication funciona, o que prepara o contexto para nós. Esse é o trabalho de preparar o aplicativo que fizemos quando éramos desenvolvedores regulares do Spring. Por exemplo, WebXML configurado.
- Mas existem estágios ainda anteriores. Mostra quem, onde e para quem trabalha.
- Ainda existe um estágio cinza no qual é impossível cunhar de alguma maneira. Este é o estágio em que o SpringApplication fica fora da caixa (apenas entre no código).
Se você notou, durante o relatório de duas partes, fomos da direita para a esquerda: começamos do final, estragamos a configuração que voava do iniciador e adicionamos o seguinte, etc. Agora vamos falar rapidamente de toda a cadeia na direção oposta.
Você escreve em seu SpringApplication.run
principal. Ele encontra ouvintes diferentes, lança um evento que ele começou a construir. Depois disso, os ouvintes encontram o EnvironmentPostProcessor
, deixe-os configurar o ambiente. Depois que o ambiente é configurado, começamos a construir o contexto (Carlson entra). Carlson cria o contexto e permite que todos os Inicializadores de Aplicativos façam algo com esse contexto. Temos um ponto de extensão. Depois disso, o contexto já está configurado e, em seguida, acontece o mesmo que em um aplicativo Spring regular, quando o contexto é criado - BeanFactoryPostProcessor
, BeanPostProcessor
, os beans são configurados. É isso que a primavera comum faz.
Como executar
Terminamos de discutir o processo de criação de um aplicativo.
Mas tínhamos mais uma coisa que os desenvolvedores não gostam. Eles não gostam de pensar como, no final, sua aplicação será iniciada. O administrador o executará no Tomcat, JBoss ou no WebLogic? Apenas tem que funcionar. Se não funcionar, na pior das hipóteses, o desenvolvedor precisará configurar algo novamente
Então, quais são os nossos métodos de lançamento?
- guerra tomcat;
- ideia;
java -jar/war
.
O Tomcat não é uma tendência em massa, não falaremos sobre isso em detalhes.
A ideia também não é, em princípio, muito interessante. É apenas um pouco mais complicado do que vou dizer abaixo. Mas em Idea, em princípio, não deve haver problemas. Ela vê que tipo de dependências o acionador de partida trará.
Se fizermos java -jar
, o principal problema é criar um caminho de classe antes de iniciar o aplicativo.
O que as pessoas fizeram em 2001? Eles escreveram em java -jar
qual jar deve ser executado, então um espaço, classpath=...
e scripts foram indicados lá. No nosso caso, existem 150 MB de várias dependências que os iniciantes adicionaram. E tudo isso teria que ser feito manualmente. Naturalmente, ninguém faz isso. Nós apenas escrevemos: java -jar
, qual jar deve ser executado e é isso. De alguma forma, o caminho de classe ainda está sendo construído. Vamos falar sobre isso agora.
Vamos começar com a preparação do arquivo jar para que ele possa ser iniciado sem o Tomcat. Antes de criar o java -jar
, você precisa construir um jar. Esse jarro obviamente deve ser incomum, algum tipo de analógico de guerra, onde tudo estará dentro, incluindo o Tomcat incorporado.
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
Quando baixamos o projeto, alguém já registrou um plug-in em nosso POM. Aqui, a propósito, você pode lançar configurações, mas mais sobre isso mais tarde.Como resultado, além do jar comum que cria o Maven ou Gradle do seu aplicativo, também é criado um jar incomum. Por um lado, parece bom:

Mas se você olhar de lado:

Isso é basicamente um análogo da guerra.
Vamos ver do que se trata.
Obviamente, existem peças padrão, como no jarro comum. Quando escrevemos java -jar
, todas as classes que estão na raiz, por exemplo, são ativadas org.springframework.boot
. Mas essas não são as nossas aulas. Dificilmente escrevemos o pacote org.springframework.boot. Portanto, antes de tudo, para nós é familiarMETA-INF

O Spring Boot também MANIFEST (através do mesmo plug-in Maven ou Gradle), personaliza-o e prescreve a classe principal, que inicia no jar.
, jar- : -, main-. java -jar
-jar, , main-class-.
, , MANIFEST, main-class , main ( Idea). , . class path? java -jar
, main, , — main, . MANIFEST JarLauncher.

I.e. , , JarLauncher. , main, class path.
, main? property — Start-class
.
I.e. . class path jar. , — org.springframework.boot
— class path. org.springframework.boot.loader.JarLauncher
main-class. , main-class . class path, BOOT-INF
( lib class , ).
RavenApplication, properties class BOOT-INF
, , Tomcat , BOOT-INF/lib/
. JarLauncher classpath, — , start-class
. Spring, ContextSpringApplication
— flow, .
, start-class-? , . , .
, . property, mainClass
, MANIFEST Start-Class
, mainClass
— JarLauncher.
, mainClass, ? . Spring boot plugin – mainClass:
- – . — main class;
- – , mainClass
@SpringBootApplication
, , , ; - — exception , main class, , jar- . I.e. , , . , , main class.
- @SpringBootApplication — .
JarLauncher . Tomcat WarLauncher, war- , jar-.
, java -jar
. ? Você pode. .
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <executable>true</executable> </configuration> </plugin> </plugins> </build>
<configuration>
<executable>true</executable>
Gradle , :
springBoot { executable = true }
jar executable jar. .
, . Windows , exe-, . Spring Boot, .. jar, . , .
?
(jar — zip-, ):

Spring Boot - .
-, jar-. , , — #!/bin/bash
. .
. exit 0
- — zip-.

, zip- — 0xf4ra
. , , .

(, ..).
jar :
- — ;
- , " bash" (
#!/bin/bash
); - bash ;
exit 0
;java -jar
— jar-, ;java -jar
zip- jar-, , , .
Conclusões
, Spring Boot — , , .
-, . , Spring, Spring — Spring Boot. , , — , , , . , , Spring, Spring Boot .
-, @SpringBootApplication
, best practice, Spring-.
— , , . property environment variable, var arg , , JSON. @value
, . configuration properties , , , . , Spring . , , .
. , . Spring, Spring Boot . - , , , .

Minuto de publicidade. 19-20 Joker 2018, « [Joker Edition]» , «Micronaut vs Spring Boot, ?» . , Joker . .