Arranca tú mismo, llega la primavera (Parte 2)

Evgeny EvgenyBorisov Borisov (NAYA Technologies) y Kirill tolkkv Tolkachev (Cyan.Finance, Twitter ) continúan hablando sobre el uso de Spring Boot para resolver los problemas del imaginario Braavos Iron Bank. En la segunda parte, nos centraremos en los perfiles y sutilezas del lanzamiento de la aplicación.






La primera parte del artículo se puede encontrar aquí .


Entonces, hasta hace poco, el cliente vino y solo exigió enviar un cuervo. Ahora la situación ha cambiado. Ha llegado el invierno, el muro ha caído.


En primer lugar, el principio de emisión de préstamos está cambiando. Si antes con una probabilidad del 50% se entregaba a todos excepto a Starks, ahora ahora se entrega solo a aquellos que pagan deudas. Por lo tanto, estamos cambiando las reglas para emitir préstamos en nuestra lógica de negocios. Pero solo para las sucursales del banco, que se encuentran donde ya ha llegado el invierno, en todo lo demás todo permanece como antes. Les recuerdo que este es un servicio que decide si otorgar un préstamo o no. Simplemente haremos otro servicio que funcionará solo en invierno.


Vamos a nuestra lógica de negocios:


public class WhiteListBasedProphetService implements ProphetService {  @Override  public boolean willSurvive(String name) {    return false;  } } 

Ya tenemos una lista de quienes pagan deudas.


 spring: application.name: money-raven jpa.hibernate.ddl-auto: validate ironbank: ---:   -  : -: ,   : true 

Y hay una clase que ya está asociada con la propiedad: .


 public class ProphetProperties { List<String> ; } 

Como en tiempos anteriores, simplemente lo inyectamos aquí:


 public class WhiteListBasedProphetService implements ProphetService { private final ProphetProperties prophetProperties; @Override public boolean willSurvive(String name) {   return false; } } 

Recuerde acerca de la inyección del constructor (sobre anotaciones mágicas):


 @Service @RequiredArgsConstructor public class WhiteListBasedProphetService implements ProphetService { private final ProphetProperties prophetProperties; @Override public boolean willSurvive(String name) {   return false; } } 

Casi terminado


Ahora debemos dar solo a aquellos que pagan deudas:


 @Service @RequiredArgsConstructor public class WhiteListBasedProphetService implements ProphetService { private final ProphetProperties prophetProperties; @Override public boolean willSurvive(String name) { return prophetProperties.get().contains(name); } } 

Pero aquí tenemos un pequeño problema. Ahora tenemos dos implementaciones: los servicios antiguos y los nuevos.


 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... 

Es lógico dividir estos beans en diferentes perfiles. Perfil de y perfil de . Deje que nuestro nuevo servicio funcione solo en el 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); } } 

Y el antiguo servicio es en .


 @Service @Profile(ProfileConstants.) public class NameBasedProphetService implements ProphetService { @Override public boolean willSurvive(String name) {   return !name.contains("Stark") && ThreadLocalRandom.current().nextBoolean(); } } 

Pero el invierno llega lentamente. En los reinos al lado del muro roto, ya es invierno. Pero en algún lugar del sur, todavía no. Es decir Las aplicaciones ubicadas en diferentes sucursales y zonas horarias deberían funcionar de manera diferente. De acuerdo con las condiciones de nuestra tarea, no podemos borrar la implementación anterior donde ha llegado el invierno y usar la nueva clase. Queremos que los empleados del banco no hagan nada en absoluto: les entregaremos una aplicación que funcionará en modo verano hasta que llegue el invierno. Y cuando llega el invierno, simplemente lo reinician y eso es todo. No tendrán que cambiar el código, borrar ninguna clase. Por lo tanto, inicialmente tenemos dos perfiles: parte del contenedor se crea cuando el verano y parte del contenedor se crea cuando el invierno.


Pero aparece otro problema:




Ahora no tenemos un solo bean, porque especificamos dos perfiles, y la aplicación se inicia en el perfil predeterminado.


Entonces tenemos un nuevo requisito del cliente.


Iron Law 2. No se permite ningún perfil.




No queremos elevar el contexto si el perfil no está activado, porque ya ha llegado el invierno, todo se ha vuelto muy malo. Hay ciertas cosas que deberían suceder o no, dependiendo de si el o el . Además, observe la excepción, cuyo texto se proporciona arriba. No explica nada. Un perfil no está definido, por lo tanto, no hay implementación de ProphetService . Al mismo tiempo, nadie dijo que es necesario establecer un perfil.


