Spring Boot 2: lo que no escriben en las notas de la versión



Cuando un proyecto a gran escala se somete a una actualización masiva, todo nunca es simple: inevitablemente hay matices obvios (en otras palabras, un rastrillo). Y luego, no importa cuán buena sea la documentación, solo la experiencia, la suya o la de otra persona, ayudará con algo.

En la conferencia Joker 2018, hablé sobre los problemas que encontré cuando cambié a Spring Boot 2 y cómo se resuelven. Y ahora especialmente para Habr: la versión de texto de este informe. Para mayor comodidad, la publicación tiene tanto una grabación de video como una tabla de contenido: no puede leer todo el contenido, pero vaya directamente al problema que le preocupa.

Tabla de contenidos





Buen dia Quiero contarles algunas características (llamémoslas rastrillos) que pueden surgir al actualizar Spring Boot Framework a la segunda versión y su posterior operación.

Mi nombre es Vladimir Plizgá ( GitHub ), trabajo para CFT, uno de los desarrolladores de software más grandes y antiguos de Rusia. Durante los últimos años, he estado desarrollando backend allí, responsable del desarrollo técnico del banco en línea de tarjetas prepagas. Fue en este proyecto que me convertí en el iniciador y ejecutor de la transición de una arquitectura monolítica a una arquitectura de microservicios (que todavía está en curso). Bueno, dado que la mayor parte del conocimiento que decidí compartir con usted se ha acumulado en el ejemplo de este proyecto en particular, le contaré un poco más al respecto.

Brevemente sobre el producto experimental.


Este es un banco de Internet que sirve a más de dos docenas de empresas asociadas en Rusia: brinda a los clientes finales la capacidad de administrar su dinero a través de servicios bancarios remotos (aplicaciones móviles, sitios). Uno de los socios es Beeline y su tarjeta de pago. Resultó ser una buena banca por Internet, a juzgar por la calificación de Markswebb Mobile Banking Rank , donde nuestro producto tomó una buena posición para los principiantes.

Las "tripas" todavía están en transición, por lo que tenemos un monolito, el llamado núcleo, alrededor del cual se construyen 23 microservicios. En el interior, los microservicios de Spring Cloud Netflix, Spring Integration y más. Y en Spring Boot 2, todo esto ha estado volando desde aproximadamente el mes de julio. Y justo en este lugar nos detenemos con más detalle. Al traducir este proyecto a la segunda versión, me encontré con algunas características que quiero contarles.

Resumen del informe




Hay muchas áreas donde aparecieron las características de Spring Boot 2, trataremos de repasar todas. Para hacer esto rápidamente, necesitamos un detective o investigador experimentado, alguien que descubra todo esto como si fuera por nosotros. Como Holmes y Watson ya hicieron una presentación en Joker, seremos asistidos por otro especialista, el teniente Colombo. ¡Adelante!

Spring boot / 2


Primero, algunas palabras sobre Spring Boot en general y la segunda versión en particular. En primer lugar, se lanzó esta versión, por decirlo suavemente, no ayer: el 1 de marzo de 2018, ya estaba en Disponibilidad general. Uno de los principales objetivos que persiguieron los desarrolladores fue admitir completamente Java 8 en el nivel de origen. Es decir, no se puede compilar en una versión más pequeña, aunque el tiempo de ejecución es compatible. Se tomó como base el Spring Framework de la quinta versión, que se lanzó un poco antes que Spring Boot 2. Y esta no es la única dependencia. También tiene un concepto como BOM (Bill Of Materials): este es un enorme XML, que enumera todas las dependencias (transitivas para nosotros) de todo tipo de bibliotecas de terceros, marcos adicionales, herramientas y más.

En consecuencia, no todos los efectos especiales que trae el segundo Spring Boot provienen de sí mismos o del ecosistema de Spring. Se han escrito dos documentos excelentes para toda esta granja: Notas de la versión y la Guía de migración . Los documentos son geniales, la primavera en este sentido generalmente está bien hecha. Pero, por razones obvias, está lejos de ser posible cubrir todo lo que hay allí: hay algunos detalles, desviaciones, etc. que no pueden o no deben incluirse allí. Hablaremos de tales características.

Tiempo de compilación Ejemplos de cambio de API


Comencemos con el rastrillo más o menos simple y obvio: estos son los que surgen en tiempo de compilación. Es decir, algo que ni siquiera le permitirá compilar el proyecto si simplemente cambia el número 1 en el script de arranque a 2.

