En la montaña se encuentra Spring Boot ...

... sus cuatro están depurando.


Inspirado por un informe de Vladimir Plizgi ( Spring Boot 2: lo que no escriben en las notas de la versión ), decidí hablar sobre mi experiencia con Spring Booth, sus características y dificultades que encontré en mi camino.


Spring data jpa


El informe de Vladimir está dedicado a la migración a Spring Booth 2 y las dificultades que surgen. En la primera parte, describe los errores de compilación, por lo que también comenzaré con ellos.


Creo que muchos están familiarizados con el marco de Spring Data y sus derivados para varios almacenes de datos. Trabajé solo con uno de ellos: Spring Data JPA. La interfaz principal utilizada en este marco, JpaRepository , ha sufrido cambios significativos en las versiones 2. *.


Considere los métodos más utilizados:


  // interface JpaRepository<T, ID> { T findOne(ID id); List<T> findAll(Iterable<ID> ids); <S extends T> Iterable<S> save(Iterable<S> entities); } // interface JpaRepository<T, ID> { Optional<T> findById(ID id); List<T> findAllById(Iterable<ID> ids); <S extends T> Iterable<S> saveAll(Iterable<S> entities); } 

De hecho, hay más cambios, y después de pasar a la versión 2. * todos se convertirán en errores de compilación.


Cuando uno de nuestros proyectos planteó el problema de la migración a Spring Booth 2 y se tomaron los primeros pasos (reemplazar la versión en pom.xml), todo el proyecto literalmente se puso rojo: docenas de repositorios y cientos de llamadas a sus métodos llevaron a la migración "(usando nuevos métodos) engancharía cada segundo archivo. Imagina el código más simple:


 SomeEntity something = someRepository.findOne(someId); if (something == null) { something = someRepository.save(new SomeEntity()); } useSomething(something); 

Para que el proyecto solo se junte, necesita


  • llama a otro método
  • cambie el tipo de la variable something a Optional<SomeEntity>
  • reemplace la referencia de verificación vacía con Optional::isPresent u Optional::orElse / Optional::orElseGet
  • pruebas de reparación

Afortunadamente, existe una forma más sencilla, ya que Spring Date ofrece a los usuarios oportunidades sin precedentes para personalizar los repositorios según sus necesidades. Todo lo que necesitamos es definir (si aún no se han definido) la interfaz y la clase que formarán la base de todos los repositorios de nuestro proyecto. Esto se hace así:


 //    @NoRepositoryBean public interface BaseJpaRepository<T, ID extends Serializable> extends JpaRepository<T, ID> { //   findOne,     1.* @Deprecated T findOne(ID id); //    findAll @Deprecated //         List<T> findAll(Iterable<ID> ids); } 