Por lo tanto, ahora queremos atornillar una pieza adicional en nuestro iniciador, que, al construir el contexto, verificará que se haya configurado algún perfil. Si no está configurado, no subiremos y lanzaremos tal excepción (y no alguna excepción sobre la falta de un contenedor).


¿Podemos hacer esto con nuestra aplicación de escucha? No Y hay tres razones para esto:


  • El oyente de responsabilidad única es responsable de hacer volar al cuervo. El oyente no debe verificar si se ha activado un perfil porque la activación de un perfil afecta no solo al oyente en sí, sino también mucho más.
  • Cuando se construye un contexto, suceden cosas diferentes. Y no queremos que comiencen a suceder si no se ha establecido un perfil.
  • El oyente funciona al final cuando se resuelve el contexto. Y el hecho de que no hay perfil, lo sabemos mucho antes. ¿Por qué esperar estos cinco minutos condicionales hasta que el servicio casi se eleva y luego todo se cae?

Además, todavía no sé qué errores aparecerán debido al hecho de que comenzamos a aumentar sin un perfil (supongamos que no conozco la lógica comercial). Por lo tanto, en ausencia de un perfil, es necesario reducir el contexto en una etapa muy temprana. Por cierto, si usa Spring Cloud, esto se vuelve aún más relevante para usted, porque la aplicación hace muchas cosas en una etapa temprana.


Para implementar el nuevo requisito, hay ApplicationContextInitializer . Esta es otra interfaz que nos permite extender algún punto de Spring al especificarlo en spring.factories.




Implementamos esta interfaz y tenemos un Context Initializer, que tiene un ConfigurableApplicationContext :


 public class ProfileCheckAppInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { } } 

Con él, podemos obtener el medio ambiente, lo que SpringApplication nos preparó. Toda la propiedad que le pasamos llegó allí. Entre otras cosas, también contienen perfiles.


Si no hay perfiles allí, entonces deberíamos lanzar una excepción diciendo que no puedes trabajar así.


 public class ProfileCheckAppInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> { @Override public void initialize(ConfigurableApplicationContext applicationContext) { if applicationContext.getEnvironment().getActiveProfiles().length == 0 {     throw new RuntimeException("  !");   } } } 

Ahora necesita registrar estas cosas en spring.factories.


 org.springframework.boot.context.properties.EnableConfigurationProperties=com.ironbank.moneyraven.starter.IronConfiguration org.springframework.context.ApplicationContextInitializer=com.ironbank.moneyraven.starter.ProfileCheckAppInitializer 

De lo anterior, puede adivinar que ApplicationContextInitializer es un punto de extensión. ApplicationContextInitializer funciona cuando el contexto apenas comienza a construirse, todavía no hay contenedores.


Surge la pregunta: si escribimos ApplicationContextInitializer , ¿por qué no, como oyente, debería escribirse en una configuración que se extienda de todos modos? La respuesta es simple: porque debería funcionar mucho antes cuando no hay contexto ni configuraciones. Es decir No se puede inyectar todavía. Por lo tanto, lo prescribimos como una pieza separada.


Un intento de lanzamiento mostró que todo había caído lo suficientemente rápido e informó que estábamos comenzando sin un perfil. Ahora intentemos especificar algún perfil, y todo funciona: se envía el cuervo.


ApplicationContextInitializer : se cumple cuando el contexto ya se ha creado, pero no hay nada más que el entorno.




¿Quién crea el medio ambiente? Carlson - SpringBootApplication . Lo llena con varias metainformaciones, que luego se pueden sacar de contexto. La mayoría de las cosas se pueden inyectar a través de @value , algo se puede obtener del entorno, ya que acabamos de obtener perfiles.


Por ejemplo, diferentes propiedades vienen aquí:


  • que Spring Boot puede construir;
  • que al inicio se transmiten a través de la línea de comando;
  • sistémico
  • enunciadas como variables de entorno;
  • prescrito en las propiedades de aplicación;
  • registrado en algunos otros archivos de propiedades.

Todo esto se recopila y establece en un objeto de entorno. También contiene información sobre qué perfiles están activos. El objeto de entorno es lo único que existe en el momento en que Spring Boot comienza a construir contexto.


