... ses quatre déboguent.
Inspiré par un rapport de Vladimir Plizgi ( Spring Boot 2: ce qu'ils n'écrivent pas dans les notes de version ), j'ai décidé de parler de mon expérience avec Spring Booth, de ses fonctionnalités et des pièges que j'ai rencontrés en chemin.
Spring data jpa
Le rapport de Vladimir est consacré à la migration vers Spring Booth 2 et aux difficultés qui en découlent. Dans la première partie, il décrit les erreurs de compilation, je vais donc aussi commencer par elles.
Je pense que beaucoup connaissent le framework Spring Data et ses dérivés pour divers entrepôts de données. J'ai travaillé avec un seul d'entre eux - Spring Data JPA. L'interface principale utilisée dans ce cadre - JpaRepository
, a subi des changements importants dans les versions 2. *.
Considérez les méthodes les plus utilisées:
En fait, il y a plus de changements, et après le passage à la version 2. * tous se transformeront en erreurs de compilation.
Lorsqu'un de nos projets a soulevé la question de la migration vers Spring Booth 2 et que les premières étapes ont été franchies (remplacement de la version dans pom.xml), l'ensemble du projet est littéralement devenu rouge: des dizaines de référentiels et des centaines d'appels à leurs méthodes ont conduit à la migration "(en utilisant de nouvelles méthodes) accrocherait un fichier sur deux. Imaginez le code le plus simple:
SomeEntity something = someRepository.findOne(someId); if (something == null) { something = someRepository.save(new SomeEntity()); } useSomething(something);
Pour que le projet se réunisse simplement, vous devez:
- appeler une autre méthode
- changer le type de la variable
something
en Optional<SomeEntity>
- remplacer la vérification de la référence vide par la
Optional::isPresent
ou Optional::orElse
/ Optional::orElseGet
- corriger les tests
Heureusement, il existe un moyen plus simple, puisque Spring Date offre aux utilisateurs des opportunités sans précédent pour adapter les référentiels à leurs besoins. Il suffit de définir (s'ils ne l'ont pas encore été) l'interface et la classe qui formeront la base de tous les référentiels de notre projet. Cela se fait comme ceci:
Tous nos référentiels hériteront de cette interface. Maintenant implémentation:
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);
Le reste est à l'image et à la ressemblance. Toutes les erreurs de compilation disparaissent, le code fonctionne comme auparavant et seules 2 classes sont modifiées, et non 200. Vous pouvez maintenant remplacer lentement l'API obsolète par une nouvelle au fur et à mesure du développement, car l'idée coloriera soigneusement tous les appels aux méthodes @Deprecated en jaune.
Dernier coup - nous informons Spring que désormais les référentiels devraient être construits au dessus de notre classe:
@Configuration @EnableJpaRepositories(repositoryBaseClass = BaseJpaRepositoryImpl.class) public class SomeConfig{ }
Dans une piscine calme, on trouve des poubelles
Spring Booth est basé sur deux concepts - démarreur et configuration automatique. Dans la vie, la conséquence de ceci est une propriété importante - la bibliothèque qui est entrée dans le chemin de classe de l'application est analysée par le SB et si une classe y est trouvée qui inclut un certain paramètre, alors elle s'activera à votre insu et sans instructions explicites. Nous montrons cela avec un exemple simple.
Beaucoup utilisent la bibliothèque gson pour transformer des objets en chaînes JSON et vice versa. Vous pouvez souvent voir ce code:
@Component public class Serializer { public <T> String serialize(T obj) { return new Gson().toJson(obj); } }
Avec des accès fréquents, ce code chargera en outre le garbage collector, ce que nous ne voulons pas. Les personnes particulièrement intelligentes créent un objet et l'utilisent:
@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); } }
... sans même réaliser que le Spring Booth peut tout faire lui-même. Par défaut, Sat est livré avec la org.springframework.boot:spring-boot-autoconfigure
, qui comprend de nombreuses classes nommées *AutoConfiguration
, par exemple, ceci:
@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(); } }
Le réglage est simple comme un rail: s'il y a une classe Gson
dans l'application et qu'il n'y a pas de bean défini manuellement de ce type, une implémentation par défaut sera créée. C'est une puissance énorme, permettant à une dépendance et à quelques annotations de générer une configuration à part entière, dont la description utilisait auparavant des feuilles XML et de nombreux bacs manuscrits.
Mais c'est une grande insidiosité du Conseil de sécurité. L'insidiosité réside dans le secret, car beaucoup de travail se déroule sous le capot selon des scripts pré-écrits. C'est bon pour une application typique, mais sortir des sentiers battus peut causer des problèmes - le convoi tire sans avertissement. De nombreux beans sont créés à notre insu, l'exemple de Gson le montre bien. Soyez prudent avec la classe et vos dépendances, car ...
Tout ce qui entre dans votre chemin de classe peut être utilisé contre vous.
Un cas de la vie. Il existe deux applications: l'une utilise LdapTemplate
, tandis que l'autre l'a utilisé une fois. Les deux applications dépendent du projet core
, où certaines classes communes sont supprimées, et les bibliothèques communes aux deux applications ont été soigneusement repliées dans pom.xml.
Après un certain temps à partir du deuxième projet, toutes les utilisations de LdapTemplate
ont LdapTemplate
car inutiles. Mais la org.springramework:spring-ldap
est restée au core
. Ensuite, le SB a frappé et org.springramework.ldap:ldap-core
s'est transformé en org.springramework.boot:spring-boot-starter-data-ldap
.
Pour cette raison, des messages intéressants sont apparus dans les journaux:
Multiple Spring Data modules found, entering strict repository configuration mode! Spring Data LDAP - Could not safely identify store assignment for repository candidate interface ....
Dépendance à l' org.springramework.boot:spring-boot-starter-data-ldap
core
led org.springramework.boot:spring-boot-starter-data-ldap
s'adapter à un projet dans lequel LDAP n'est pas utilisé du tout. Que signifie hésiter? Frappez le chemin de classe :). Plus loin avec tous les arrêts:
- Spring Boot remarque
org.springramework.boot:spring-boot-starter-data-ldap
dans classpath - chaque référentiel est maintenant vérifié pour voir s'il peut être utilisé avec Spring Data JPA ou Spring Data LDAP
- réorganisation des dépendances et suppression des
org.springramework.boot:spring-boot-starter-data-ldap
inutiles org.springramework.boot:spring-boot-starter-data-ldap
réduit le temps de démarrage des applications de 20 (!) secondes en moyenne sur un total de 40 à 50
Un lecteur attentif et critique vous demandera probablement: pourquoi avez-vous dû changer org.springramework.ldap:ldap-core
en org.springramework.boot:spring-boot-starter-data-ldap
si Spring Data LDAP n'est pas utilisé, mais qu'un seul est utilisé classe org.springframework.ldap.LdapTempate
?
Réponse: c'était une erreur. Le fait est qu'avant la version 2.1.0.M1, le réglage automatique pour LDAP ressemblait à ceci
@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; } }
Où est le LdapTemplate
? Mais il ne l'est pas :). Plus précisément, il l'est, mais se trouve ailleurs:
@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); } }
Ainsi, l'hypothèse a été faite que vous LdapTemplate
obtenir LdapTemplate
dans votre application en satisfaisant la @ConditionalOnClass({LdapContext.class, LdapRepository.class})
, ce qui est possible lorsque vous ajoutez la dépendance spring-boot-starter-data-ldap
au @ConditionalOnClass({LdapContext.class, LdapRepository.class})
.
Autre possibilité: déterminer ce haricot avec vos mains, ce que vous ne voulez pas (car pourquoi avons-nous alors besoin de SB). org.springramework.boot:spring-boot-starter-data-ldap
ont trouvé après avoir remplacé org.springramework.boot:spring-boot-starter-data-ldap
par org.springramework.ldap:ldap-core
.
Le problème est résolu ici: https://github.com/spring-projects/spring-boot/pull/13136 . Changement majeur: la LdapTemplate
bean LdapTemplate
déplacée vers LdapAutoConfiguration
. Vous pouvez maintenant utiliser LDAP sans vous lier à spring-boot-starter-data-ldap
et définir manuellement le bac LdapTemplate
.
L'agitation du capot
Si vous utilisez Hibernate, vous connaissez probablement l'antipattern open-in-view . Nous ne nous attarderons pas sur sa description, par référence, elle est décrite suffisamment en détail, et dans d'autres sources, ses effets nocifs sont décrits en détail.
Un drapeau spécial est chargé d'activer / désactiver ce mode:
spring: jpa: open-in-view: true
Dans SB version 1. * il était activé par défaut, alors que l'utilisateur n'était pas informé à ce sujet. Dans les versions 2. * il est toujours activé, mais maintenant un avertissement est écrit dans le journal:
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.
Initialement, il y avait une demande pour désactiver ce mode, l' épopée épique sur le sujet contient plusieurs dizaines (!) De commentaires détaillés, dont par Oliver Görke (développeur Spring Date), Vlad Michalce (développeur Hibernate), Phil Web et Vedran Pavic (développeurs Spring) avec des avantages et des inconvénients.
Ils ont convenu que le comportement ne changera pas, mais un avertissement sera affiché (ce qui est observé). Il existe également une astuce assez courante pour désactiver ce mode:
spring: jpa: open-in-view: false
C'est tout, écrivez sur votre rôle et les caractéristiques intéressantes du Conseil de sécurité - c'est vraiment un sujet inépuisable.