Na montanha fica Spring Boot ...

... os quatro dele estão depurando.


Inspirado em um relatório de Vladimir Plizgi ( Spring Boot 2: o que eles não escrevem nas notas de versão ), decidi falar sobre minha experiência com o Spring Booth, seus recursos e armadilhas que encontrei no caminho.


Jpa de dados do Spring


O relatório de Vladimir é dedicado à migração para o Spring Booth 2 e às dificuldades que surgem. Na primeira parte, ele descreve erros de compilação, então também começarei com eles.


Eu acho que muitos estão familiarizados com o framework Spring Data e seus derivados para vários data warehouses. Eu trabalhei com apenas um deles - Spring Data JPA. A interface principal usada nesta estrutura - JpaRepository , sofreu alterações significativas nas versões 2. *.


Considere os métodos mais usados:


  // 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 fato, há mais alterações e, depois de passar para a versão 2. * todas elas se transformarão em erros de compilação.


Quando um de nossos projetos levantou a questão da migração para o Spring Booth 2 e as primeiras etapas foram tomadas (substituindo a versão no pom.xml), todo o projeto ficou literalmente vermelho: dezenas de repositórios e centenas de chamadas para seus métodos levaram à migração "(usando novos métodos) conectaria cada segundo arquivo. Imagine o código mais simples:


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

Para que o projeto se reúna, você precisa


  • chamar outro método
  • altere o tipo da variável para something como Optional<SomeEntity>
  • substitua a verificação da referência vazia Optional::orElseGet Optional::isPresent ou Optional::orElse / Optional::orElseGet
  • corrigir testes

Felizmente, existe uma maneira mais simples, já que o Spring Date oferece aos usuários oportunidades sem precedentes para adaptar os repositórios às suas necessidades. Tudo o que precisamos é definir (se ainda não foram definidos) a interface e a classe que formarão a base de todos os repositórios do nosso projeto. Isso é feito assim:


 //    @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 os nossos repositórios serão herdados dessa interface. Agora implementação:


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

O resto está na imagem e semelhança. Todos os erros de compilação desaparecem, o código funciona como antes e apenas duas classes são alteradas, e não 200. Agora você pode substituir lentamente a API obsoleta por uma nova à medida que o desenvolvimento avança, pois o Idea colorirá cuidadosamente todas as chamadas dos métodos @ Deprecated em amarelo.


Último golpe - informamos ao Spring que a partir de agora os repositórios devem ser construídos sobre a nossa classe:


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

Em uma piscina calma, caixas são encontradas


O Spring Booth é baseado em dois conceitos - configuração inicial e automática. Na vida, a conseqüência disso é uma propriedade importante - a biblioteca que entrou no caminho de classe do aplicativo é examinada pelo SB e, se for encontrada uma classe que inclua uma determinada configuração, ela será ativada sem o seu conhecimento e instruções explícitas. Mostramos isso com um exemplo simples.


Muitos usam a biblioteca gson para transformar objetos em strings JSON e vice-versa. Muitas vezes você pode ver este código:


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

Com acessos frequentes, esse código carrega adicionalmente o coletor de lixo, o que não queremos. Pessoas particularmente inteligentes criam um objeto e o usam:


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

... sem nem perceber que o Spring Booth pode fazer tudo sozinho. Por padrão, o SB vem com a org.springframework.boot:spring-boot-autoconfigure , que inclui muitas classes denominadas *AutoConfiguration , por exemplo, isto:


 @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(); } } 

A configuração é simples como um trilho: se o aplicativo tiver uma classe Gson e não houver um bean definido manualmente desse tipo, uma implementação padrão será criada. Esse é um poder tremendo, permitindo que uma dependência e algumas anotações aumentem uma configuração completa, cuja descrição costumava usar folhas XML e várias caixas manuscritas.


Mas isso é uma grande insidiosidade do Conselho de Segurança. A insidiosidade está em segredo, porque muito trabalho acontece oculto de acordo com os scripts pré-escritos. Isso é bom para uma aplicação típica, mas sair da trilha batida pode causar problemas - o comboio dispara sem aviso prévio. Muitos beans são criados sem nosso conhecimento, o exemplo com o Gson mostra isso bem. Tenha cuidado com a classe e suas dependências, pois ...


