Quando um projeto em grande escala passa por uma atualização em grande escala, tudo nunca é simples: inevitavelmente, existem nuances não óbvias (em outras palavras, um rake). E então, não importa quão boa seja a documentação, apenas a experiência - sua ou de outra pessoa - ajudará em algo.
Na conferência Joker 2018, falei sobre os problemas que eu mesmo encontrei ao mudar para o Spring Boot 2 e como eles são resolvidos. E agora especialmente para Habr - a versão em texto deste relatório. Por conveniência, o post tem uma gravação de vídeo e um sumário: você não pode ler tudo, mas vai diretamente para o problema que o preocupa.
Sumário
Bom dia Quero falar sobre alguns recursos (vamos chamá-los de rakes) que você pode encontrar ao atualizar o framework Spring Boot para a segunda versão e sua operação subseqüente.
Meu nome é Vladimir Plizgá (
GitHub ), trabalho na CFT, um dos maiores e mais antigos desenvolvedores de software da Rússia. Nos últimos anos, venho desenvolvendo backend por lá, responsável pelo desenvolvimento técnico do banco on-line de cartões pré-pagos. Foi nesse projeto que me tornei o iniciador e o executor da transição de uma arquitetura monolítica para a arquitetura de microsserviços (que ainda está em andamento). Bem, como a maior parte do conhecimento que eu decidi compartilhar com você foi acumulada no exemplo desse projeto em particular, vou falar um pouco mais sobre isso.
Brevemente sobre o produto experimental
Este é um banco da Internet que, sozinho, atende a mais de duas dúzias de empresas parceiras em toda a Rússia: fornece aos clientes finais a capacidade de gerenciar seu dinheiro por meio de serviços bancários remotos (aplicativos móveis, sites). Um dos parceiros é a Beeline e seu cartão de pagamento. Acabou sendo um bom banco na Internet, a julgar pela classificação do
Markswebb Mobile Banking Rank , onde nosso produto assumiu uma boa posição para iniciantes.
As "tripas" ainda estão em transição, por isso temos um monólito, o chamado núcleo, em torno do qual são criados 23 microsserviços. No interior, os microsserviços Spring Cloud Netflix, Spring Integration e muito mais. E no Spring Boot 2, essa coisa toda está voando desde o mês de julho. E justamente neste lugar, moramos com mais detalhes. Ao traduzir este projeto para a segunda versão, deparei-me com alguns recursos sobre os quais quero falar.
Esboço do relatório