Me gustaría adivinar automáticamente cuál será el perfil si las personas se olvidaran de preguntarlo con sus manos (hacemos todo lo posible para que los empleados del banco que están lo suficientemente indefensos sin programadores puedan iniciar la aplicación para que todo funcione para ellos, pase lo que pase). Para hacer esto, agregaremos a nuestro arranque algo que adivinará el perfil, o no, dependiendo de la temperatura en la calle. Y otra nueva interfaz mágica nos ayudará a todos con esto: EnvironmentPostProcessor , porque necesitamos hacer esto antes de que ApplicationContextInitializer funcione. Y antes de ApplicationContextInitializer solo hay EnvironmentPostProcessor .


Nuevamente estamos implementando una nueva interfaz. Solo hay un método, que de la misma manera que ConfigurableEnvironment arroja en SpringApplication , porque todavía no tenemos ConfigurableContext (ya existe en SpringInitializer no está aquí; solo hay entorno).


 public class ResolveProfileEnvironmentPostProcessor implements EnvironmentPostProcessor { @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { } } 

En este entorno, podemos establecer el perfil. Pero primero debe verificar que nadie lo haya instalado antes. Por lo tanto, getActiveProfiles debemos verificar getActiveProfiles . Si las personas saben lo que están haciendo y configuran un perfil, entonces no intentaremos adivinarlas. Pero si no hay un perfil, trataremos de entender por el clima.


Y el segundo: debemos entender si tenemos clima de invierno o verano ahora. Devolveremos la 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; } } 

Bajo esta condición, tenemos invierno, y podemos establecer un nuevo perfil. Recordamos que el perfil se llama :


 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; } } 

Ahora necesitamos especificar el EnvironmentPostProcessor en 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, la aplicación comienza sin un perfil, decimos que es producción y verificamos en qué perfil comenzó con nosotros. Mágicamente, nos dimos cuenta de que nuestro perfil es el . Y la aplicación no cayó, porque el ApplicationContextInitializer , que verifica si hay un perfil, viene a continuación.
El 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; } } 

Hablamos sobre EnvironmentPostProcessor , que se ejecuta antes de ApplicationContextInitializer . ¿Pero quién lo dirige?




Este fenómeno lo inicia, que, aparentemente, es el hijo ilegítimo de ApplicationListener y EnvironmentPostProcessor , porque se hereda de ApplicationListener y EnvironmentPostProcessor . Se llama ConfigFileApplicationListener (por qué "ConfigFile" - nadie lo sabe).


Él es nuestro Carlson, es decir. Spring Application proporciona un entorno preparado para escuchar dos eventos: ApplicationPreparedEvent y ApplicationEnvironmentPreparedEvent . No analizaremos ahora quién lanza estos eventos. Hay otra capa (en mi opinión ya es completamente superflua, al menos en esta etapa del desarrollo de Spring), que arroja un evento de que el entorno está comenzando a construirse (Application.yml, propiedades, variables de entorno se analizan, etc. )
Después de recibir ApplicationEnvironmentPreparedEvent , el oyente comprende que necesita configurar el entorno: busque todos los EnvironmentPostProcessor y déjelos trabajar.




Después de eso, le dice a SpringFactoriesLoader que entregue todo lo que ordenó, es decir, todo el EnvironmentPostProcessor , a spring.factories. Luego rellena todo el EnvironmentPostProcessor en una Lista.




y entiende que él también es un EnvironmentPostProcessor (concurrentemente), por lo tanto, se empuja allí,

al mismo tiempo, los clasifica, los acompaña y llama al postProcessEnvironment cada método.


De esta manera, todos los postProcessEnvironment se inician en una etapa temprana antes de SpringApplicationInitializer . En este caso, también se inicia un EnvironmentPostProcessor incomprensible EnvironmentPostProcessor llamado ConfigFileApplicationListener .


Cuando se configura el entorno, todo vuelve a Carlson nuevamente.


Si el entorno está listo, puede crear un contexto. Y Carlson comienza a construir contexto con el ApplicationInitializer . Aquí tenemos nuestra propia pieza, que comprueba que en el contexto hay un entorno en el que hay perfiles activos. Si no, estamos cayendo, porque de lo contrario tendremos problemas más tarde. Entonces los arrancadores funcionan, con todas las configuraciones habituales ya.


La imagen de arriba refleja que a Spring tampoco le está yendo bien. Dichos extraterrestres se reúnen periódicamente allí, no se respeta la responsabilidad individual y debe escalar con cuidado.


Ahora queremos hablar un poco sobre el otro lado de esta extraña criatura, que es oyente por un lado y EnvironmentPostProcessor por el otro.