Qualquer coisa que entrar no seu caminho de classe pode ser usada contra você.


Um caso da vida. Existem dois aplicativos: um usa LdapTemplate , enquanto o outro o usa uma vez. Ambos os aplicativos dependem do projeto core , onde algumas classes comuns são retiradas, e as bibliotecas comuns para ambos os aplicativos foram cuidadosamente dobradas em pom.xml.


Depois de algum tempo, a partir do segundo projeto, todos os usos do LdapTemplate foram LdapTemplate como desnecessários. Mas a org.springramework:spring-ldap permaneceu no core . Então o SB atacou e org.springramework.ldap:ldap-core transformou em org.springramework.boot:spring-boot-starter-data-ldap .


Por esse motivo, mensagens interessantes apareceram nos logs:


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

Dependência do org.springramework.boot:spring-boot-starter-data-ldap led org.springramework.boot:spring-boot-starter-data-ldap caber em um projeto no qual o LDAP não é usado. O que significa hesitar? Bata no caminho da classe :). Além disso, com todas as paradas:


  • O Spring Boot percebe org.springramework.boot:spring-boot-starter-data-ldap no caminho de classe
  • agora cada repositório é verificado para ver se é adequado para uso com o Spring Data JPA ou Spring Data LDAP
  • reorganização de dependências e org.springramework.boot:spring-boot-starter-data-ldap desnecessário de org.springramework.boot:spring-boot-starter-data-ldap reduz o tempo de inicialização do aplicativo em uma média de 20 (!) segundos em um total de 40-50

Um leitor atento e crítico provavelmente perguntará: por que você precisou alterar org.springramework.ldap:ldap-core para org.springramework.boot:spring-boot-starter-data-ldap se o Spring Data LDAP não for usado, mas apenas um for usado classe org.springframework.ldap.LdapTempate ?


Resposta: foi um erro. O fato é que, antes da versão 2.1.0.M1, o autoajuste para LDAP era algo como isto


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

Onde está o LdapTemplate ? Mas ele não é :). Mais precisamente, é, mas está em outro lugar:


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

Assim, assumiu-se que você LdapTemplate obter o LdapTemplate no seu aplicativo satisfazendo a condição @ConditionalOnClass({LdapContext.class, LdapRepository.class}) , possível quando a dependência de spring-boot-starter-data-ldap é adicionada ao caminho de classe.


Outra possibilidade: determinar esse feijão com as mãos, o que você não deseja (porque então precisamos do SB). org.springramework.boot:spring-boot-starter-data-ldap criaram isso depois de substituir org.springramework.boot:spring-boot-starter-data-ldap por org.springramework.ldap:ldap-core .


O problema foi resolvido aqui: https://github.com/spring-projects/spring-boot/pull/13136 . Alteração principal: a LdapTemplate bean LdapTemplate movida para LdapAutoConfiguration . Agora você pode usar o LDAP sem vincular ao spring-boot-starter-data-ldap e definir manualmente a bandeja LdapTemplate .


Barulho de capuz


Se você usa o Hibernate, provavelmente conhece o antipadrão aberto na tela . Não vamos nos debruçar sobre sua descrição; por referência, ela é descrita em detalhes suficientes e, em outras fontes, seus efeitos nocivos são descritos em grandes detalhes.


Um sinalizador especial é responsável por ativar / desativar este modo:


 spring: jpa: open-in-view: true 

No SB versão 1. *, ele era ativado por padrão, enquanto o usuário não era informado sobre isso. Nas versões 2. * ele ainda está ativado, mas agora um aviso é gravado no log:


 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, houve uma solicitação para desativar esse modo, o épico srach no tópico contém várias dezenas (!) De comentários detalhados, incluindo de Oliver Görke (desenvolvedor do Spring Date), Vlad Michalce (desenvolvedor do Hibernate), Phil Web e Vedran Pavic (desenvolvedores do Spring) com prós e contras.


Eles concordaram que o comportamento não mudará, mas um aviso será exibido (o que é observado). Há também uma dica bastante comum para desativar esse modo:


 spring: jpa: open-in-view: false 

Isso é tudo, escreva sobre o seu rake e os recursos interessantes do Conselho de Segurança - este é realmente um tópico inesgotável.

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


All Articles