Em muitas áreas em que os recursos do Spring Boot 2 apareceram, tentaremos analisar tudo. Para fazer isso rapidamente, precisamos de um detetive ou investigador experiente - alguém que descubra tudo isso como se fosse para nós. Como Holmes e Watson já
fizeram uma apresentação no Joker, seremos assistidos por outro especialista, o tenente Colombo. Vá em frente!
Bota de mola / 2
Primeiro, algumas palavras sobre o Spring Boot em geral e a segunda versão em particular. Em primeiro lugar, esta versão foi lançada, para dizer o mínimo, não ontem: em 1 de março de 2018, já estava em Disponibilidade Geral. Um dos principais objetivos que os desenvolvedores buscavam era dar suporte total ao Java 8 no nível de origem. Ou seja, não pode ser compilado em uma versão menor, embora o tempo de execução seja compatível. O Spring Framework da quinta versão, lançada um pouco antes do Spring Boot 2., foi tomado como base, e essa não é a única dependência. Ele também tem um conceito como
BOM (Bill Of Materials) - este é um XML enorme, que lista todas as dependências (transitivas para nós) de todos os tipos de bibliotecas de terceiros, estruturas adicionais, ferramentas e muito mais.
Por conseguinte, nem todos os efeitos especiais que a segunda Bota de Primavera traz provêm de si ou do ecossistema da Primavera. Dois documentos excelentes foram escritos para todo esse farm:
notas de versão e o
guia de migração . Os documentos são legais, o Spring, nesse sentido, geralmente é bem feito. Mas, por razões óbvias, está longe de ser possível cobrir tudo: existem alguns detalhes, desvios etc. que não podem ser ou não devem ser incluídos lá. Vamos falar sobre esses recursos.
Tempo de compilação. Exemplos de mudança de API
Vamos começar com o rake mais ou menos simples e óbvio: estes são os que surgem em tempo de compilação. Ou seja, algo que nem mesmo permitirá que você compile o projeto se você simplesmente alterar o número 1 no script de inicialização para 2.
A principal fonte de mudanças, que se tornou a base para essas edições no Spring Boot, é, obviamente, a transição do Spring para o Java 8. Além disso, a pilha da Web do Spring 5 e do Spring Boot 2 se dividiu, relativamente falando, em duas. Agora é servlet, tradicional para nós, e reativo. Além disso, era necessário levar em consideração uma série de deficiências das versões anteriores. As bibliotecas de terceiros foram iniciadas (de fora do Spring). Se você olhar para as Notas da versão, não verá nenhuma armadilha na hora e, francamente, quando li as Notas da versão pela primeira vez, pareceu-me que estava tudo bem. E para mim, parecia algo assim:
Mas, como você provavelmente adivinha, nem tudo é tão bom.
Em que a compilação será interrompida (exemplo 1):- Por que : a classe
WebMvcConfigurerAdapter
não existe mais; - Por que : suportar chips Java 8 (métodos padrão em interfaces);
- O que fazer : use a interface
WebMvcConfigurer
.
Um projeto pode não ser compilado pelo menos devido ao fato de que algumas classes simplesmente não existem mais. Porque Sim, porque no Java 8 eles não são necessários. Se esses eram adaptadores com implementação primitiva de métodos, não há nada especial para explicar, os métodos padrão resolvem tudo isso perfeitamente. Aqui está um exemplo dessa classe, é claro que é suficiente usar a própria interface e não são necessários adaptadores.
Em que compilação será interrompida (exemplo 2):- Por que : o método
PropertySourceLoader#load
começou a retornar uma lista de fontes em vez de uma; - Motivo : oferecer suporte a recursos de vários documentos, por exemplo, YAML;
- O que fazer : agrupar a resposta em
singletonList()
(quando substituído).
Um exemplo de uma área completamente diferente. Alguns métodos até mudaram de assinatura. Se você já usou o método load PropertySourceLoader, agora ele retorna uma coleção. Consequentemente, isso permitiu o suporte de recursos para vários documentos. Por exemplo, no YAML, através de três hífens, você pode especificar vários documentos em um arquivo. Se agora você precisa trabalhar com ele a partir de Java, lembre-se de que isso deve ser feito por meio da coleção.
Em que compilação será interrompida (exemplo 3):- Por que : algumas classes do pacote
org.springframework.boot.autoconfigure.web
dos pacotes .servlet
- .servlet
e .reactive
; - Motivo : apoiar o jet stack em pé de igualdade com o tradicional;
- O que fazer : atualizar importações.
Ainda mais alterações foram introduzidas, assim, empilhamento. Por exemplo, o que costumava estar no mesmo pacote da Web agora foi dividido em dois pacotes com várias classes. Estes são
.servlet
e
.reactive
. Por que isso é feito? Porque a pilha de jatos não era uma muleta enorme em cima do servlet. Era necessário fazer isso para que eles pudessem manter seu próprio ciclo de vida, desenvolver-se em suas próprias direções e não interferir entre si. O que fazer sobre isso? O suficiente para alterar as importações: a maioria dessas classes permaneceu compatível no nível da API. A maioria, mas não todas.
Em que compilação será interrompida (exemplo 4):- Por que : a assinatura dos métodos da classe
ErrorAttributes
: em vez de RequestAttributes
, WebRequest(servlet)
e ServerRequest(reactive)
começaram a ser usados; - Motivo : apoiar o jet stack em pé de igualdade com o tradicional;
- O que fazer : substitua os nomes das classes nas assinaturas.
Por exemplo, na classe ErrorAttributes, agora, em vez de RequestAttributes, duas outras classes começaram a ser usadas nos métodos: são WebRequest e ServerRequest. O motivo é o mesmo. E o que fazer sobre isso? Se você estiver alternando da primeira para a segunda Spring Boot, precisará alterar RequestAttributes para WebRequest. Bem, se você já está no segundo, use ServerRequest. Obviamente, não é?
Como ser
Existem alguns exemplos, mas não vamos resolver todos eles. O que fazer sobre isso? Antes de tudo, vale a pena dar uma olhada no Guia de Migração do Spring Boot 2.0 para observar as alterações relacionadas a você a tempo. Por exemplo, menciona renomear classes completamente não óbvias. Ainda assim, se algo se separou e quebrou, vale a pena considerar que o conceito de "web" é dividido em 2: "servlet" e "reativo". Com orientação em todas as classes e pacotes, isso pode ajudar. Além disso, deve-se ter em mente que não apenas as próprias classes e pacotes foram renomeados, mas também dependências e artefatos inteiros. Como isso, por exemplo, aconteceu com o Spring Cloud.
Tipo de conteúdo. Determinando o tipo de resposta HTTP
Chega de coisas simples desde o tempo de compilação, tudo é claro e simples lá. Vamos falar sobre o que pode acontecer em tempo de execução e, portanto, pode ser gravado, mesmo que o Spring Boot 2 esteja trabalhando para você há muito tempo. Vamos falar sobre a definição do tipo de conteúdo.