Al igual que EnvironmentPostProcessor puede cargar application.yml, propiedades de la aplicación, todo tipo de variables de entorno, argumentos de comando, etc. Y como oyente, puede escuchar dos eventos:


  • ApplicationPreparedEvent
  • ApplicationEnvironmentPreparedEvent

La pregunta es:




Todos estos eventos fueron en la vieja primavera. Y los que mencionamos anteriormente son eventos de Spring Boot (eventos especiales que agregó para su ciclo de vida). Y hay un montón de ellos. Estos son los principales:


  • ApplicationStartingEvent
  • ApplicationEnvironmentPreparedEvent
  • ApplicationPreparedEvent
  • ContextRefreshedEvent
  • EmbeddedServletContainerInitializedEvent
  • ApplicationReadyEvent
  • ApplicationFailedEvent

Esta lista está lejos de todo. Pero es importante que algunos de ellos se relacionen con Spring Boot, y parte de Spring (buen viejo ContextRefreshedEvent , etc.).


La advertencia es que no todos estos eventos podemos obtener en la aplicación (los simples mortales, diferentes abuelas, no pueden simplemente escuchar los eventos complejos que lanza Spring Boot). Pero si conoce los mecanismos secretos de spring.factories y define su Application Listener en el nivel de spring.factories, estos eventos desde la etapa más temprana de inicio de la aplicación le llegarán.




Como resultado, puede influir en el inicio de su aplicación en una etapa bastante temprana. El chiste, sin embargo, es que parte de este trabajo se lleva a cabo en otras entidades, como EnvironmentPostProcessor y ApplicationContextInitializer .


Podrías hacer todo en los oyentes, pero sería inconveniente y feo. Si desea escuchar todos los eventos que lanza Spring, y no solo ContextRefreshedEvent y ContextStartedEvent , no necesita configurar el oyente, como un bean, de la forma habitual (de lo contrario, se crea demasiado tarde). También debe registrarse a través de spring.factories, luego se creará mucho antes.


Por cierto, cuando miramos esta lista, no nos quedó claro cuándo se ContextStartedEvent y ContextStoppedEvent .




Resultó que estos eventos nunca funcionan en absoluto. Durante mucho tiempo nos preguntamos qué eventos deberían detectarse para comprender que la aplicación realmente comenzó. Y resultó que los eventos de los que estábamos hablando ahora aparecen cuando extrae con fuerza los métodos del contexto:


  • ctx.start(); -> ContextStartedEvent
  • ctx.stop(); -> ContextStoppedEvent

Es decir SpringApplication.run solo vendrán si ejecutamos SpringApplication.run , obtenemos el contexto, ctx.start(); de él ctx.start(); o ctx.stop(); . No está muy claro por qué esto es necesario. Pero, nuevamente, te dieron un punto de extensión.


¿Spring tiene algo que ver con esto? Si es así, en algún lugar debería haber una excepción:


  • ctx.stop(); (1)
  • ctx.start(); (2)
  • ctx.close(); (3)
  • ctx.start(); (4)

De hecho, estará en la última línea, porque después de ctx.close(); nada se puede hacer con el contexto. Pero llame a ctx.stop(); antes de ctx.start(); - puedes (Spring simplemente ignora estos eventos, son solo para ti).


Escriba a sus oyentes, escúchese a usted mismo, ctx.stop(); sus leyes, qué hacer en ctx.stop(); y qué hacer en ctx.start(); .


En total, el diagrama de interacción y ciclo de vida de la aplicación se parece a esto:




Los colores aquí muestran diferentes períodos de la vida.


  • Azul es Spring Boot, la aplicación ya ha comenzado. Esto significa que las solicitudes de servicio de Tomcat que llegan de los clientes se procesan, todo el contexto se plantea definitivamente, todos los beans funcionan, las bases de datos están conectadas, etc.
  • Verde: un evento ContextRefreshedEvent llegado y se construye el contexto. A partir de este momento, por ejemplo, los escuchas de aplicaciones comienzan a funcionar, lo que implementa ya sea configurando la anotación ApplicationListener o mediante la interfaz homónima con un genérico que escucha ciertos eventos. Si desea recibir más eventos, debe escribir el mismo ApplicationListener en spring.factories (el Spring habitual funciona aquí). Una barra indica dónde comienza el informe Spring Ripper .
  • En una etapa anterior, SpringApplication funciona, lo que prepara el contexto para nosotros. Este es el trabajo de preparar la aplicación que hicimos cuando éramos desarrolladores regulares de Spring. Por ejemplo, WebXML configurado.
  • Pero incluso hay etapas más tempranas. Muestra quién, dónde y para quién trabaja.
  • Todavía hay una etapa gris en la que es imposible acuñar de ninguna manera. Esta es la etapa en la que SpringApplication se ejecuta fuera de la caja (solo ingrese al código).