Todos nuestros repositorios heredarán de esta interfaz. Ahora implementación:


 public class BaseJpaRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> implements BaseJpaRepository<T, ID> { private final JpaEntityInformation<T, ?> entityInfo; private final EntityManager entityManager; public BaseJpaRepositoryImpl(JpaEntityInformation<T, ?> entityInfo, EntityManager entityManager) { super(entityInfo, entityManager); this.entityInfo = entityInfo; this.entityManager = entityManager; } @Override public T findOne(ID id) { return findById(id).orElse(null); //    1.* } @Override public List<T> findAll(Iterable<ID> ids) { return findAllById(ids); //      } } 

El resto está a imagen y semejanza. Todos los errores de compilación desaparecen, el código funciona como antes, y solo se cambian 2 clases, y no 200. Ahora puede reemplazar lentamente la API obsoleta por una nueva a medida que avanza el desarrollo, ya que la Idea coloreará cuidadosamente todas las llamadas a los métodos @Deprecated amarillo.


Último golpe: informamos a Spring que a partir de ahora los repositorios se deben construir en la parte superior de nuestra clase:


 @Configuration @EnableJpaRepositories(repositoryBaseClass = BaseJpaRepositoryImpl.class) public class SomeConfig{ } 

En una piscina tranquila, se encuentran contenedores


Spring Booth se basa en dos conceptos: arranque y configuración automática. En la vida, la consecuencia de esto es una propiedad importante: la biblioteca que ingresó en el classpath de la aplicación es escaneada por el SB y si se encuentra una clase que incluya una determinada configuración, se activará sin su conocimiento e instrucciones explícitas. Mostramos esto con un simple ejemplo.


Muchos usan la biblioteca gson para convertir objetos en cadenas JSON y viceversa. A menudo puedes ver este código:


 @Component public class Serializer { public <T> String serialize(T obj) { return new Gson().toJson(obj); } } 

Con accesos frecuentes, este código cargará adicionalmente el recolector de basura, que no queremos. Las personas especialmente inteligentes crean un objeto y lo usan:


 @Configuration public class SomeConfig { @Bean public Gson gson() { return new Gson(); } } @Component @RequiredArgsConstructor public class Serializer { private final Gson gson; public String serialize(T obj) { return gson.toJson(obj); } } 

... sin siquiera darse cuenta de que Spring Booth puede hacer todo por sí mismo. Por defecto, Sat viene con la org.springframework.boot:spring-boot-autoconfigure , que incluye muchas clases llamadas *AutoConfiguration , por ejemplo, esto:


 @Configuration @ConditionalOnClass(Gson.class) @EnableConfigurationProperties(GsonProperties.class) public class GsonAutoConfiguration { @Bean @ConditionalOnMissingBean public GsonBuilder gsonBuilder(List<GsonBuilderCustomizer> customizers) { GsonBuilder builder = new GsonBuilder(); customizers.forEach((c) -> c.customize(builder)); return builder; } @Bean @ConditionalOnMissingBean public Gson gson(GsonBuilder gsonBuilder) { return gsonBuilder.create(); } } 

La configuración es simple como un riel: si hay una clase Gson en la aplicación y no hay un bean definido manualmente de este tipo, se creará una implementación predeterminada. Este es un poder tremendo, que permite una dependencia y un par de anotaciones para generar una configuración completa, cuya descripción solía tomar hojas XML y numerosos contenedores escritos a mano.


Pero esta es una gran insidia del Consejo de Seguridad. La insidiosidad reside en el secreto, porque mucho trabajo sucede bajo el capó según los guiones preescritos. Esto es bueno para una aplicación típica, pero abandonar la ruta establecida puede causar problemas: el convoy dispara sin previo aviso. Muchos frijoles se crean sin nuestro conocimiento, el ejemplo con Gson lo muestra bien. Tenga cuidado con la clase y sus dependencias, para ...


Cualquier cosa que entre en tu classpath puede ser usada en tu contra.


Un caso de la vida. Hay dos aplicaciones: una usa LdapTemplate , mientras que la otra lo ha usado una vez. Ambas aplicaciones dependen del proyecto core , donde se toman algunas clases comunes, y las bibliotecas comunes para ambas aplicaciones se plegaron cuidadosamente en pom.xml.


Después de un tiempo, desde el segundo proyecto, todos los usos de LdapTemplate se LdapTemplate como innecesarios. Pero la org.springramework:spring-ldap mantuvo en el core . Luego, la SB golpeó y org.springramework.ldap:ldap-core convirtió en org.springramework.boot:spring-boot-starter-data-ldap .


Debido a esto, aparecieron mensajes interesantes en los registros:


 Multiple Spring Data modules found, entering strict repository configuration mode! Spring Data LDAP - Could not safely identify store assignment for repository candidate interface .... 

Dependencia del core led org.springramework.boot:spring-boot-starter-data-ldap encajar en un proyecto en el que LDAP no se utiliza en absoluto. ¿Qué significa dudar? Golpea el classpath :). Además con todas las paradas:


  • Avisos de Spring Boot org.springramework.boot:spring-boot-starter-data-ldap en classpath
  • cada repositorio ahora se verifica para ver si es adecuado para usar con Spring Data JPA o Spring Data LDAP
  • La reorganización de dependencias y org.springramework.boot:spring-boot-starter-data-ldap eliminación de org.springramework.boot:spring-boot-starter-data-ldap innecesarios org.springramework.boot:spring-boot-starter-data-ldap reduce el tiempo de inicio de la aplicación en un promedio de 20 (!) segundos de un total de 40-50

Un lector atento y crítico probablemente preguntará: ¿por qué necesita cambiar org.springramework.ldap:ldap-core a org.springramework.boot:spring-boot-starter-data-ldap si Spring Data LDAP no se usa, pero solo se usa uno? clase org.springframework.ldap.LdapTempate ?


Respuesta: fue un error. El hecho es que antes de la versión 2.1.0.M1, el autoajuste para LDAP se parecía a esto


 @Configuration @ConditionalOnClass({ContextSource.class}) @EnableConfigurationProperties({LdapProperties.class}) public class LdapAutoConfiguration { private final LdapProperties properties; private final Environment environment; public LdapAutoConfiguration(LdapProperties properties, Environment environment) { this.properties = properties; this.environment = environment; } @Bean @ConditionalOnMissingBean public ContextSource ldapContextSource() { LdapContextSource source = new LdapContextSource(); source.setUserDn(this.properties.getUsername()); source.setPassword(this.properties.getPassword()); source.setBase(this.properties.getBase()); source.setUrls(this.properties.determineUrls(this.environment)); source.setBaseEnvironmentProperties(Collections.unmodifiableMap(this.properties.getBaseEnvironment())); return source; } } 

¿Dónde está el LdapTemplate ? Pero él no es :). Más precisamente, lo es, pero se encuentra en otra parte:


 @Configuration @ConditionalOnClass({LdapContext.class, LdapRepository.class}) @AutoConfigureAfter({LdapAutoConfiguration.class}) public class LdapDataAutoConfiguration { @Bean @ConditionalOnMissingBean({LdapOperations.class}) public LdapTemplate ldapTemplate(ContextSource contextSource) { return new LdapTemplate(contextSource); } } 