La principal fuente de cambios, que se convirtió en la base para tales ediciones en Spring Boot, es, por supuesto, la transición de Spring a Java 8. Además, la pila web de Spring 5 y Spring Boot 2 se divide, en términos relativos, en dos. Ahora es servlet, tradicional para nosotros y reactivo. Además, era necesario tener en cuenta una serie de deficiencias de versiones anteriores. Se iniciaron bibliotecas de terceros (desde fuera de Spring). Si miras las Notas de la versión, no verás ningún obstáculo sobre la marcha y, francamente, cuando leí las Notas de la versión por primera vez, me pareció que todo estaba bien allí. Y para mí se parecía a esto:


Pero, como probablemente adivine, no todo es tan bueno.

En qué se romperá la compilación (ejemplo 1):

  • Por qué : la clase WebMvcConfigurerAdapter ya no existe;
  • Por qué : para admitir chips Java 8 (métodos predeterminados en las interfaces);
  • Qué hacer : use la interfaz WebMvcConfigurer .

Un proyecto puede no compilarse al menos debido al hecho de que algunas clases simplemente ya no existen. Por qué Sí, porque en Java 8 no son necesarios. Si se tratara de adaptadores con una implementación primitiva de métodos, entonces no hay nada especial que explicar, los métodos predeterminados resuelven todo esto perfectamente. Aquí hay un ejemplo de esta clase, está claro que es suficiente usar la interfaz en sí, y no se necesitan adaptadores.

En qué compilación se romperá (ejemplo 2):

  • Por qué : el método de PropertySourceLoader#load comenzó a devolver una lista de fuentes en lugar de una;
  • Por qué : para admitir recursos de documentos múltiples, por ejemplo, YAML;
  • Qué hacer : ajustar la respuesta en singletonList() (cuando se reemplaza).

Un ejemplo de un área completamente diferente. Algunos métodos incluso han cambiado las firmas. Si alguna vez usó el método load PropertySourceLoader, ahora devuelve una colección. En consecuencia, esto permitió el soporte de recursos de documentos múltiples. Por ejemplo, en YAML, a través de tres guiones, puede especificar un grupo de documentos en un archivo. Si ahora necesita trabajar con él desde Java, tenga en cuenta que esto debe hacerse a través de la colección.

En qué compilación se romperá (ejemplo 3):

  • Por qué : algunas clases del paquete org.springframework.boot.autoconfigure.web han org.springframework.boot.autoconfigure.web de los paquetes .servlet : .servlet y .reactive ;
  • Por qué : para apoyar el jet stack a la par de lo tradicional;
  • Qué hacer : actualizar las importaciones.

Incluso se han introducido más cambios, por lo que se apilan. Por ejemplo, lo que solía estar en el mismo paquete web ahora se ha dividido en dos paquetes con un montón de clases. Estos son .servlet y .reactive . ¿Por qué se hace? Porque no se suponía que el jet stack fuera una enorme muleta encima del servlet. Era necesario hacer esto para que pudieran mantener su propio ciclo de vida, desarrollarse en sus propias direcciones y no interferir entre sí. ¿Qué hacer al respecto? Suficiente para cambiar las importaciones: la mayoría de estas clases seguían siendo compatibles a nivel API. La mayoría, pero no todos.

En qué compilación se romperá (ejemplo 4):

  • Por qué : la firma de los métodos de la clase ErrorAttributes ha ErrorAttributes : en lugar de RequestAttributes , WebRequest(servlet) y ServerRequest(reactive) comenzaron a usarse;
  • Por qué : para apoyar el jet stack a la par de lo tradicional;
  • Qué hacer : reemplazar los nombres de clase en las firmas.

Por ejemplo, en la clase ErrorAttributes, ahora, en lugar de RequestAttributes, otras dos clases comenzaron a usarse en métodos: estos son WebRequest y ServerRequest. El motivo es el mismo. ¿Y qué hacer al respecto? Si cambia del primer Spring Boot al segundo, debe cambiar RequestAttributes a WebRequest. Bueno, si ya estás en el segundo, usa ServerRequest. Obviamente, ¿no es así?

Como ser


Hay bastantes ejemplos de este tipo; no los resolveremos todos. ¿Qué hacer al respecto? En primer lugar, vale la pena echar un vistazo a la Guía de migración de Spring Boot 2.0 para notar el cambio que le concierne a tiempo. Por ejemplo, menciona renombrar clases completamente no obvias. Aún así, si algo se ha separado y roto, vale la pena considerar que el concepto de "web" se divide en 2: "servlet" y "reactivo". Con orientación en todas las clases y paquetes, esto puede ayudar. Además, debe tenerse en cuenta que no solo se cambió el nombre de las clases y paquetes, sino también dependencias y artefactos completos. Como esto, por ejemplo, sucedió con Spring Cloud.