Si se dio cuenta, durante el informe de dos partes, fuimos de derecha a izquierda: comenzamos desde el final, atornillamos la configuración que voló desde el arranque, luego agregamos lo siguiente, etc. Ahora hablemos rápidamente toda la cadena en la dirección opuesta.
Usted escribe en su SpringApplication.run principal. Encuentra diferentes oyentes, les lanza un evento que comenzó a construir. Después de eso, los oyentes encuentran EnvironmentPostProcessor , les permiten configurar el entorno. Una vez que se configura el entorno, comenzamos a construir contexto (entra Carlson). Carlson crea el contexto y permite que todos los inicializadores de aplicaciones hagan algo con este contexto. Tenemos un punto de extensión. Después de esto, el contexto ya está configurado y luego comienza a suceder lo mismo que en la aplicación Spring habitual, cuando se construye el contexto: BeanFactoryPostProcessor , BeanPostProcessor , se configuran los beans. Esto es lo que hace Spring ordinaria.


Como correr


Hemos terminado de discutir el proceso de redacción de una solicitud.


Pero teníamos una cosa más que a los desarrolladores no les gusta. No les gusta pensar cómo, al final, comenzará su aplicación. ¿El administrador lo ejecutará en Tomcat, JBoss o en WebLogic? Solo tiene que funcionar. Si no funciona, en el peor de los casos, el desarrollador tendrá que configurar algo nuevamente


¿Cuáles son nuestros métodos de lanzamiento?


  • guerra de tomcat;
  • idea
  • java -jar/war .

Tomcat no es una tendencia masiva, no hablaremos de ello en detalle.


La idea tampoco es, en principio, muy interesante. Es un poco más complicado de lo que diré a continuación. Pero en Idea, en principio, no debería haber problemas. Ella ve qué tipo de dependencias traerá el motor de arranque.
Si hacemos java -jar , el problema principal es construir un classpath antes de iniciar la aplicación.


¿Qué hizo la gente en 2001? Escribieron java -jar qué jar debería ejecutarse, luego un espacio, classpath=... y los scripts se indicaron allí. En nuestro caso, hay 150 MB de varias dependencias que los iniciadores agregaron. Y todo esto debería hacerse manualmente. Naturalmente, nadie hace eso. Simplemente escribimos: java -jar , qué jar debe ejecutarse y eso es todo. De alguna manera, el classpath todavía se está construyendo. Hablaremos de esto ahora.


Comencemos con la preparación del archivo jar para que incluso se pueda iniciar sin Tomcat. Antes de hacer java -jar , necesitas construir un jar. Este frasco obviamente debería ser inusual, algún tipo de análogo de guerra, donde todo estará dentro, incluido Tomcat incrustado.


 <build> <plugins>    <plugin>       <groupId>org.springframework.boot</groupId>       <artifactId>spring-boot-maven-plugin</artifactId>    </plugin> </plugins> </build> 

Cuando descargamos el proyecto, alguien ya registró un complemento en nuestro POM. Aquí, por cierto, puede lanzar configuraciones, pero más sobre eso más adelante. jar, Maven Gradle , jar . :




:




war-.


, .


, jar. java -jar , , , , org.springframework.boot . . org.springframework.boot package. META-INF




Spring Boot MANIFEST ( Maven Gradle), main class, jar-.


, jar- : -, main-. java -jar -jar, , main-class-.


, , MANIFEST, main-class , main ( Idea). , . class path? java -jar , main, , — main, . MANIFEST JarLauncher.




Es decir , , JarLauncher. , main, class path.
, main? property — Start-class .


Es decir . 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- . Es decir , , . , , main class.
  • @SpringBootApplication — .

JarLauncher . Tomcat WarLauncher, war- , jar-.


, java -jar . ? Usted puede .


 <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-, , , .

Conclusiones


, 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 publicidad. 19-20 Joker 2018, « [Joker Edition]» , «Micronaut vs Spring Boot, ?» . , Joker . .

Source: https://habr.com/ru/post/es425333/


All Articles