Por lo tanto, se supuso que LdapTemplate obtener LdapTemplate en su aplicación al satisfacer la @ConditionalOnClass({LdapContext.class, LdapRepository.class}) , que es posible cuando se agrega la dependencia spring-boot-starter-data-ldap a la ruta de clase.


Otra posibilidad: determinar este frijol con las manos, que no desea (porque ¿por qué necesitamos SB entonces?). org.springramework.boot:spring-boot-starter-data-ldap ocurrió esto después de reemplazar org.springramework.boot:spring-boot-starter-data-ldap con org.springramework.ldap:ldap-core .


El problema se resuelve aquí: https://github.com/spring-projects/spring-boot/pull/13136 . Cambio importante: la LdapTemplate bean LdapTemplate movió a LdapAutoConfiguration . Ahora puede usar LDAP sin vincular a spring-boot-starter-data-ldap y definir manualmente el contenedor LdapTemplate .


Alboroto de la capilla


Si usa Hibernate, probablemente esté familiarizado con el antipatrón abierto . No nos detendremos en su descripción, por referencia se describe con suficiente detalle, y en otras fuentes sus efectos nocivos se describen con gran detalle.


Una bandera especial es responsable de activar / desactivar este modo:


 spring: jpa: open-in-view: true 

En SB versión 1. * estaba habilitado de forma predeterminada, mientras que el usuario no estaba informado sobre esto. En las versiones 2. * todavía está habilitado, pero ahora se escribe una advertencia en el registro:


 WARN spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning. 

Inicialmente, hubo una solicitud para deshabilitar este modo, el episodio épico sobre el tema contiene varias docenas (!) De comentarios detallados, incluidos de Oliver Görke (desarrollador de Spring Date), Vlad Michalce (desarrollador de Hibernate), Phil Web y Vedran Pavic (desarrolladores de Spring) con pros y contras.


Acordaron que el comportamiento no cambiará, pero se mostrará una advertencia (que se observa). También hay un consejo bastante común para deshabilitar este modo:


 spring: jpa: open-in-view: false 

Eso es todo, escriba sobre su rastrillo y las características interesantes del Consejo de Seguridad; este es realmente un tema inagotable.

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


All Articles