Lorsqu'un projet à grande échelle subit une mise à jour à grande échelle, tout n'est jamais simple: il y a inévitablement des nuances non évidentes (en d'autres termes, un râteau). Et puis, quelle que soit la qualité de la documentation, seule l'expérience - la vôtre ou celle de quelqu'un d'autre - vous aidera.
Lors de la conférence Joker 2018, j'ai parlé des problèmes que j'ai moi-même rencontrés lors du passage à Spring Boot 2 et comment ils sont résolus. Et maintenant spécialement pour Habr - la version texte de ce rapport. Pour plus de commodité, la publication a à la fois un enregistrement vidéo et une table des matières: vous ne pouvez pas lire le tout, mais passez directement au problème qui vous inquiète.
Table des matières
Bonjour! Je veux vous parler de certaines fonctionnalités (appelons-les râteaux) que vous pouvez rencontrer lors de la mise à jour du framework Spring Boot vers la deuxième version et de son fonctionnement ultérieur.
Je m'appelle Vladimir Plizgá (
GitHub ), je travaille pour CFT, l'un des plus grands et des plus anciens développeurs de logiciels en Russie. Depuis quelques années, j'y fais du développement backend, en charge du développement technique de la banque en ligne de cartes prépayées. C'est sur ce projet que je suis devenu l'initiateur et l'exécuteur de la transition d'une architecture monolithique à une architecture de microservices (qui se poursuit toujours). Eh bien, puisque la plupart des connaissances que j'ai décidé de partager avec vous ont été accumulées sur l'exemple de ce projet particulier, je vais vous en dire un peu plus à ce sujet.
En bref sur le produit expérimental
Il s'agit d'une banque Internet qui dessert à elle seule plus de deux douzaines d'entreprises partenaires à travers la Russie: elle offre aux clients finaux la possibilité de gérer leur argent via des services bancaires à distance (applications mobiles, sites). L'un des partenaires est Beeline et sa carte de paiement. Il s'est avéré être une bonne banque sur Internet, à en juger par la note de
Markswebb Mobile Banking Rank , où notre produit a pris une bonne position pour les débutants.
Les «tripes» sont encore en transition, nous avons donc un monolithe, le soi-disant noyau, autour duquel 23 microservices sont construits. À l'intérieur, les microservices Spring Cloud Netflix, l'intégration de Spring et plus encore. Et sur Spring Boot 2, tout cela vole depuis environ le mois de juillet. Et juste à cet endroit, nous nous attardons plus en détail. En traduisant ce projet dans la deuxième version, je suis tombé sur quelques fonctionnalités dont je veux vous parler.
Plan du rapport