Tipo de contenido. Determinar el tipo de respuesta HTTP


Suficiente sobre estas cosas simples desde el momento de la compilación, todo es claro y simple allí. Hablemos de lo que puede suceder en tiempo de ejecución y, en consecuencia, puede disparar, incluso si Spring Boot 2 ha estado trabajando para usted durante mucho tiempo. Hablemos de la definición de tipo de contenido.



No es ningún secreto que Spring puede escribir aplicaciones web, tanto API de página como REST, y puede representar contenido con una amplia variedad de tipos, ya sea XML, JSON u otra cosa. Y uno de los encantos que tanto le gusta a Spring es que no tiene que preocuparse por la definición del tipo que se proporciona en su código. Puedes esperar magia. Esta magia funciona, en términos relativos, de tres maneras diferentes: se basa en el encabezado Aceptar que vino del cliente, o en la extensión del archivo solicitado, o en un parámetro especial en la URL, que, por supuesto, también se puede dirigir.

Considere un ejemplo simple ( código fuente completo ). En adelante utilizaré la notación de Gradle, pero incluso si eres un fanático de Maven, no será difícil para ti entender lo que está escrito aquí: creamos una pequeña aplicación en el primer Spring Boot y usamos solo una web de inicio.

Ejemplo (v1.x):

 dependencies { ext { springBootVersion = '1.5.14.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") } 

Como código ejecutable, tenemos una sola clase en la que el método del controlador se declara inmediatamente.

 @GetMapping(value = "/download/{fileName: .+}", produces = {TEXT_HTML_VALUE, APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE}) public ResponseEntity<Resource> download(@PathVariable String fileName) { //   ,  Content-Type } 

Toma un cierto nombre de archivo como entrada, que supuestamente formará y dará. Realmente forma su contenido en uno de los tres tipos indicados (determinando esto por el nombre del archivo), pero no especifica el tipo de contenido de ninguna manera: tenemos Spring, él hará todo por sí mismo.



En general, incluso puedes intentar hacerlo. De hecho, si solicitamos el mismo documento con diferentes extensiones, será devuelto con el tipo de contenido correcto dependiendo de lo que devolvamos: si quieres - json, si quieres - txt, si quieres - html. Funciona como un cuento de hadas.

Actualizando a v2.x


 dependencies { ext { springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") } 

Es hora de actualizar al segundo Spring Boot. Simplemente cambiamos el número 1 a 2.



Spring MVC Path Matching Cambio de comportamiento predeterminado

Pero somos ingenieros, echaremos un vistazo a la Guía de Migración, y de repente se dice algo al respecto. Pero menciona algún tipo de "coincidencia de ruta de sufijo". Se trata de cómo mapear correctamente los métodos en Java con una URL. Pero este no es nuestro caso, aunque un poco como.



¡Por lo tanto, marcamos, verificamos y golpeamos! - De repente no funciona. Por alguna razón, solo texto / html comienza a darse en todas partes, y si lo cava, no es solo texto / html, sino solo el primero de los tipos que especificó en el atributo produce en la anotación @GetMapping. Por qué Parece, por decirlo suavemente, incomprensible.



Y aquí, las Notas de la versión no ayudarán, debe leer la fuente.

ContentNegotiationManagerFactoryBean


 public ContentNegotiationManagerFactoryBean build() { List<ContentNegotiationStrategy> strategies = new ArrayList<>(); if (this.strategies != null) { strategies.addAll(this.strategies); } else { if (this.favorPathExtension) { PathExtensionContentNegotiationStrategy strategy; // … 

Allí puede encontrar un clásico con un nombre corto lacónico muy comprensible, que menciona un determinado indicador llamado "considerar extensión en el camino" (favorPathExtension). El valor de este indicador "verdadero" corresponde a la aplicación de una determinada estrategia con otro nombre conciso corto y comprensible, a partir del cual está claro que solo es responsable de determinar el tipo de contenido por extensión de archivo. Como puede ver, si la bandera es falsa, entonces la estrategia no se aplicará.



Sí, probablemente, muchos notaron que en primavera, aparentemente, hay algún tipo de pauta, por lo que el nombre debe estar bien, al menos veinte caracteres de largo.



Si profundizas un poco más, puedes cavar un fragmento así. En el propio marco de Spring, y no en la quinta versión, como cabría esperar, pero desde tiempos inmemoriales, este indicador por defecto se ha establecido en "verdadero". Mientras estaba en Spring Boot y en la segunda versión, fue bloqueado por otro, que ahora está disponible para el control desde la configuración. Es decir, ahora podemos desviarlos de la gestión ambiental, y esto es solo en la segunda versión. ¿Te sientes? Allí ya asumió el significado de "falso". Es decir, querían, más o menos, hacer lo mejor posible, poner esta bandera en la configuración (y esto es genial), pero el valor predeterminado se cambió a otro (esto no es muy).

Los desarrolladores del marco también son personas, también tienden a cometer errores. ¿Qué hacer al respecto? Está claro que necesita cambiar el parámetro en su proyecto, y todo estará bien.

Lo único que vale la pena hacer por si acaso, para despejar la conciencia, es buscar en la documentación de Spring Boot cualquier mención de esta bandera. Y allí se lo menciona realmente, pero solo en un contexto extraño:
Si comprende las advertencias y aún desea que su aplicación use la coincidencia de patrón de sufijo, se requiere la siguiente configuración:
spring.mvc.contentnegotiation.favor-path-extension = true
...
Está escrito, dicen, si comprende todos los trucos y aún desea utilizar la coincidencia de ruta de sufijo, marque esta casilla. Siente la discrepancia? Parece que estamos hablando de la definición de tipo de contenido en el contexto de este indicador, pero aquí estamos hablando de la coincidencia de métodos y URL de Java. Parece de alguna manera incomprensible.

Tenemos que cavar más lejos. Existe una solicitud de extracción en GitHub:



Dentro del marco de esta solicitud de extracción, se realizaron estos cambios, cambiando el valor predeterminado, y allí uno de los autores del marco dice que este problema tiene dos aspectos: uno es solo la coincidencia de ruta y el segundo es la definición del tipo de contenido . Es decir, en otras palabras, la bandera se aplica a ambos, y están inextricablemente vinculados.

Podría, por supuesto, encontrarlo de inmediato en GitHub si supiera dónde buscar.


Partido de sufijo

Además, la documentación del Spring Framework en sí mismo también afirma que el uso de extensiones de archivo era necesario anteriormente, pero ahora ya no se considera una necesidad. Además, resultó ser problemático en varios casos.

Resumir


Cambiar el valor predeterminado de la bandera no es un error, sino una característica. Está inextricablemente vinculado con la definición de coincidencia de ruta y está diseñado para hacer tres cosas :

  • reducir los riesgos de seguridad (cuáles aclararé);
  • Alinear el comportamiento de WebFlux y WebMvc, diferían en este aspecto;
  • alinee la declaración en la documentación con el código marco.

Como ser


Primero, siempre que sea posible, no debe confiar en la definición de tipo de contenido por extensión. El ejemplo que mostré es un contraejemplo, ¡no es necesario hacer esto! Además, no es necesario confiar en el hecho de que las solicitudes del formulario "OBTENER algo.json", por ejemplo, simplemente "OBTENER algo". Este fue el caso en Spring Framework 4 y en Spring Boot 1. Esto ya no funciona. Si necesita asignar a un archivo con la extensión, debe hacerlo explícitamente. En cambio, es mejor confiar en el encabezado Aceptar o en el parámetro URL, cuyo nombre puede dirigir. Bueno, si no puede hacerlo de ninguna manera, supongamos que tiene algunos clientes móviles antiguos que dejaron de actualizarse en el siglo pasado, tendrá que devolver este indicador, configurarlo como "verdadero", y todo funcionará como antes.

Además, para una comprensión general, puede leer el capítulo "Coincidencia de sufijos" en la documentación del Spring Framework, es considerado por los desarrolladores como una especie de colección de mejores prácticas en esta área, y familiarizarse con lo que es el ataque Reflected File Download , solo implementado usando manipulaciones con extensión de archivo

Programación Tareas programadas o periódicas


Cambiemos un poco el alcance y hablemos de completar tareas en un horario o periódicamente.

Un ejemplo de una tarea. Mensaje de registro cada 3 segundos


Lo que se dice, creo, es comprensible. Tenemos algunas necesidades comerciales, hacer algo con algún tipo de repetición, por lo que procederemos inmediatamente al ejemplo. Supongamos que tenemos una tarea megacompleja: generar algo de suciedad en el registro cada 3 segundos.



Esto se puede hacer, obviamente, en una variedad de formas, para ellos de cualquier manera ya hay algo en primavera. Y encuéntralo, de muchas maneras.

Opción 1: busque un ejemplo en su proyecto


 /** *A very helpful service */ @Service public class ReallyBusinessService { // … a bunch of methods … @Scheduled(fixedDelay = 3000L) public void runRepeatedlyWithFixedDelay() { assert Runtime.getRuntime().availableProcessors() >= 4; } // … another bunch of methods … } 

Podemos buscar en nuestro propio proyecto y probablemente encontraremos algo como esto. Una anotación dependerá del método público, y quedará claro que tan pronto como la cuelgues, todo funciona como en un cuento de hadas.

Opción 2: busque la anotación deseada




Puede buscar la anotación directamente por nombre, y también quedará claro en la documentación que la cuelga, y todo funciona.

Opción 3: buscar en Google


Si no tienes fe en ti mismo, puedes buscarlo en Google y, por lo que has encontrado, también estará claro que todo comenzará con una anotación.

 @Component public class EventCreator { private static final Logger LOG = LoggerFactory.getLogger(EventCreator.class); private final EventRepository eventRepository; public EventCreator(final EventRepository eventRepository) { this.eventRepository = eventRepository; } @Scheduled(fixedRate = 1000) public void create() { final LocalDateTime start = LocalDateTime.now(); eventRepository.save( new Event(new EventKey("An event type", start, UUID.randomUUID()), Math.random() * 1000)); LOG.debug("Event created!"); } } 

¿Quién ve la trampa en esto? Somos ingenieros después de todo, veamos cómo funciona esto en realidad.

¡Muéstrame el código!


Considere una tarea específica (la tarea misma y el código están en mi repositorio ).

Quien no quiera leer, puede ver este fragmento del video con una demostración (hasta el minuto 22):


Como dependencia, usaremos el primer Spring Boot con dos iniciadores. Uno es para la web, es como si estuviéramos desarrollando un servidor web, y el segundo es un actuador de arranque por resorte para que tengamos características listas para la producción para que al menos nos parezcamos a algo real.

 dependencies { ext { springBootVersion = '1.5.14.RELEASE' // springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") compile("org.springframework.boot:spring-boot-starter-actuator:$springBootVersion") // +100500      } 

Y nuestro código ejecutable será aún más simple.

 package tech.toparvion.sample.joker18.schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.Scheduled; @SpringBootApplication public class EnableSchedulingDemoApplication { private static final Logger log = LoggerFactory.getLogger(EnableSchedulingDemoApplication.class); public static void main(String[] args) { SpringApplication.run(EnableSchedulingDemoApplication.class, args); } @Scheduled(fixedRate = 3000L) public void doOnSchedule() { log.info(“ 3     …”); } } 

En general, casi nada notable, excepto por el único método en el que colgamos la anotación. Lo copiamos en alguna parte y esperamos que funcione.

Vamos a ver, somos ingenieros. Empezamos Suponemos que cada tres segundos se registrará dicho mensaje. Todo debería salir de la caja, nos aseguramos de que todo se inicie en el primer Spring Boot, y esperamos la salida de la línea deseada. Pasan tres segundos: se emite una línea, seis pasadas: se muestra una línea. Los optimistas ganaron, todo funciona.



Solo llega el momento de actualizar al segundo Spring Boot. No nos molestaremos, solo cambiaremos de uno a otro:

 dependencies { ext { // springBootVersion = '1.5.14.RELEASE' springBootVersion = '2.0.4.RELEASE' } 

En teoría, la Guía de Migración no nos advirtió sobre nada, y esperamos que todo funcione sin desviaciones. Desde el punto de vista del código ejecutable, no tenemos ninguno de los otros rastrillos que mencioné anteriormente (incompatibilidad a nivel API u otra cosa), ya que la aplicación es lo más simple posible.

Empezamos En primer lugar, estamos convencidos de que estamos trabajando en el segundo Spring Boot, de lo contrario, al parecer, no hay desviaciones.



Sin embargo, pasan 3 segundos, 6, 9, pero todavía no hay Herman, no hay conclusión, nada funciona.
Como sucede a menudo, las expectativas están en desacuerdo con la realidad. A menudo nos escriben en la documentación que, de hecho, todo funciona de forma inmediata en Spring Boot, que en general simplemente podemos comenzar como está con problemas mínimos, y no se requiere configuración. Pero tan pronto como se hace realidad, a menudo resulta que todavía se debe leer la documentación. En particular, si profundiza, puede encontrar aquí estas líneas:
7.3.1. Habilitar anotaciones de programación
Para habilitar la compatibilidad con las anotaciones @Scheduled y Async , puede agregar @EnableScheduling y @EnableAsync a una de sus clases de @Configuration.
Para que la anotación programada funcione, debe colgar otra anotación en la clase con otra anotación. Bueno, como siempre en primavera. ¿Pero por qué funcionó antes? No hicimos nada de eso. Obviamente, esta anotación en algún lugar colgó antes en el primer Spring Boot, pero ahora por alguna razón no está en el segundo.



Comenzamos a hurgar en los códigos fuente del primer Spring Boot. Encontramos que hay una clase en la que supuestamente depende. Miramos más de cerca, se llama "MetricExportAutoConfiguration" y, aparentemente, es responsable de entregar estas métricas de rendimiento al exterior, a algunos agregadores centralizados, y realmente tiene esta anotación.



Además, funciona de tal manera que incluye su comportamiento en toda la aplicación a la vez, no necesita colgarse en clases separadas. Fue esta clase la que proporcionó este comportamiento y, por alguna razón, no lo hizo. Por qué



De todos modos, GitHub nos empuja a una excavación arqueológica: como parte de la transición a la segunda versión de Spring Boot, esta clase se cortó junto con la anotación. Por qué Sí, porque el motor de entrega de métricas también ha cambiado: ya no usan su propio script, sino que cambiaron a Micrometer, una solución realmente significativa. Eso es algo superfluo que le queda. Quizás esto sea incluso correcto.

Quien no quiera leer, vea una breve demostración durante 30 segundos:


Se deduce que si ahora tomamos y colgamos manualmente la anotación faltante en nuestra clase original, entonces, en teoría, el comportamiento debería ser correcto.

 package tech.toparvion.sample.joker18.schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; @SpringBootApplication @EnableScheduling public class EnableSchedulingDemoApplication { private static final Logger log = LoggerFactory.getLogger(EnableSchedulingDemoApplication.class); public static void main(String[] args) { SpringApplication.run(EnableSchedulingDemoApplication.class, args); } @Scheduled(fixedRate = 3000L) public void doOnSchedule() { log.info(“ 3     …”); } } 

¿Crees que funcionará? Vamos a ver Empezamos



Se puede ver que después de 3 segundos, después de 6 y después de 9, el mensaje esperado por nosotros todavía se muestra en el registro.

Como ser


¿Qué hacer con esto en este caso particular y más general? No importa cuán moralista pueda sonar, en primer lugar, vale la pena leer no solo fragmentos de documentación copiados, sino también un poco más amplios, solo para cubrir tales aspectos.

En segundo lugar, recuerde que en Spring Boot, aunque muchas funciones están listas para usar (programación, asíncrona, almacenamiento en caché, ...), no siempre se incluyen, deben habilitarse explícitamente.

En tercer lugar, no se molesta en estar seguro: agregue anotaciones Enable * (y toda su familia) a su código, sin esperar un marco. Pero entonces surge la pregunta: ¿qué pasará si por casualidad yo y mis colegas agregamos algunas anotaciones, cómo se comportarán? , . : . , .

, @EnableAsync Enable Caching , , , , , . , . ? javadoc , . , . , Enable *, , . ? .

Spring Cloud & Co.




Spring Boot 2 , Spring Cloud — Service Discovery ( ). JavaMelody. - . , , JDBC, H2.



, , JavaMelody — , , . dev-, test, - , Prometheus.

Gradle :

 dependencies { ext { springBootVersion = '2.0.4.RELEASE' springCloudVersion = '2.0.1.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") runtime("org.springframework.boot:spring-boot-starter-jdbc:$springBootVersion") runtime group: "org.springframework.cloud", name: "spring-clooud-starter-netflix-eureka-client", version: springCloudVersion runtime("net.bull.javamelody:javamelody-spring-boot-starter:1.72.0") //… } 

( )

Spring Boot — web jdbc, Spring Cloud eureka (, , Service Discovery), JavaMelody. .

 @SpringBootApplication public class HikariJavamelodyDemoApplication { public static void main(String[] args) { SpringApplication.run(HikariJavamelodyDemoApplication.class, args); } } 

Empezamos



. , , - com.sun.proxy Hikari, HikariDataSource. , Hikari — , Tomcat, C3P0 .


? .


Spring Cloud dataSource


, Spring Cloud , dataSource ( ), . , AutoRefresh RefreshScope — . . CGLIB.

, , Spring Boot Spring : JDK ( , ) CGLIB ( ). BeanPostProcessor' BeanDefinition , .

JavaMelody dataSource


— JavaMelody. DataSource , , . JavaMelody JDK-, , . — BeanPostProcessor.

, , DataSource JDK-, CGLIB-. :



. , .

Spring Boot dataSource.unwrap()


Spring Boot, DataSource#unwrap(), JMX. JDK- ( ), CGLIB-, Spring Cloud, Spring Context. , , JDK-, CGLIB API .

, :


https://jira.spring.io/browse/SPR-17381

, , , . , , , , - .



. Hikari?

, Hikari - , Spring Cloud . : Hikari Spring Boot 2. ? - - . , Spring Cloud? , - , ? . , .

…


 org.springframework.cloud.autoconfigure.RefreshAutoConfiguration .RefreshScopeBeanDefinitionEnhancer: /** * Class names for beans to post process into refresh scope. Useful when you * don't control the bean definition (eg it came from auto-configuration). */ private Set<String> refreshables = new HashSet<>( Arrays.asList("com.zaxxer.hikari.HikariDataSource")); 

Spring Cloud autoconfiguration, Enhancer BeanDefinition', , Hikari. Spring Cloud . .

? Spring Cloud , CGLIB-. , , , , - . (jira.spring.io/browse/SPR-17381). BeanPostProcessor, . BeanDefinition , BeanPostProcessor'. Stack Overflow , - , , proxyTargetClass true false , . , . .

, - , .

:

  • (, Tomcat JDBC Pool)
    spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource

    runtime 'org.apache.tomcat:tomcat-jdbc:8.5.29'
    Hikari , , , , , Tomcat, Spring Boot.
  • JavaMelody, JDBC-, .
    javamelody.excluded-datasources=scopedTarget.dataSource
  • Spring Cloud.
    spring.cloud.refresh.enabled=false
    , , , Service Discovery, .


. , .

( *)


* Spring Cloud ( JavaMelody)

 @Component @ManagedResource @EnableAsync public class MyJmxResource { @ManagedOperation @Async public void launchLongLastingJob() { // -   } } 

: github.com/toparvion/joker-2018-samples/tree/master/jmx-resource .

. , Spring Cloud. JavaMelody , Spring-, . , , , JMX . - , Async, JMX- . JMX, @ManagedOperation, , ( Spring — , OK).

, , , , , myJMXResource JMX, . , — , CGLIB JDK.



JDK CGLIB-. , - BeanPostProcessor.

, BeanPostProcessor':
AsyncAnnotationBeanPostProcessor

  • : Async
  • : org.springframework.scheduling
  • : @EnableAsync ( Import )

2. DefaultAdvisorAutoProxyCreator
  • : AOP-,
  • : org.springframework.aop.framework.autoproxy
  • : @Configuration- PointcutAdvisorConfig ( )

DefaultAdvisorAutoProxyCreator @Configuration-. , , JavaMelody, configuration-. , PointcutAdvisorConfig, .



, . PointcutAdvisorConfig, AdvisorConfig, , configuration-, , , , , .

, , , , -.



BeanPostProcessor'. , , , BeanPostProcessor . , Advised ( BeanPostProcessor'), , , , , , JDK-, . .

. , :



JMX . BeanPostProcessor. BeanPostProcessor', , , , , JAR, , .

Como ser


-, , Spring AOP, , . « »? , - Advice Advisor, , .

-, best practices. , JMX- , . - , , , . , autowire' () . . , , - . Order , . , , , .. proxyTargetClass, .

: , . -, «Keep calm and YAGNI». , . « », - , - , , , . , , : -, , — , . . , Spring , , . tolkkv , , 436- , . , .

Relax Binding. ()


, .


https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#boot-features-external-config-relaxed-binding

, Relax Binding Spring Boot. - . , - firstName , acme.my-project.person, Spring Boot . : camel case, , , - — firstName. Relax Binding.

Spring Boot' , , — . , , , :


, , . - . , .

Por ejemplo:

 dependencies { ext { springBootVersion = '1.5.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter:$springBootVersion") } 

( )

- , web, Spring Boot, - .

 @SpringBootApplication public class RelaxBindingApplication implements ApplicationRunner { private static final Logger log = LoggerFactory.getLogger(RelaxBindingDemoApplication.class); @Autowired private SecurityProperties securityProperties; public static void main(String[] args) { SpringApplication.run(RelaxBindingDemoApplication.class, args); } @Override public void main run(ApplicationArguments args) { log.info("KEYSTORE TYPE IS: {}", securityProperties.getKeyStoreType()); } } 

, POJO- ( ) , KEYSTORE TYPE. POJO , applications.properties application.yaml, .

keystoreType, private String keystoreType, applications.properties: security.keystoreType=jks.

 @Component @ConfigurationProperties(prefix = "security") public class SecurityProperties { private String keystorePath; private String keystoreType; public String getKeystorePath() { return keystorePath; } public void setKeystorePath(String keystorePath) { this.keystorePath = keystorePath; } public String getKeyStoreType() { return keystoreType; } public void setKeystoreType(String keystoreType) { this.keystoreType = keystoreType; } } 

Spring Boot .



, , , . , .



, . , , , - , , - key-store-type. , , , .

. , .



. 2 , , . Java properties — , . , , , , , . — Java bean . , , . , «keystore» , : «Key» «Store». …



, , ? Nosotros entendemos

, , Relax Binding ( getStoreType()). , . , . , keyStoreType, . , Relax Binding, , , .

, - , - , . , . :



, - , , , , -, . .

Como ser


: - . -, , dev- , , YAML properties, — , , . -, c , Relax Binding, . , , , Spring Boot .

Unit Testing. Mockito 2


, - , Mockito.

Mockito , Spring Boot Starter, Spring, Mockito.

 $gradle -q dependencyInsight --configuration testCompile --dependency mockito org.mockito:mockito-core:2.15.0 variant "runtime" /--- org.springframework.boot:spring-boot-starter-test:2.0.2.RELEASE /---testCompile 

? . Spring Boot Mockito , 1.5.2 Spring Boot Mockito 2, . - . Mockito 2.

Mockito , 2016- Mockito 2.0 Mockito.2.1— : Java 8 , Hamcrest - . , , .

, , ( ) .



, , JButton Swing, null, , - . , string' null, , null instanceof string. , Mockito 1 , Mockito 2 , anyString null , , . : null, . , , , Mockito 1.

, , .

 public class MyService { public void setTarget(Object target) { //… } } <hr/> @Test public void testAnyStringMatcher() { MyService myServiceMock = mock(MyService.class); myServiceMock.setTarget(new JButton()); verify(myServiceMock).setTarget(anyString()); } 

, , . JButton. anyString. , : , — . , . - 10- , . Mockito 1 , :



Mockito 2 , , , anyString :



. , . , , , , SocketTimeoutException, - . , SocketTimeoutException , . Mockito 1.



Mockito 2 , :



, Mockito 1 , , . new SocketTimeoutException new, constructor, Mockito 1 .

¿Qué hacer al respecto? , , RuntimeException, , Mockito .

. , . compile-time. - , Hamcrest. Spring Boot, Mockito 1 @MockBean @SpyBean. , Spring Integration, review.
(: https://docs.spring.io/spring-integration/docs/5.0.0.RELEASE/reference/htmlsingle/#testing )

Como ser


, , Mockito 1, Mockito 2 ( dzone.com/refcardz/mockito ).

-, , , Spring Boot 1.5.2 Mockito 2.

-, , , Mockito 2, : , .

Gradle Plugin. Spring Boot


, , — Spring Boot- Gradle.

Migration Guide , Spring Boot Gradle . , . : Gradle 4 (, settings.gradle ). dependency management plugin, . bootRepackage, , : bootWar bootJar. bootJar .

bootJar:

  • , org.springframework.boot java;
  • jar;
  • mainClassName ( ) ( , - ).

, , — , , Gradle, Spring Boot.

? - Spring Boot 2, , , Gradle 4 Spring Boot-. , , : , , ( , ).



, . app1 , app2, app3 . . app1 lib.

«Show me the code!»




 subprojects { repositories { mavenCentral() } apply plugin: 'java' apply plugin: 'org.springframework.boot' } 

— : java Spring Boot .

, , , . , , , . .

app1:

 dependencies { ext { springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter:$springBootVersion") compile project(':lib') } 

lib , Spring Boot-.

app1:

 @SpringBootApplication public class GradlePluginDemoApplication implements ApplicationRunner { //… @Override public void run(ApplicationArguments args) { String appVersion = Util.getAppVersion(getClass()); log.info("Current application version: {}", appVersion); } } 

Util, .

lib:

 public abstract class Util { public static String getAppVersion(Class<?> appClass) { return appClass.getPackage().getImplementationVersion(); } } 

Util getAppVersion , , ImplementationVersion . .



IDE, , , . gradle build IDE , . Util. , , , , .


:

  • bootJar jar;
  • Gradle jar.

:
  • ;
  • , jar ( ImplementationVersion), .

? : .



Spring Boot- , , lib . .

2: SB Gradle Plugin Spring Boot-

 bootJar { enabled = false } 

, - , , , , , bootJar . , jar , .

Otros


, , , Spring Boot. .



Spring Boot : web-, , . - , , Spring Boot properties migrator, , , . , , -, .

Actuator. , () , Spring Security. .



Spring Cloud . , . , Netflix Feign.



Spring Integration, , Spring Framework, . — , Java DSL , , . , , , , handle handleWithAdapter.

.


, , :



, , , Web, .

(Properties Binding), , , Relax Binding.

— , : , AOP , , Spring Boot 2 .

, , , — , Mockito 1 Mockito 2. - , ?


-, , , , YAGNI. , - , . , , .

-, - - , , , . , , , , . Migration Guide. , , , , , .

, Spring Boot. , Spring Boot, , … .
, : 5-6 JPoint , Spring Boot: Spring Boot- Java 8 Java 11. — .

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


All Articles