Não é segredo que o Spring pode escrever aplicativos da Web, APIs de página e REST, e pode renderizar conteúdo com uma grande variedade de tipos, seja XML, JSON ou qualquer outra coisa. E um dos encantos de que o Spring gosta tanto é que você não precisa se preocupar com a definição do tipo dado no seu código. Você pode esperar por mágica. Essa mágica funciona, relativamente falando, de três maneiras diferentes: depende do cabeçalho Accept que veio do cliente ou da extensão do arquivo solicitado ou de um parâmetro especial na URL, que, é claro, também pode ser direcionada.
Considere um exemplo simples (
código fonte completo ). A seguir, usarei a notação de Gradle, mas mesmo se você é um fã do Maven, não será difícil entender o que está escrito aqui: criamos um pequeno aplicativo no primeiro Spring Boot e usamos apenas uma Web de iniciação.
Exemplo (v1.x):
dependencies { ext { springBootVersion = '1.5.14.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") }
Como código executável, temos uma única classe na qual o método do controlador é declarado imediatamente.
@GetMapping(value = "/download/{fileName: .+}", produces = {TEXT_HTML_VALUE, APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE}) public ResponseEntity<Resource> download(@PathVariable String fileName) {
É necessário um determinado nome de arquivo como entrada, que supostamente será formado e fornecido. Ele realmente forma seu conteúdo em um dos três tipos indicados (determinando isso pelo nome do arquivo), mas não especifica o tipo de conteúdo de forma alguma - temos o Spring, ele fará tudo sozinho.

Em geral, você pode até tentar fazê-lo. De fato, se solicitarmos o mesmo documento com extensões diferentes, ele será retornado com o tipo de conteúdo correto, dependendo do que retornarmos: se você quiser - json, se você quiser - txt, se você quiser - html. Funciona como um conto de fadas.
Atualizando para a v2.x
dependencies { ext { springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") }
É hora de atualizar para o segundo Spring Boot. Apenas mudamos o número 1 para 2.
Caminho do Spring MVC que corresponde à alteração de comportamento padrãoMas somos engenheiros, vamos dar uma olhada no Guia de Migração e, de repente, algo é dito sobre isso. Mas ele menciona algum tipo de "correspondência de caminho de sufixo". É sobre como mapear corretamente métodos em Java com uma URL. Mas este não é o nosso caso, embora um pouco como.

Portanto, marcamos, checamos e batemos! - de repente não funciona. Por algum motivo, apenas text / html começa a ser fornecido em todos os lugares e, se você o digitar, não é apenas text / html, mas apenas o primeiro dos tipos especificados no atributo produz na anotação @GetMapping. Porque Parece, para dizer o mínimo, incompreensível.

E aqui, nenhuma nota de versão ajudará, você deve ler a fonte.
ContentNegotiationManagerFactoryBean
public ContentNegotiationManagerFactoryBean build() { List<ContentNegotiationStrategy> strategies = new ArrayList<>(); if (this.strategies != null) { strategies.addAll(this.strategies); } else { if (this.favorPathExtension) { PathExtensionContentNegotiationStrategy strategy;
Lá você pode encontrar um clássico com um nome abreviado lacônico muito compreensível, que menciona uma determinada bandeira chamada "considerar extensão no caminho" (favorPathExtension). O valor desse sinalizador "true" corresponde à aplicação de uma determinada estratégia com outro nome conciso curto compreensível, do qual fica claro que ele é apenas responsável por determinar o tipo de conteúdo por extensão de arquivo. Como você pode ver, se a bandeira for falsa, a estratégia não será aplicada.

Sim, provavelmente, muitos notaram que na primavera, aparentemente, há algum tipo de orientação, de modo que o nome deve estar bem, com pelo menos vinte caracteres.

Se você se aprofundar um pouco mais, poderá cavar esse fragmento. No próprio framework Spring, e não na quinta versão, como seria de esperar, mas desde tempos imemoriais, esse sinalizador por padrão foi definido como "true". Enquanto estava no Spring Boot e na segunda versão, foi bloqueado por outro, que agora está disponível para controle das configurações. Ou seja, agora podemos orientá-los a partir da gestão ambiental, e isso é apenas na segunda versão. Você sente? Lá ele já assumiu o significado de "falso". Ou seja, eles queriam, mais ou menos, fazer o melhor, colocar esse sinalizador nas configurações (e isso é ótimo), mas o valor padrão foi alterado para outro (isso não é muito).
Os desenvolvedores da estrutura também são pessoas, eles também tendem a cometer erros. O que fazer sobre isso? É claro que você precisa mudar o parâmetro no seu projeto, e tudo ficará bem.
A única coisa que vale a pena fazer, para clarear sua consciência, é procurar na documentação do Spring Boot apenas qualquer menção a essa bandeira. E lá ele é realmente
mencionado , mas apenas em algum contexto estranho:
Se você entende as advertências e ainda deseja que seu aplicativo use a correspondência de padrões de sufixo, a seguinte configuração é necessária:
spring.mvc.contentnegotiation.favor-path-extension = true
...
Está escrito, eles dizem, se você entender todos os truques e ainda quiser usar a correspondência de caminho de sufixo, marque esta caixa. Sinta a discrepância? Parece que estamos falando sobre a definição de tipo de conteúdo no contexto desse sinalizador, mas aqui estamos falando sobre a correspondência de métodos e URLs Java. Parece de alguma forma incompreensível.
Temos que cavar mais. Existe uma
solicitação de recebimento no GitHub:

Dentro da estrutura dessa solicitação de recebimento, essas alterações foram feitas - alternando o valor padrão - e um dos autores da estrutura diz que esse problema tem dois aspectos: um é apenas o caminho correspondente e o segundo é a definição do tipo de conteúdo . Ou seja, em outras palavras, a bandeira se aplica a ambos, e eles estão inextricavelmente vinculados.
Você poderia, é claro, encontrá-lo imediatamente no GitHub se soubesse onde procurar.
Correspondência de sufixoAlém disso, a documentação do Spring Framework também afirma que o uso de extensões de arquivo era necessário anteriormente, mas agora não é mais considerado uma necessidade. Além disso, provou ser problemático em vários casos.
Resumir
Alterar o valor do sinalizador padrão não é um bug, mas um recurso. Ele está inextricavelmente vinculado à definição de correspondência de caminho e foi projetado para fazer
três coisas :
- reduzir os riscos de segurança (quais, vou esclarecer);
- alinhar o comportamento do WebFlux e WebMvc, eles diferiam nesse aspecto;
- alinhe a instrução na documentação com o código da estrutura.
Como ser
Primeiro, sempre que possível, você não deve confiar na definição do tipo de conteúdo por extensão. O exemplo que mostrei é um contra-exemplo, não é necessário fazer isso! Além disso, não é necessário confiar no fato de que solicitações no formato "GET something.json", por exemplo, simplesmente "GET something". Esse foi o caso no Spring Framework 4 e no Spring Boot 1. Isso não funciona mais. Se você precisar mapear para um arquivo com a extensão, faça isso explicitamente. Em vez disso, é melhor confiar no cabeçalho Accept ou no parâmetro URL, cujo nome você pode orientar. Bem, se você não conseguir fazer isso, digamos que você tenha alguns clientes móveis antigos que pararam de atualizar no século passado, será necessário retornar esse sinalizador, defini-lo como "true" e tudo funcionará como antes.
Além disso, para uma compreensão geral, você pode ler o capítulo "Correspondência de sufixo" na documentação do Spring Framework, que é considerado pelos desenvolvedores como uma espécie de coleção de práticas recomendadas nesta área e se familiarizar com o que é o
ataque Download de arquivo refletido , implementado usando manipulações com extensão de arquivo
Agendamento. Tarefas agendadas ou periódicas
Vamos mudar um pouco o escopo e falar sobre a conclusão de tarefas em um cronograma ou periodicamente.
Um exemplo de uma tarefa. Mensagem de log a cada 3 segundos
O que está sendo dito, eu acho, é compreensível. Temos algumas necessidades de negócios, para fazer algo com algum tipo de repetição, por isso iremos imediatamente para o exemplo. Suponha que tenhamos uma tarefa mega complexa: gerar uma sujeira no log a cada 3 segundos.

Isso pode ser feito, obviamente, de várias maneiras, para eles, de qualquer forma, já existe algo na primavera. E encontre - de várias maneiras.
Opção 1: procure um exemplo em seu projeto
@Service public class ReallyBusinessService {
Podemos olhar em nosso próprio projeto e provavelmente encontraremos algo assim. Uma anotação ficará no método público e ficará claro que, assim que você desligá-lo, tudo funciona como em um conto de fadas.
Opção 2: procure a anotação desejada

Você pode procurar a anotação diretamente pelo nome, e também ficará claro na documentação que você a pendurou - e tudo funciona.
Opção 3: Google
Se você não acredita em si mesmo, pode pesquisar no Google e, a partir do que
encontrou, também ficará claro que tudo começará com uma anotação.
@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!"); } }
Quem vê o problema nisso? Somos engenheiros, afinal, vamos verificar como isso funciona na realidade.
Mostre-me o código!
Considere uma tarefa específica (a tarefa em si e o código estão
no meu repositório ).
Quem não quiser ler, assista a este fragmento do vídeo com uma demonstração (até o 22º minuto):
Como dependência, usaremos o primeiro Spring Boot com duas entradas. Uma é para a Web, é como se estivéssemos desenvolvendo um servidor da Web, e a segunda é o atuador de acionador de mola, para que tenhamos recursos prontos para produção, para que pareçamos algo real.
dependencies { ext { springBootVersion = '1.5.14.RELEASE'
E nosso código executável será ainda mais simples.
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 …”); } }
Em geral, quase nada de notável, exceto o único método no qual penduramos a anotação. Nós o copiamos em algum lugar e esperamos que funcione.
Vamos verificar, somos engenheiros. Começamos. Assumimos que a cada três segundos essa mensagem será registrada. Tudo deve funcionar imediatamente, garantimos que tudo seja lançado na primeira Spring Boot e esperamos a saída da linha desejada. Passagem de três segundos - uma linha é emitida, seis passagens - uma linha é exibida. Otimistas venceram, tudo funciona.

Somente chega a hora de atualizar para o segundo Spring Boot. Não vamos nos preocupar, basta mudar de um para o outro:
dependencies { ext {
Em teoria, o Guia de Migração não nos alertou sobre nada, e esperamos que tudo funcione sem desvios. Do ponto de vista do código executável, não temos nenhum dos outros rakes mencionados anteriormente (incompatibilidade no nível da API ou outra coisa), pois o aplicativo é o mais simples possível.
Começamos. Primeiro de tudo, estamos convencidos de que estamos trabalhando no segundo Spring Boot, caso contrário, aparentemente não há desvios.

No entanto, 3 segundos se passam, 6, 9, mas ainda não há Herman - nenhuma conclusão, nada funciona.
Como costuma acontecer, a expectativa está em desacordo com a realidade. Eles costumam escrever para nós na documentação que, de fato, tudo funciona imediatamente no Spring Boot, que podemos começar como estão com o mínimo de problemas e que nenhuma configuração é necessária. Mas assim que se torna realidade, muitas vezes acontece que ainda se deve ler a documentação. Em particular, se você se aprofundar, poderá encontrar aqui estas linhas:
7.3.1 Ativar anotações de agendamento
Para habilitar o suporte para anotações @Scheduled e Async , você pode adicionar @EnableScheduling e @EnableAsync a uma das suas classes @Configuration.
Para que a anotação agendada funcione, você precisa pendurar outra anotação na classe com outra anotação. Bem, como sempre na primavera. Mas por que funcionou antes? Nós não fizemos nada disso. Obviamente, essa anotação em algum lugar foi suspensa no início da primeira inicialização do Spring, mas agora, por algum motivo, não está na segunda.

Começamos a vasculhar os códigos-fonte da primeira Spring Boot. Descobrimos que há alguma classe na qual supostamente depende. Observamos mais de perto, é chamado "MetricExportAutoConfiguration" e, aparentemente, é responsável por fornecer essas métricas de desempenho para fora, para alguns agregadores centralizados, e realmente possui essa anotação.

Além disso, ele funciona de tal maneira que inclui seu comportamento em todo o aplicativo de uma só vez, e não precisa ser pendurado em classes separadas. Era essa classe que fornecia esse comportamento e, por algum motivo, não o fazia. Porque

Mesmo assim, o GitHub nos leva a uma escavação arqueológica: como parte da transição para a segunda versão do Spring Boot, essa classe foi cortada junto com a anotação. Porque Sim, porque o mecanismo de entrega de métricas também mudou: eles não usam mais seu próprio script, mas mudaram para o micrômetro - uma solução realmente significativa. Isso é apenas algo supérfluo deixado com ele. Talvez isso esteja correto.
Quem não quiser ler, veja uma breve demonstração por 30 segundos:
Daqui resulta que, se agora pegarmos e travarmos manualmente a anotação ausente em nossa classe original, em teoria, o comportamento deverá se tornar correto.
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 …”); } }
Você acha que vai funcionar? Vamos verificar. Começamos.

Pode-se observar que após 3 segundos, após 6 e 9, a mensagem esperada por nós ainda é exibida no log.
Como ser
O que fazer com isso neste caso particular e mais geral? Não importa o quão moralista isso possa parecer, em primeiro lugar, vale a pena ler não apenas fragmentos de documentação copiados, mas também um pouco mais amplo, apenas para cobrir esses aspectos.
Em segundo lugar, lembre-se de que, no Spring Boot, embora muitos recursos estejam prontos para uso (programação, assíncrona, cache, ...), eles nem sempre são incluídos, devem ser ativados explicitamente.
Em terceiro lugar, não se preocupa em ser seguro: adicione anotações
Enable * (e toda a família) ao seu código, sem esperar por uma estrutura. Mas então surge a pergunta: o que acontecerá se, por acaso, eu e meus colegas adicionarmos algumas anotações, como eles se comportarão? , . : . , .
, @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); } }
Começamos.

. , , - 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: 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 exemplo:
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». …

, , ? .
, , 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) {
, , . JButton. anyString. , : , — . , . - 10- , . Mockito 1 , :

Mockito 2 , , , anyString :

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

Mockito 2 , :

, Mockito 1 , , . new SocketTimeoutException new, constructor, Mockito 1 .
O que fazer sobre isso? , , 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 {
Util, .
lib:
public abstract class Util { public static String getAppVersion(Class<?> appClass) { return appClass.getPackage().getImplementationVersion(); } }
Util getAppVersion , , ImplementationVersion . .

IDE, , , . gradle build IDE , . Util. , , , , .
:
:
- ;
- , jar ( ImplementationVersion), .
? : .

Spring Boot- , , lib . .
2: SB Gradle Plugin Spring Boot-
bootJar { enabled = false }
, - , , , , , bootJar . , jar , .
, , , 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. — .