Il y a beaucoup de domaines où les fonctionnalités de Spring Boot 2 sont apparues, nous allons essayer de les passer en revue. Pour ce faire rapidement, nous avons besoin d'un détective ou d'un enquêteur expérimenté - quelqu'un qui déterrera tout cela comme pour nous. Holmes et Watson ayant déjà
fait une présentation au Joker, nous serons assistés d'un autre spécialiste, le lieutenant Colombo. Allez-y!
Chaussure de printemps / 2
Tout d'abord, quelques mots sur Spring Boot en général et la deuxième version en particulier. Tout d'abord, cette version a été publiée, pour le moins, pas hier: le 1er mars 2018, elle était déjà en disponibilité générale. L'un des principaux objectifs poursuivis par les développeurs était de prendre pleinement en charge Java 8 au niveau source. Autrement dit, il ne peut pas être compilé sur une version plus petite, bien que le runtime soit compatible. Le Spring Framework de la cinquième version, qui a été publié un peu plus tôt que Spring Boot 2, a été pris comme base, et ce n'est pas la seule dépendance. Il a également un concept tel que la
nomenclature (Bill Of Materials) - c'est un énorme XML, qui répertorie toutes les dépendances (transitives pour nous) sur toutes sortes de bibliothèques tierces, des cadres supplémentaires, des outils, etc.
Par conséquent, tous les effets spéciaux apportés par le deuxième Spring Boot ne viennent pas d'eux-mêmes ou de l'écosystème Spring. Deux excellents documents ont été rédigés pour toute cette batterie: les
notes de version et le
guide de migration . Les documents sont cool, le printemps en ce sens est généralement bien fait. Mais, pour des raisons évidentes, il est loin d'être possible de tout couvrir: il y a des détails, des écarts, etc. qui ne peuvent pas ou ne doivent pas y être inclus. Nous parlerons de ces fonctionnalités.
Compiler le temps. Exemples de changement d'API
Commençons par le râteau plus ou moins simple et évident: ce sont ceux qui surviennent au moment de la compilation. Autrement dit, quelque chose qui ne vous permettra même pas de compiler le projet si vous changez simplement le numéro 1 dans le script de démarrage à 2.
La principale source de changements, qui est devenue la base de ces modifications dans Spring Boot, est, bien sûr, la transition de Spring vers Java 8. De plus, la pile Web de Spring 5 et Spring Boot 2 s'est divisée, relativement parlant, en deux. Maintenant, il est servlet, traditionnel pour nous, et réactif. En outre, il était nécessaire de prendre en compte un certain nombre de lacunes des versions précédentes. Des bibliothèques tierces ont démarré (en dehors de Spring). Si vous regardez les notes de version, vous ne verrez aucun écueil à la volée et, franchement, lorsque j'ai lu les notes de version pour la première fois, il m'a semblé que tout allait bien. Et pour moi, cela ressemblait à ceci:
Mais, comme vous le devinez probablement, tout n'est pas si bon.
Sur quoi la compilation va s'arrêter (exemple 1):- Pourquoi : la classe
WebMvcConfigurerAdapter
n'est plus; - Pourquoi : prendre en charge les puces Java 8 (méthodes par défaut dans les interfaces);
- Que faire : utilisez l'interface
WebMvcConfigurer
.
Un projet peut ne pas être compilé au moins du fait que certaines classes n'existent tout simplement plus. Pourquoi? Oui, car en Java 8, ils ne sont pas nécessaires. S'il s'agissait d'adaptateurs avec une implémentation primitive de méthodes, il n'y a rien de spécial à expliquer, les méthodes par défaut résolvent tout cela parfaitement. Voici un exemple de cette classe, il est clair qu'il suffit d'utiliser l'interface elle-même, et aucun adaptateur n'est nécessaire.
Sur quelle compilation va s'arrêter (exemple 2):- Pourquoi : la méthode
PropertySourceLoader#load
commencé à renvoyer une liste de sources au lieu d'une; - Pourquoi : pour prendre en charge des ressources multi-documents, par exemple, YAML;
- Que faire : encapsuler la réponse dans
singletonList()
(en cas de substitution).
Un exemple d'un domaine complètement différent. Certaines méthodes ont même changé les signatures. Si vous avez déjà utilisé la méthode load PropertySourceLoader, elle retourne maintenant une collection. En conséquence, cela a permis le support de ressources multi-documents. Par exemple, dans YAML, à travers trois tirets, vous pouvez spécifier un tas de documents dans un fichier. Si maintenant vous devez travailler avec Java, gardez à l'esprit que cela doit être fait via la collection.
Sur quelle compilation va s'arrêter (exemple 3):- Pourquoi : certaines classes du package
org.springframework.boot.autoconfigure.web
ont org.springframework.boot.autoconfigure.web
des packages .servlet
- .servlet
et .reactive
; - Pourquoi : soutenir le jet stack au même niveau que le traditionnel;
- Que faire : mettre à jour les importations.
Encore plus de changements ont été introduits, empilant ainsi. Par exemple, ce qui était dans le même package Web est maintenant divisé en deux packages avec un tas de classes. Ce sont
.servlet
et
.reactive
. Pourquoi est-ce fait? Parce que la pile à jet n'était pas censée être une énorme béquille au-dessus du servlet. Il était nécessaire de le faire pour qu'ils puissent maintenir leur propre cycle de vie, se développer dans leurs propres directions et ne pas interférer les uns avec les autres. Que faire à ce sujet? De quoi changer les importations: la plupart de ces classes sont restées compatibles au niveau API. La plupart, mais pas tous.
Sur quelle compilation va s'arrêter (exemple 4):- Pourquoi : la signature des méthodes de la classe
ErrorAttributes
a ErrorAttributes
: au lieu de RequestAttributes
, WebRequest(servlet)
et ServerRequest(reactive)
commencé à être utilisés; - Pourquoi : soutenir le jet stack au même niveau que le traditionnel;
- Que faire : remplacer les noms de classe dans les signatures.
Par exemple, dans la classe ErrorAttributes, maintenant, au lieu de RequestAttributes, deux autres classes ont commencé à être utilisées dans les méthodes: ce sont WebRequest et ServerRequest. La raison est la même. Et que faire? Si vous passez du premier au deuxième Spring Boot, vous devez remplacer RequestAttributes par WebRequest. Eh bien, si vous êtes déjà sur le deuxième, utilisez ServerRequest. Évidemment, n'est-ce pas? ..
Comment être
Il existe de nombreux exemples de ce type, nous ne les trierons pas tous. Que faire à ce sujet? Tout d'abord, il vaut la peine de jeter un coup d'œil dans le Guide de migration Spring Boot 2.0 afin de constater à temps le changement qui vous concerne. Par exemple, il mentionne renommer des classes complètement non évidentes. Pourtant, si quelque chose s'est séparé et cassé, il convient de considérer que le concept de «web» est divisé en 2: «servlet» et «réactif». Avec l'orientation dans toutes les classes et tous les packages, cela peut vous aider. En outre, il convient de garder à l'esprit que non seulement les classes et les packages eux-mêmes ont été renommés, mais également des dépendances et des artefacts entiers. Comme cela, par exemple, s'est produit avec Spring Cloud.
Type de contenu. Déterminer le type de réponse HTTP
Assez parlé de ces choses simples de la compilation, tout y est clair et simple. Parlons de ce qui peut se produire lors de l'exécution et, par conséquent, peut tirer, même si Spring Boot 2 fonctionne pour vous depuis longtemps. Parlons de la définition du type de contenu.

Ce n'est un secret pour personne que Spring peut écrire des applications Web, des pages et des API REST, et ils peuvent rendre le contenu avec une grande variété de types, que ce soit XML, JSON ou autre. Et l'un des charmes que Spring aime tellement, c'est que vous n'avez pas à vous soucier de la définition du type donné dans votre code. Vous pouvez espérer de la magie. Cette magie opère, relativement parlant, de trois manières différentes: soit en se basant sur l'en-tête Accept provenant du client, soit sur l'extension du fichier demandé, soit sur un paramètre spécial de l'URL, qui, bien entendu, peut également être piloté.
Prenons un exemple simple (
code source complet ). Ci-après, j'utiliserai la notation de Gradle, mais même si vous êtes un fan de Maven, il ne vous sera pas difficile de comprendre ce qui est écrit ici: nous construisons une minuscule application sur le premier Spring Boot et n'utilisons qu'un seul web de démarrage.
Exemple (v1.x):
dependencies { ext { springBootVersion = '1.5.14.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") }
En tant que code exécutable, nous avons une seule classe dans laquelle la méthode du contrôleur est immédiatement déclarée.
@GetMapping(value = "/download/{fileName: .+}", produces = {TEXT_HTML_VALUE, APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE}) public ResponseEntity<Resource> download(@PathVariable String fileName) {
Il prend un certain nom de fichier en entrée, qu'il formera et donnera probablement. Il forme vraiment son contenu dans l'un des trois types indiqués (en le déterminant par le nom du fichier), mais ne spécifie aucunement le type de contenu - nous avons Spring, il fera tout lui-même.

En général, vous pouvez même essayer de le faire. En effet, si nous demandons le même document avec des extensions différentes, il sera retourné avec le type de contenu correct en fonction de ce que nous retournons: si vous voulez - json, si vous voulez - txt, si vous voulez - html. Cela fonctionne comme un conte de fées.
Mise à jour vers v2.x
dependencies { ext { springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") }
Il est temps de passer au deuxième Spring Boot. Nous changeons simplement le nombre 1 en 2.
Spring MVC Path Matching Default Behavior ChangeMais nous sommes des ingénieurs, nous allons jeter un œil au Guide de migration, et tout à coup, quelque chose en dit. Mais il mentionne une sorte de "correspondance de chemin de suffixe". Il s'agit de savoir comment mapper correctement les méthodes en Java avec une URL. Mais ce n'est pas notre cas, bien qu'un peu comme.

Par conséquent, nous marquons, vérifions et frappons! - ne fonctionne tout à coup pas. Pour une raison quelconque, juste text / html commence à être donné partout, et si vous le creusez, ce n'est pas seulement text / html, mais juste le premier des types que vous avez spécifiés dans l'attribut produit sur l'annotation @GetMapping. Pourquoi Il semble, pour le dire doucement, incompréhensible.

Et ici, aucune note de version ne vous aidera, vous devez lire la source.
ContentNegotiationManagerFactoryBean
public ContentNegotiationManagerFactoryBean build() { List<ContentNegotiationStrategy> strategies = new ArrayList<>(); if (this.strategies != null) { strategies.addAll(this.strategies); } else { if (this.favorPathExtension) { PathExtensionContentNegotiationStrategy strategy;
Vous pouvez y trouver un classique avec un nom abrégé laconique très compréhensible, qui mentionne un certain drapeau appelé "envisager l'extension sur le chemin" (favorPathExtension). La valeur de ce drapeau «vrai» correspond à l'application d'une certaine stratégie avec un autre nom concis court compréhensible, à partir de laquelle il est clair qu'il est juste responsable de déterminer le type de contenu par extension de fichier. Comme vous pouvez le voir, si le drapeau est faux, la stratégie ne s'appliquera pas.

Oui, probablement, beaucoup ont remarqué qu'au printemps, apparemment, il existe une sorte de directive, de sorte que le nom doit être bien, au moins vingt caractères.

Si vous approfondissez un peu plus, vous pouvez creuser un tel fragment. Dans le framework Spring lui-même, et non dans la cinquième version, comme on pouvait s'y attendre, mais depuis des temps immémoriaux, ce drapeau par défaut a été défini sur "true". Dans Spring Boot et dans la deuxième version, il a été bloqué par un autre, qui est maintenant disponible pour le contrôle à partir des paramètres. Autrement dit, nous pouvons maintenant les orienter de la gestion de l'environnement, et ce n'est que dans la deuxième version. Tu te sens? Là, il a déjà pris le sens de «faux». Autrement dit, ils voulaient, en quelque sorte, faire de leur mieux, mettre ce drapeau dans les paramètres (et c'est super), mais la valeur par défaut a été changée en une autre (ce n'est pas très).
Les développeurs du framework sont également des personnes, ils ont également tendance à faire des erreurs. Que faire à ce sujet? Il est clair que vous devez changer le paramètre dans votre projet, et tout ira bien.
La seule chose à faire au cas où, pour vider votre conscience, est de consulter la documentation de Spring Boot juste pour toute mention de ce drapeau. Et là, il est vraiment
mentionné , mais seulement dans un contexte étrange:
Si vous comprenez les mises en garde et souhaitez toujours que votre application utilise la correspondance de modèle de suffixe, la configuration suivante est requise:
spring.mvc.contentnegotiation.favor-path-extension = true
...
Il est écrit, disent-ils, si vous comprenez toutes les astuces et souhaitez toujours utiliser la correspondance de chemin de suffixe, cochez cette case. Sentez-vous la différence? Il semble que nous parlions de la définition du type de contenu dans le contexte de cet indicateur, mais ici nous parlons de la correspondance des méthodes Java et des URL. Cela semble en quelque sorte incompréhensible.
Nous devons creuser plus loin. Il existe une telle
demande de pull sur GitHub:

Dans le cadre de cette demande de tirage, ces modifications ont été apportées - en changeant la valeur par défaut - et là l'un des auteurs du cadre dit que ce problème a deux aspects: l'un est juste la correspondance de chemin, et le second est la définition du type de contenu . Autrement dit, le drapeau s'applique aux deux, et ils sont inextricablement liés.
Vous pouvez bien sûr le trouver tout de suite sur GitHub si vous saviez seulement où chercher.
Correspondance de suffixeDe plus, la documentation du Spring Framework lui-même indique également que l'utilisation d'extensions de fichiers était nécessaire auparavant, mais maintenant elle n'est plus considérée comme une nécessité. De plus, cela s'est révélé problématique dans un certain nombre de cas.
Résumer
La modification de la valeur d'indicateur par défaut n'est pas du tout un bug, mais une fonctionnalité. Il est inextricablement lié à la définition de la correspondance de chemin et est conçu pour faire
trois choses :
- réduire les risques de sécurité (lesquels, je vais clarifier);
- aligner le comportement de WebFlux et WebMvc, ils différaient dans cet aspect;
- aligner l'instruction dans la documentation avec le code du framework.
Comment être
Tout d'abord, dans la mesure du possible, vous ne devez pas vous fier à la définition du type de contenu par extension. L'exemple que j'ai montré est un contre-exemple, pas besoin de faire ça! De plus, il n'est pas nécessaire de s'appuyer sur le fait que les demandes de la forme "GET something.json", par exemple, seront simplement "GET something". Ce fut le cas dans Spring Framework 4 et Spring Boot 1. Cela ne fonctionne plus. Si vous devez mapper vers un fichier avec l'extension, vous devez le faire explicitement. Au lieu de cela, il est préférable de s'appuyer sur l'en-tête Accept ou sur le paramètre URL, dont vous pouvez piloter le nom. Eh bien, si vous ne pouvez pas le faire de quelque façon que ce soit, disons que certains de vos anciens clients mobiles ont cessé de se mettre à jour au siècle dernier, alors vous devrez retourner ce drapeau, le définir sur "true", et tout fonctionnera comme avant.
De plus, pour une compréhension générale, vous pouvez lire le chapitre «Suffix match» dans la documentation du Spring Framework, il est considéré par les développeurs comme une sorte de recueil de bonnes pratiques dans ce domaine, et vous familiariser avec ce qu'est l'
attaque Reflected File Download , juste implémentée à l'aide de manipulations avec extension de fichier.
Ordonnancement. Tâches planifiées ou périodiques
Modifions un peu la portée et parlons de la réalisation de tâches selon un calendrier ou périodiquement.
Un exemple de tâche. Message de journal toutes les 3 secondes
Ce qui est dit, je pense, est compréhensible. Nous avons des besoins commerciaux, faire quelque chose avec une sorte de répétition, donc nous allons immédiatement passer à l'exemple. Supposons que nous ayons une tâche méga-complexe: produire de la boue dans le journal toutes les 3 secondes.

Cela peut être fait, évidemment, de diverses manières, pour eux, il y a déjà quelque chose au printemps. Et le trouver - de nombreuses façons.
Option 1: recherchez un exemple dans votre projet
@Service public class ReallyBusinessService {
Nous pouvons regarder dans notre propre projet et nous trouverons probablement quelque chose comme ça. Une annotation va s'accrocher à la méthode publique, et il sera clair que dès que vous la raccrochez, tout fonctionne comme dans un conte de fées.
Option 2: rechercher l'annotation souhaitée

Vous pouvez rechercher l'annotation directement par son nom, et il sera également clair dans la documentation que vous la suspendez - et tout fonctionne.
Option 3: googler
Si vous n'avez pas confiance en vous, vous pouvez le rechercher sur Google, et d'après ce que vous avez
trouvé, il sera également clair que tout commencera par une annotation.
@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 voit le hic là-dedans? Nous sommes des ingénieurs après tout, vérifions comment cela fonctionne dans la réalité.
Montrez-moi le code!
Considérez une tâche spécifique (la tâche elle-même et le code sont
dans mon référentiel ).
Qui ne veut pas lire, vous pouvez regarder ce fragment de la vidéo avec une démonstration (jusqu'à la 22e minute):
En tant que dépendance, nous utiliserons le premier Spring Boot avec deux démarreurs. L'un est pour le Web, c'est comme si nous développions un serveur Web, et le second est un actionneur à ressort, de sorte que nous ayons des fonctionnalités prêtes pour la production, de sorte que nous ressemblions au moins à quelque chose de réel.
dependencies { ext { springBootVersion = '1.5.14.RELEASE'
Et notre code exécutable sera encore plus 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 général, presque rien de remarquable, à l'exception de la seule et unique méthode à laquelle nous avons suspendu l'annotation. Nous l'avons copié quelque part et nous nous attendons à ce que cela fonctionne.
Vérifions, nous sommes des ingénieurs. Nous commençons. Nous supposons que toutes les trois secondes un tel message sera enregistré. Tout devrait fonctionner hors de la boîte, nous nous assurons que tout est lancé sur le premier Spring Boot, et nous attendons la sortie de la ligne souhaitée. Trois secondes passent - une ligne est émise, six passes - une ligne s'affiche. Les optimistes ont gagné, tout fonctionne.

Seul le moment est venu de passer au deuxième Spring Boot. Nous ne nous embêterons pas, il suffit de passer de l'un à l'autre:
dependencies { ext {
En théorie, le Guide de migration ne nous a avertis de rien et nous nous attendons à ce que tout fonctionne sans déviation. Du point de vue du code exécutable, nous n'avons aucun des autres râteaux que j'ai mentionnés plus tôt (incompatibilité au niveau de l'API ou autre), car l'application est aussi simple que possible.
Nous commençons. Tout d'abord, nous sommes convaincus que nous travaillons sur le deuxième Spring Boot, sinon il n'y a pas, semble-t-il, d'écart.

Cependant, 3 secondes passent, 6, 9, mais il n'y a toujours pas d'Herman - pas de conclusion, rien ne fonctionne.
Comme cela arrive souvent, l'attente est en contradiction avec la réalité. Ils nous écrivent souvent dans la documentation que, en fait, tout fonctionne par défaut dans Spring Boot, qu'en général nous pouvons simplement démarrer tel quel avec un minimum de problèmes, et aucune configuration n'est requise. Mais dès qu'il s'agit de la réalité, il s'avère souvent que l'on doit toujours lire la documentation. En particulier, si vous creusez profondément, vous pouvez trouver ici ces lignes:
7.3.1. Activer la planification des annotations
Pour activer la prise en charge des annotations @Scheduled et Async , vous pouvez ajouter @EnableScheduling et @EnableAsync à l'une de vos classes @Configuration.
Pour que l'annotation planifiée fonctionne, vous devez accrocher une autre annotation à la classe avec une autre annotation. Eh bien, comme d'habitude au printemps. Mais pourquoi cela a-t-il fonctionné auparavant? Nous n'avons rien fait de tel. De toute évidence, cette annotation quelque part était suspendue plus tôt dans le premier Spring Boot, mais maintenant, pour une raison quelconque, elle n'est pas dans le second.

Nous commençons à fouiller dans les codes sources du premier Spring Boot. Nous constatons qu'il y a une classe sur laquelle il est censé se bloquer. Nous regardons de plus près, il s'appelle "MetricExportAutoConfiguration" et, apparemment, est responsable de la livraison de ces mesures de performances à l'extérieur, à certains agrégateurs centralisés, et il a vraiment cette annotation.

De plus, il fonctionne de telle manière qu'il inclut son comportement sur l'ensemble de l'application à la fois, il n'a pas besoin d'être suspendu sur des classes distinctes. C'est cette classe qui a été le fournisseur de ce comportement, et ce n'est pas le cas pour une raison quelconque. Pourquoi?

GitHub nous pousse tout de même à une telle fouille archéologique: dans le cadre de la transition vers la deuxième version de Spring Boot, cette classe a été fauchée avec l'annotation. Pourquoi? Oui, car le moteur de livraison des métriques a également changé: ils n'utilisent plus leur propre script, mais sont passés à Micrometer - une solution vraiment significative. C'est juste quelque chose de superflu qui lui reste. C'est peut-être même correct.
Qui ne veut pas lire, voir une courte démo pendant 30 secondes:
Il s'ensuit que si nous prenons maintenant et suspendons manuellement l'annotation manquante dans notre classe d'origine, alors, en théorie, le comportement devrait devenir correct.
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 …”); } }
Pensez-vous que cela fonctionnera? Vérifions. Nous commençons.

On peut voir qu'après 3 secondes, après 6 et après 9, le message attendu par nous est toujours affiché dans le journal.
Comment être
Que faire de cela dans ce cas particulier et plus général? Peu importe à quel point cela peut sembler moraliste, premièrement, cela vaut la peine de lire non seulement des fragments de documentation copiés, mais aussi un peu plus large, juste pour couvrir ces aspects.
Deuxièmement, rappelez-vous que dans Spring Boot, même si de nombreuses fonctionnalités sont prêtes à l'emploi (planification, async, mise en cache, ...), elles ne sont pas toujours incluses, elles doivent être explicitement activées.
Troisièmement, cela ne dérange pas d'être sûr: ajoutez des annotations
Enable * (et toute leur famille) à votre code, sans espérer de framework. Mais la question se pose alors: que se passera-t-il si, par hasard, mes collègues et moi ajoutons quelques annotations, comment se comporteront-ils? , . : . , .
, @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); } }
Nous commençons.

. , , - 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, , .
Comment être
-, , 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' , , — . , , , :
, , . - . , .
Par exemple:
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, , , .
, - , - , . , . :

, - , , , , -, . .
Comment être
: - . -, , 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 .
Que faire à ce sujet? , , 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 )
Comment être
, , 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 , .
Autre
, , , 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. — .