Evgeny
EvgenyBorisov Borisov (NAYA Technologies) et Kirill
tolkkv Tolkachev (Cyan.Finance,
Twitter ) parlent des moments les plus importants et intéressants de Spring Boot sur l'exemple d'un démarreur pour une banque de fer imaginaire.

L'article est basé sur le
rapport d' Eugene et Cyril de notre conférence Joker 2017. Sous la coupe se trouve la transcription vidéo et texte du rapport.
La conférence Joker est parrainée par de nombreuses banques, alors imaginons que l'application sur laquelle nous étudierons le travail de Spring boot et le démarreur que nous créons est liée à la banque.

Supposons donc qu'une commande soit reçue pour une demande de la Banque de fer de Braavos. Une banque ordinaire transfère simplement de l'argent dans les deux sens. Par exemple, comme ceci (nous avons une API pour cela):
http://localhost:8080/credit\?name\=Targarian\&amount\=100
Et dans Iron Bank, avant de transférer de l'argent, il est nécessaire que l'API de la banque calcule si une personne peut le retourner. Peut-être qu'il ne survivra pas à l'hiver et qu'il n'y aura personne pour revenir. Par conséquent, il est fourni un service qui vérifie la fiabilité.
Par exemple, si nous essayons de transférer de l'argent à Targaryen, l'opération sera approuvée:

Mais si Stark, alors non:

Pas étonnant: les Starks meurent trop souvent. Pourquoi transférer de l'argent si une personne ne survit pas à l'hiver?
Voyons à quoi ça ressemble à l'intérieur.
@RestController @RequiredArgsConstructor public class IronBankController { private final TransferMoneyService transferMoney; private final MoneyDao moneyDao; @GetMapping("/credit") public String credit(@RequestParam String name, @RequestParam long amount) { long resultedDeposit = transferMoney.transfer(name, amount); if (resultedDeposit == -1) { return "Rejected<br/>" + name + " <b>will`t</b> survive this winter"; } return format( "<i>Credit approved for %s</i> <br/>Current bank balance: <b>%s</b>", name, resultedDeposit ); } @GetMapping("/state") public long currentState() { return moneyDao.findAll().get(0).getTotalAmount(); } }
Il s'agit d'un contrôleur de chaîne standard.
Qui est responsable de la logique du choix, à qui accorder un prêt et à qui - pas? Ligne simple: si vous vous appelez Stark, nous ne trahissons certainement pas. Dans d'autres cas - quelle chance. Banque ordinaire.
@Service public class NameBasedProphetService implements ProphetService { @Override public boolean willSurvive(String name) { return !name.contains("Stark") && ThreadLocalRandom.current().nextBoolean(); } }
Tout le reste n'est pas si intéressant. Ce sont des annotations qui font tout le travail pour nous. Tout est très rapide.
Où sont toutes les configurations principales? Il n'y a qu'un seul contrôleur. Dans Dao, c'est généralement une interface vide.
public interface MoneyDao extends JpaRepository<Bank, String> { }
Dans les services - seuls les services de traduction et de prédiction auxquels vous pouvez émettre. Il n'y a pas de répertoire Conf. En fait, nous n'avons que application.yml (une liste de ceux qui remboursent des dettes). Et le principal est le plus courant:
@SpringBootApplication @EnableConfigurationProperties(ProphetProperties.class) public class MoneyRavenApplication { public static void main(String[] args) { SpringApplication.run(MoneyRavenApplication.class, args); } }
Alors, où est toute la magie cachée?
Le fait est que les développeurs n'aiment pas penser aux dépendances, configurer les configurations, surtout s'il s'agit de configurations XML, et penser au démarrage de leur application. Par conséquent, Spring Boot résout ces problèmes pour nous. Il suffit d'écrire une application.
Dépendances
Le premier problème que nous avons toujours eu est un conflit de version. Chaque fois que nous connectons différentes bibliothèques qui font référence à d'autres bibliothèques, des conflits de dépendance apparaissent. Chaque fois que je lis sur Internet que j'ai besoin d'ajouter un gestionnaire d'entité, une question se pose et quelle version dois-je ajouter pour qu'elle ne casse rien?
Spring Boot résout le problème des conflits de version.
Comment obtenons-nous généralement un projet Spring Boot (si nous ne sommes pas arrivés à un endroit où il existe déjà)?
- ou allez sur start.spring.io , cochez les cases que Josh Long nous a appris à définir, cliquez sur Télécharger le projet et ouvrez le projet où tout est déjà là;
- ou utilisez IntelliJ, où, grâce à l'option apparue, les cases à cocher de Spring Initializer peuvent être définies à partir de là.
Si nous travaillons avec Maven, alors le projet aura pom.xml, où il y a un parent Spring Boot appelé
spring-boot-dependencies
. Il y aura un énorme bloc de gestion des dépendances.
Je n'entrerai pas dans les détails de Maven maintenant. Juste deux mots.
Le bloc de gestion des dépendances n'enregistre pas les dépendances. Il s'agit d'un bloc avec lequel vous pouvez spécifier des versions au cas où ces dépendances seraient nécessaires. Et lorsque vous indiquez une sorte de dépendance dans le bloc de gestion des dépendances sans spécifier la version, Maven commence à rechercher s'il existe un bloc de gestion des dépendances dans lequel le parent de cette version est écrit dans le pom parent ou ailleurs. C'est-à-dire dans mon projet, en ajoutant une nouvelle dépendance, je n'indiquerai plus la version dans l'espoir qu'elle soit indiquée quelque part dans le parent. Et si ce n'est pas spécifié dans le parent, cela ne créera certainement aucun conflit avec personne. Dans notre gestion des dépendances, cinq cents bonnes dépendances sont indiquées, et elles sont toutes cohérentes entre elles.
Mais quel est le problème? Le problème est que dans mon entreprise, par exemple, j'ai mon propre pom parent. Si je veux utiliser Spring, que dois-je faire avec mon pom parent?

Nous n'avons pas d'héritage multiple. Nous voulons utiliser notre pom et obtenir le bloc de gestion des dépendances de l'extérieur.

Cela peut être fait. Il suffit d'enregistrer l'importation de nomenclature du bloc de gestion des dépendances.
<dependencyManagement> <dependencies> <dependency> <groupId>io.spring.platform</groupId> <artifactId>platform-bom</artifactId> <version>Brussels-SR2</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
Qui veut en savoir plus sur bom - voir le rapport "
Maven vs Gradle ". Là, tout cela a été expliqué en détail.
Aujourd'hui, il est devenu assez à la mode parmi les grandes entreprises d'écrire de tels blocs de gestion des dépendances, où ils indiquent toutes les versions de leurs produits et toutes les versions de produits qui utilisent leurs produits et qui n'entrent pas en conflit les uns avec les autres. Et cela s'appelle bom. Cette chose peut être importée dans votre bloc de gestion des dépendances sans héritage.
Et voici comment cela se fait à Gradle (comme d'habitude, la même chose, seulement plus facile):
dependencyManagement { imports { mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE' } }
Parlons maintenant des dépendances elles-mêmes.
Qu'écrirons-nous dans l'application? La gestion des dépendances est bonne, mais nous voulons que l'application ait certaines capacités, par exemple, pour répondre via HTTP, pour avoir une base de données ou un support pour JPA. Par conséquent, tout ce dont nous avons besoin maintenant est d'obtenir trois dépendances.
Cela ressemblait à ça. Je veux travailler avec la base de données et cela commence: une sorte de gestionnaire de transactions est nécessaire, en conséquence le module spring-tx est nécessaire. J'ai besoin d'une mise en veille prolongée, donc EntityManager, hibernate-core ou autre chose est requis. Je configure tout via Spring, j'ai donc besoin d'un noyau de ressort. Autrement dit, pour une chose simple, vous avez dû penser à une douzaine de dépendances.
Aujourd'hui, nous avons des entrées. L'idée d'un démarreur est que nous en dépendons. Pour commencer, il agrège les dépendances nécessaires au monde dont il est issu. Par exemple, s'il s'agit d'un démarreur de sécurité, alors vous ne pensez pas aux dépendances nécessaires, elles arrivent immédiatement sous forme de dépendances transitives au démarreur. Ou si vous travaillez avec Spring Data Jpa, mettez une dépendance sur le démarreur et cela apportera tous les modules nécessaires pour travailler avec Spring Data Jpa.
C'est-à-dire Notre pom ressemble à ceci: il ne contient que les 3-5 dépendances dont nous avons besoin:
'org.springframework.boot:spring-boot-starter-web' 'org.springframework.boot:spring-boot-starter-data-jpa' 'com.h2database:h2'
Avec les dépendances triées, tout est devenu plus facile. Nous devons penser moins maintenant. Il n'y a pas de conflit et le nombre de dépendances a diminué.
Réglage du contexte
Parlons de la prochaine douleur que nous avons toujours eue - définir le contexte. Chaque fois que nous commençons à écrire une application à partir de zéro, la configuration de l'infrastructure entière prend beaucoup de temps. Nous avons enregistré dans xml ou java config beaucoup de soi-disant beans d'infrastructure. Si nous travaillions avec hibernate, nous avions besoin du bean EntityManagerFactory. Beaucoup de beans infrastructure - gestionnaire de transactions, source de données, etc. - Il fallait régler avec vos mains. Naturellement, ils sont tous tombés dans leur contexte.
Lors du rapport
Spring Ripper , nous avons créé le contexte dans le principal, et s'il s'agissait du contexte xml, il était initialement vide. Si nous avons construit le contexte via
AnnotationConfigApplicationContext
, il y avait quelques beanpostprocessors qui pouvaient configurer les beans en fonction des annotations, mais le contexte était également presque vide.
Et maintenant, dans le principal, il y a
SpringApplication.run
et aucun contexte n'est visible:
@SpringBootApplilcation class App { public static void main(String[] args) { SpringApplication.run(App.class,args); } }
Mais en réalité, nous avons un contexte.
SpringApplication.run
nous renvoie un peu de contexte.

Il s'agit d'un cas complètement atypique. Il y avait auparavant deux options:
- s'il s'agit d'une application de bureau, directement dans le principal, vous avez dû écrire de nouvelles avec vos mains, sélectionnez
ClassPathXmlApplicationContext
, etc.
- si nous travaillions avec Tomcat, il y avait un gestionnaire de servlets, qui, selon certaines conventions, cherchait XML et, par défaut, en construisait un contexte.
En d'autres termes, le contexte était en quelque sorte. Et nous avons quand même passé quelques classes de configuration à l'entrée. Dans l'ensemble, nous avons choisi le type de contexte. Maintenant, nous n'avons que
SpringApplication.run
, il prend les configurations comme arguments et construit un contexte
Devinette: que pouvons-nous y passer?
Étant donné:RipperApplication.class
public… main(String[] args) { SpringApplication.run(?,args); }
Question: quoi d'autre peut y être transféré?
Options:RipperApplication.class
String.class
"context.xml"
new ClassPathResource("context.xml")
Package.getPackage("conference.spring.boot.ripper")
La réponseLa réponse est:
La documentation indique que tout peut y être transféré. Au minimum, cela compilera et fonctionnera en quelque sorte.

C'est-à-dire en fait, toutes les réponses sont correctes. N'importe lequel d'entre eux peut fonctionner, même
String.class
, et dans certaines conditions, vous n'avez même rien à faire pour le faire fonctionner. Mais c'est une autre histoire.
La seule chose qui n'est pas mentionnée dans la documentation est sous quelle forme nous y envoyer. Mais cela vient déjà du domaine de la connaissance secrète.
SpringApplication.run(Object[] sources, String[] args) # APPLICATION SETTINGS (SpringApplication) spring.main.sources= # class name, package name, xml location spring.main.web-environment= # true/false spring.main.banner-mode=console # log/off
SpringApplication
est vraiment important ici - plus loin dans les diapositives, nous l'aurons avec Carlson.
Notre Carlson crée une sorte de contexte basé sur l'apport que nous lui transmettons. Je vous rappelle, nous lui donnons, par exemple, cinq options merveilleuses que vous pouvez faire fonctionner tout en utilisant
SpringApplication.run
:
RipperApplication.class
String.class
"context.xml"
new ClassPathResource("context.xml")
Package.getPackage("conference.spring.boot.ripper")
Que fait
SpringApplication
pour nous?

Lorsque nous avons créé le contexte de
new
in main à
new
, nous avions beaucoup de classes différentes qui implémentent l'interface
ApplicationContext
:

Et quelles sont les options disponibles lorsque Carlson crée le contexte?
Il ne crée que deux types de contexte: soit un contexte Web (
WebApplicationContext
), soit un contexte générique (
AnnotationConfigApplicationContext
).

Le choix du contexte est basé sur la présence de deux classes dans le chemin de classe:

Autrement dit, le nombre de configurations n'a pas diminué. Pour construire un contexte, nous pouvons spécifier toutes les options de configuration. Pour construire le contexte, je peux passer un script groovy ou xml; Je peux indiquer les packages à analyser ou passer la classe marquée avec quelques annotations. Autrement dit, j'ai toutes les possibilités.
Cependant, c'est Spring Boot. Nous n'avons pas encore créé un seul bac, pas une seule classe, nous n'avons que principal, et c'est notre Carlson -
SpringApplication.run
. À l'entrée, il reçoit une classe marquée d'une sorte d'annotation Spring Boot.
Si vous regardez dans ce contexte, que se passera-t-il là-bas?
Dans notre application, après avoir connecté une paire de démarreurs, il y avait 436 bacs.

Près de 500 grains juste pour commencer à écrire.

Ensuite, nous comprendrons d'où venaient ces grains.
Mais d'abord, nous voulons faire de même.
La magie des entrées, en plus de résoudre tous les problèmes de toxicomanie, est que nous n'avons connecté que 3 à 4 entrées et que nous avons 436 bacs. Nous relierions 10 démarreurs, il y aurait plus de 1000 bacs, car chaque démarreur, à l'exception des dépendances, apporte déjà des configurations dans lesquelles certains bacs nécessaires sont enregistrés. C'est-à-dire Vous avez dit que vous vouliez un démarreur pour le Web, vous avez donc besoin d'un répartiteur de servlet et de
InternalResourceViewResolver
. Nous avons connecté le démarreur jpa - nous avons besoin du bean
EntityManagerFactory
. Tous ces grains sont déjà quelque part dans les configurations de démarrage, et ils arrivent par magie à l'application sans aucune action de notre part.
Pour comprendre comment cela fonctionne, nous allons écrire aujourd'hui un démarreur, qui apportera également des bacs d'infrastructure à toutes les applications qui utilisent ce démarreur.
Loi sur le fer 1.1. Envoyez toujours un corbeau

Examinons l'exigence du client. Iron Bank possède de nombreuses applications différentes fonctionnant dans différentes succursales. Les clients veulent qu'un corbeau soit envoyé chaque fois que l'application augmente - des informations indiquant que l'application a augmenté.
Commençons à écrire le code dans l'application d'une banque de fer spécifique (banque de fer). Nous allons écrire un démarreur afin que toutes les applications Iron Bank qui dépendront de ce démarreur puissent envoyer automatiquement un corbeau. Nous nous souvenons que les démarreurs nous permettent de resserrer automatiquement les dépendances. Et surtout, nous n'écrivons presque aucune configuration.
Nous faisons un écouteur qui écoute le contexte à mettre à jour (le dernier événement), après quoi il envoie un corbeau. Nous écouterons
ContextRefreshEvent
.
public class IronListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println(" ..."); } }
Nous écrivons écouteur dans la configuration de démarrage. Jusqu'à présent, il n'y aura qu'un auditeur, mais demain le client demandera d'autres éléments d'infrastructure, et nous les écrirons également dans cette configuration.
@Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } }
La question se pose: comment faire en sorte que la configuration de notre démarreur s'adapte automatiquement à toutes les applications qui utilisent ce démarreur?
Pour toutes les occasions, il y a un «activer quelque chose».

Vraiment, si je compte sur 20 démarreurs, je vais devoir mettre
@Enable
? Et si le démarreur a plusieurs configurations? La classe de configuration principale sera bloquée avec
@Enable*
, comment est l'arbre du Nouvel An?

En fait, je veux obtenir une sorte d'inversion de contrôle au niveau de la dépendance. Je veux connecter le démarreur (pour que tout fonctionne), et je ne sais rien de son nom. Par conséquent, nous utiliserons spring.factories.

Alors qu'est-ce que spring.factories
La documentation indique qu'il existe de telles usines spring.factories dans lesquelles vous devez indiquer la correspondance des interfaces et ce que vous devez charger sur elles - nos configurations. Et tout cela apparaîtra comme par magie dans le contexte, tandis que diverses conditions y travailleront.

Ainsi, nous obtenons l'inversion de contrôle, dont nous avions besoin.

Essayons de mettre en œuvre. Au lieu d'accéder aux tripes du démarreur que j'ai connecté (prenez cette configuration, et ça ...), tout sera exactement le contraire. Le démarreur aura un fichier appelé
spring.factories . Dans ce fichier, nous indiquons quelle configuration de ce démarreur doit être activée pour tous ceux qui l'ont téléchargé. Un peu plus tard, je vais expliquer comment cela fonctionne exactement dans Spring Boot - à un moment donné, il commence à analyser tous les pots et à rechercher le fichier spring.factories.
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ironbank.IronConfiguration
@Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } }
Maintenant, il ne nous reste plus qu'à connecter le démarreur au projet.
compile project(':iron-starter')
Dans maven, de même - vous devez enregistrer la dépendance.
Nous lançons notre application. Le corbeau devrait décoller au moment où il monte, bien que nous n'ayons rien fait dans l'application elle-même. En termes d'infrastructure, nous avons bien sûr écrit et configuré le démarreur. Mais du point de vue du développeur, nous venons de connecter la dépendance et la configuration est apparue - le corbeau a volé. Tout ce que nous voulions.
Ce n'est pas magique. L'inversion du contrôle ne doit pas être magique. Tout comme l'utilisation du printemps ne doit pas être magique. Nous savons que c'est un cadre principalement pour l'inversion de contrôle. Comme il y a inversion de contrôle pour votre code, il y a donc inversion de contrôle pour les modules.
@SpringBootApplication autour de la tête
Rappelez-vous le moment où nous avons construit le contexte avec nos mains. Nous avons écrit un
new AnnotationConfigApplicationContext
et passé une configuration à l'entrée, qui était une classe java. Maintenant, nous écrivons également
SpringApplication.run
et passons la classe là, qui est la configuration, seulement elle est marquée avec une autre annotation plutôt puissante
@SpringBootApplication
, qui porte le monde entier.
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { … }
Tout d'abord, à l'intérieur, il y a
@Configuration
, c'est-à-dire qu'il s'agit d'une configuration. Vous pouvez y écrire
@Bean
et, comme d'habitude, enregistrer les beans.
Deuxièmement,
@ComponentScan
se situe au-dessus. Par défaut, il analyse absolument tous les packages et sous-packages. Par conséquent, si vous commencez à créer des services dans le même package ou dans ses
@Service
-
@Service
-
@Service
,
@RestController
- ils sont automatiquement analysés, car la configuration principale démarre votre processus d'analyse.
En fait,
@SpringBootApplication
ne fait rien de nouveau. Il a simplement compilé toutes les meilleures pratiques qui étaient dans les applications Spring, il s'agit donc maintenant d'une sorte de composition d'annotation, y compris
@ComponentScan
.
De plus, il y a encore des choses qui n'existaient pas auparavant -
@EnableAutoConfiguration
. C'est la classe que j'ai prescrite dans les usines de printemps.
@EnableAutoConfiguration
, si vous regardez, porte
@Import
avec:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration { String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; Class<?>[] exclude() default {}; String[] excludeName() default {}; }
La tâche principale de
@EnableAutoConfiguration
est de faire l'importation dont nous voulions nous débarrasser dans notre application, car son implémentation aurait dû nous forcer à écrire le nom d'une classe à partir du démarreur. Et nous ne pouvons le découvrir que dans la documentation. Mais tout devrait être seul.
Vous devez faire attention à cette classe. Il se termine par
ImportSelector
. Au printemps, nous écrivons
Import(Some Configuration.class)
une configuration et elle se charge, comme toutes ses dépendances. C'est
ImportSelector
, ce n'est pas une configuration.
ImportSelector
tous nos démarreurs en contexte. Il traite l'annotation
@EnableAutoConfiguration
de spring.factories, qui sélectionne les configurations à charger et ajoute les beans que nous avons spécifiés dans IronConfiguration au contexte.

Comment le fait-il?
Tout d'abord, il utilise une classe utilitaire simple, SpringFactoriesLoader, qui examine spring.factories et charge tout à partir de ce qui est demandé. Il a deux méthodes, mais elles ne sont pas très différentes.

Spring Factories Loader existait au printemps 3.2, mais personne ne l'a utilisé. Il a apparemment été écrit comme un développement potentiel du cadre. Et c'est ainsi qu'il est devenu Spring Boot, où il existe de nombreux mécanismes utilisant la convention spring.factories. Nous montrerons plus loin que, outre la configuration, vous pouvez également écrire dans spring.factories - auditeurs, processeurs inhabituels, etc.
static <T> List<T> loadFactories( Class<T> factoryClass, ClassLoader cl ) static List<String> loadFactoryNames( Class<?> factoryClass, ClassLoader cl )
Voici comment fonctionne l'inversion de contrôle.
Nous semblons respecter le principe d'ouverture et de fermeture selon lequel il n'est pas nécessaire de changer quelque chose à chaque fois. Chaque démarreur apporte beaucoup de choses utiles au projet (jusqu'à présent, nous ne parlons que des configurations qu'il transporte). Et chaque démarreur peut avoir son propre fichier appelé spring.factories. Avec son aide, il raconte ce qu'il porte exactement. Et dans Spring Boot, il existe de nombreux mécanismes différents qui sont capables, de tous les démarreurs, d'apporter ce que Spring.factories dit.Mais il y a une nuance dans tout ce schéma. Si nous allons étudier comment cela fonctionne au printemps lui-même, comme l'écrivent les personnes qui ont créé tout ce schéma de démarreurs, nous verrons qu'elles ont une dépendance org.springframework.boot:spring-boot-autoconfigure
, il y a une ligne dans META-INF / spring.factories avecEnableAutoConfiguration
, et il a beaucoup de configurations (la dernière fois que j'ai regardé, il y avait environ 80 autoconfigurations non connectées là-bas). spring-boot-autoconfigure.jar/spring.factories</b> org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.\ ...
Autrement dit, que je connecte le démarreur ou que je ne me connecte pas, lorsque je travaille avec Spring Boot, il y aura toujours l'un des pots (le pot de Spring Boot lui-même), dans lequel se trouvent ses usines Spring.factories personnelles, où 90 configurations sont écrites. Chacune de ces configurations peut contenir de nombreuses autres configurations, par exemple CacheAutoConfiguration
, contenant exactement une telle chose - quelque chose que nous voulions éviter: for (int i = 0; i < types.length; i++) { Imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports;
De plus, alors une carte est statiquement retirée de la classe là-bas, et les configurations chargées (qui ne sont pas dans ce printemps. Usines) sont codées en dur dans cette carte. Ils ne seront pas si faciles à trouver. private static final Map<CacheType, Class<?>> MAPPINGS; static { Map<CacheType, Class<?>> mappings = new HashMap<CacheType, Class<?>>(); mappings.put(CacheType.GENERIC, GenericCacheConfiguration.class); mappings.put(CacheType.EHCACHE, EhCacheCacheConfiguration.class); mappings.put(CacheType.HAZELCAST, HazelcastCacheConfiguration.class); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE, JCacheCacheConfiguration.class); mappings.put(CacheType.COUCHBASE, CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS, RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE, CaffeineCacheConfiguration.class); addGuavaMapping(mappings); mappings.put(CacheType.SIMPLE, SimpleCacheConfiguration.class); mappings.put(CacheType.NONE, NoOpCacheConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); }
La chose la plus intéressante est qu'au stade de démarrage, ils vont tous vraiment essayer de démarrer.
Ils vont essayer. Mais:
Pour résumer les résultats intermédiaires. Une partie des configurations - bons, gentils, bons démarreurs, qui observent l'inversion de contrôle et le principe ouvert fermé - portent leurs usines à ressort dans lesquelles leurs tripes sont écrites. C'est ce que nous ferons; en principe, nous ne pouvons pas faire autrement.De plus, il existe une autre partie des configurations prescrites dans le Spring Boot lui-même, qui sont toujours chargées - il y en a 90 de plus. Il existe également 30 autres configurations qui sont simplement codées en dur dans Spring Boot.Tout cela monte, puis les configurations commencent à être filtrées. Fin 2013, un rapport a été publiésur les nouveautés de Spring 4, où il était dit qu'une annotation apparaissait @Conditional
, ce qui permet d'écrire des conditions dans ses annotations qui se réfèrent aux classes qui retournent true
ou false
. En fonction de cela, les beans sont créés ou non. Étant donné que la configuration java de Spring est également un bean, vous pouvez également y définir différentes conditions. Ainsi, les configurations sont prises en compte, mais si des retours conditionnels false
sont rejetés.Mais il y a des nuances. Tout d'abord, cela conduit à une situation dans laquelle le bac peut être ou non, en fonction de certains paramètres d'environnement.
Considérez ceci comme un exemple.Loi du fer 1.2. Raven uniquement en production
Le client a une nouvelle exigence. Raven est une chose chère, il n'y en a pas beaucoup. Par conséquent, ils ne doivent être lancés que si nous savons que la production a augmenté.
En conséquence, l'auditeur qui lance le corbeau ne doit être créé que s'il s'agit d'une production. Essayons de le faire.Nous entrons dans la configuration et écrivons: @Configuration <b>@ConditionalOnProduction</b> public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } }
Comment décidons-nous s'il s'agit d'une production ou non? J'ai eu une entreprise étrange qui a dit: "Si Windows est sur la machine, cela ne signifie pas la production, mais sinon Windows, alors la production." Chacun a son propre conditionnel.Plus précisément, l'Iron Bank a déclaré vouloir gérer cela manuellement: lorsque le service augmente, un pop-up devrait apparaître: "production ou non". Une telle condition n'est pas fournie dans Spring Boot. @Retention(RUNTIME) @Conditional(OnProductionCondition.class) public @interface ConditionalOnProduction { }
On fait un bon vieux popap: public class OnProductionCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { return JOptionPane.showConfirmDialog(parentComponent: null, " ?") == 0; } }
Essayons.
Nous élevons le service, cliquons oui dans la fenêtre, et le corbeau vole (un auditeur est créé).Nous recommençons, répondons non, le corbeau ne vole pas.Ainsi, l'annotation @Conditional(OnProductionCondition.class)
fait référence à la classe qui vient d'être écrite, où il existe une méthode qui devrait retourner true
ou false
. Une telle climatisation peut être inventée indépendamment, ce qui rend l'application très dynamique, lui permet de fonctionner différemment dans différentes conditions.Pazzler
Nous @ConditionalOnProduction
avons donc écrit. On peut faire plusieurs configurations, les mettre en condition. Supposons que nous ayons notre propre condition et qu'elle soit populaire - comme @ConditionalOnProduction
. Et il y a, par exemple, 15 fèves qui ne sont nécessaires qu'en production. Je les ai marqués avec cette annotation.Question: la logique qui détermine s'il s'agit de production ou non, combien de fois cela devrait-il fonctionner?Quelle différence ça marche? Eh bien, peut-être que cette logique coûte cher, cela prend du temps et le temps c'est de l'argent.À titre d'illustration, nous avons donné un exemple: @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... } @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() { return new DragonGlassFactory(); } ... }
Ici, nous avons deux bacs: un régulier, une configuration. Les deux sont marqués d'une annotation conditionnelle - ils ne sont nécessaires que si l'hiver est venu.L'annotation coûte deux fois. Chaque appel au centre météorologique dans le monde de Game of Thrones est cher - vous devez payer de l'argent à chaque fois pour connaître la météo.
Si cela fonctionnait avec la mise en cache, la logique ne serait invoquée qu'une seule fois (c'est-à-dire OnProductionCondition.class
qu'elle invoquerait une fois, une fenêtre avec un choix apparaîtrait une fois - production ou non). Un travail cohérent semble logique. D'un autre côté, une configuration est créée à un moment donné et un autre bean peut être créé en quelques secondes lorsque quelque chose change. Et si l'hiver arrive dans ces 5 secondes?Bonne réponsepas très clair - 300 ou 400. Il y a en fait une sorte de jeu complet. Nous avons fouillé pendant très longtemps pour d'abord comprendre ce qui se passait. Comment cela se produit est un problème distinct.La situation est comme ça. Si kondishn est au- dessus de la classe supérieure (classe @Component
, @Configuration
ou @Service
avec lui est kondishn), il remplit trois fois sur chaque bac. De plus, si cette configuration est enregistrée dans le démarreur, alors deux fois. @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... }
Si le bac est enregistré dans la configuration, alors toujours une fois. @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() { return new DragonGlassFactory(); } ... }
Par conséquent, cette énigme n'a pas de réponse exacte, car vous devez savoir où la configuration est écrite. S'il est enregistré dans le démarreur, sa condition pour une raison quelconque fonctionnera deux fois, et la condition pour le bac en tout cas fonctionnera une fois, nous obtenons 300. Mais si la configuration n'est pas dans le démarreur, seule sa condition démarrera trois fois, plus une fois pour le bac . Nous en obtenons 400.La question se pose: comment cela fonctionne-t-il et pourquoi est-ce ainsi? Et ma réponse est la suivante:
peu importe comment cela fonctionne. Il est important de comprendre ce qui suit: lorsque vous écrivez votre annotation de condition, cela vaut la peine de le mettre en cache vous-même, et à travers un champ statique afin que la logique ne soit pas appelée plusieurs fois. Parce que même si vous avez utilisé cette annotation une fois, la logique fonctionnera plus d'une fois.Loi sur le fer 1.3. Raven à
Nous continuons à développer notre démarreur. Il faut en quelque sorte préciser le vol du corbeau.
Dans quel dossier prescrit-on des choses pour le starter? Les démarreurs apportent une configuration dans laquelle il y a des beans. Comment sont configurés ces beans? Où trouvent-ils la source de données, l'utilisateur, etc. Naturellement, ils ont des valeurs par défaut pour toutes les occasions, mais comment permettent-ils de redéfinir cela? Il existe deux options: application.properties
et application.yml
. Vous pouvez y saisir des informations qui seront toujours magnifiquement complétées automatiquement dans IDEA.Qu'est-ce qui aggrave notre entrée? Quiconque l'utilise devrait également être en mesure de dire à quelles adresses le corbeau vole - nous devons établir une liste de destinataires. Ceci est le premier.Deuxièmement, nous voulons que l'auditeur ne soit pas créé et que le corbeau ne soit pas envoyé si la personne n'a pas enregistré de destinataires. Nous avons besoin d'une condition supplémentaire pour créer un auditeur que le corbeau envoie. C'est-à-dire
le démarreur lui-même est nécessaire car il peut avoir beaucoup de choses différentes en plus d'un corbeau. Mais s'il n'est pas écrit où le corbeau devrait voler, celui-ci n'est tout simplement pas créé.Et le troisième - nous voulons également effectuer la saisie semi-automatique, afin que les personnes qui ont tiré notre démarreur pour elles-mêmes obtiennent un compliment sur toutes les propriétés que le démarreur lit.Pour chacune de ces tâches, nous avons notre propre outil. Mais tout d'abord, vous devez regarder les annotations existantes. Peut-être que quelque chose nous conviendra? @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ...
En effet, il y a des choses ici qui nous aideront. Tout d'abord @ConditionalOnProperty
. Il s'agit d'une condition qui fonctionne s'il existe une propriété spécifique ou une propriété avec une valeur spécifiée dans application.yml. De même, nous devons @ConfigurationalProperty
effectuer une saisie semi-automatique.Saisie semi-automatique
Nous devons nous assurer que toutes les propriétés commencent à se remplir automatiquement. Ce serait bien si cela se complétait automatiquement non seulement parmi les personnes qui les enregistreraient dans leur application.yml, mais aussi dans notre entrée.Appelons notre propriété «corbeau». Il doit savoir où voler. @ConfigurationProperties("") public class RavenProperties { List<String> ; }
IDEA nous dit que quelque chose ne va pas ici:
La documentation dit que nous n'avons pas ajouté de dépendance (dans Maven il n'y aurait aucune référence à la documentation, mais un bouton «ajouter une dépendance»). Ajoutez-le simplement à votre projet. subproject { dependencies { compileOnly 'org.springframework.boot:spring-boot-configuration-processor' compile 'org.springframework.boot: spring-boot-starter' } }
Maintenant, selon IDEA, nous avons tout.Je vais vous expliquer quel type de dépendance nous avons ajouté. Tout le monde sait ce qu'est un processeur d'annotation. Dans une forme simplifiée, c'est une chose qui peut faire quelque chose au stade de la compilation. Par exemple, Lombok a son propre processeur d'annotation, qui génère beaucoup de code utile au stade de la compilation - setters, getters.D'où vient la saisie semi-automatique sur la propriété qui se trouve dans les propriétés de l'application? Il existe un fichier JSON avec lequel IDEA peut travailler. Ce fichier décrit toutes les propriétés que IDEA devrait être capable de compiler automatiquement. Si vous voulez la propriété que vous avez proposée pour le démarreur, IDEA peut également compiler automatiquement, vous avez deux façons:- vous pouvez manuellement entrer dans ce JSON vous-même et les ajouter dans un certain format;
- vous pouvez extraire le processeur d'annotation de Spring Boot, qui peut générer ce morceau de JSON lui-même au stade de la compilation. Les propriétés qui doivent y être ajoutées sont déterminées par l'annotation magique Spring Boot, avec laquelle nous pouvons marquer les classes détentrices de propriété. Au stade de la compilation, le processeur d'annotation Spring Boot recherche toutes les classes marquées
@ConfigurationalProperties
, leur lit la propriété name et génère JSON. En conséquence, tous ceux qui dépendront du démarreur recevront ce JSON en cadeau.
Vous devez également vous rappeler @EnableConfigurationProperties
que cette classe apparaît dans votre contexte comme un bean. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction public RavenListener ravenListener() { return new RavenListener(); } }
Tout n'a pas l'air très bien, mais vous devez le faire pour qu'il apparaisse un peu plus tôt que le reste des beans (car le reste des beans utilise sa propriété pour se configurer).En conséquence, il a fallu mettre deux annotations:@EnableConfigurationProperties
en indiquant les propriétés de qui;
@ConfigurationalProperties
dire quel préfixe.
Et il ne faut pas oublier les getters et les setters. Ils sont également importants, sinon rien ne fonctionne - l'action ne monte pas.Par conséquent, nous avons un fichier qui peut, en principe, être écrit manuellement. Mais personne n'aime écrire manuellement. { "hints": [], "groups": [ { "sourceType": "com.ironbank.RavenProperties", "name": "", "type": "com.ironbankRavenProperties" } ], "properties": [ { "sourceType": "com.ironbank.RavenProperties", "name": ".", "type": "java.util.List<java.lang.String>" } ] }
Adresse pour corbeau
Nous avons fait la première partie de la tâche - nous avons obtenu quelques propriétés. Mais personne n'est encore lié à ces propriétés. Maintenant, ils doivent être définis comme condition pour créer notre écouteur. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener() { return new RavenListener(); } }
Nous avons ajouté une autre condition - un corbeau ne devrait être créé qu'à la condition que quelqu'un dise où voler.Nous allons maintenant écrire où voler dans application.yml. spring: application.name: money-raven jpa.hibernate.ddl-auto: validate ironbank: ---: - : -: , : true
Reste à prescrire logiquement qu'il vole là où on lui a dit.Pour ce faire, nous pouvons générer un constructeur. Le nouveau Spring a une injection constructeur - c'est la voie recommandée. Eugène aime @Autowired
tout faire apparaître dans l'application par réflexion. J'adore suivre les conventions proposées par Spring: @RequiredArgsConstructor public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } }
Mais ce n'est pas gratuit. D'une part, vous obtenez un comportement vérifiable, d'autre part, vous obtenez des hémorroïdes. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } }
Nulle part @Aurowired
, avec Spring 4.3, vous ne pouvez pas l'installer. S'il n'y a qu'un seul constructeur, c'est bien @Aurowired
. Dans ce cas, une annotation est utilisée @RequiredArgsConstructor
, ce qui génère un seul constructeur. C'est équivalent à ce comportement: public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } }
Spring conseille d'écrire de cette façon ou d'utiliser Lombok. Jurgen Holler, qui écrit 80% du code Spring depuis 2002, vous conseille de le régler @Aurowired
pour qu'il soit visible (sinon la plupart des gens regardent et ne voient aucune injection). public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Aurowired public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } }
Comment avons-nous payé pour cette approche? Nous avons dû ajouter RavenProperties
à la configuration Java. Et si je le mettais @Aurowired
sur le terrain, rien ne devrait être changé.Ainsi, les corbeaux sont envoyés. Nous avons terminé la tâche, qui a permis aux utilisateurs de notre démarreur d'avoir des compliments dans leurs configurations, tandis que nous avons obtenu un bac qui s'allume et s'éteint en fonction de ces configurations.Loi sur le fer 1.4. Corbeau personnalisé
Il arrive que vous deviez personnaliser le comportement du démarreur. Par exemple, nous avons notre propre corbeau noir. Et nous avons besoin d'un blanc qui fume, et nous voulons l'envoyer pour que les gens voient de la fumée à l'horizon.Passons de l'allégorie à la vraie vie. Le démarreur m'a apporté un tas de haricots d'infrastructure, et c'est super. Mais je n'aime pas comment ils sont configurés. Je suis entré dans les propriétés de l'application, j'ai changé quelque chose et maintenant j'aime tout. Mais il y a des situations où les paramètres sont si compliqués qu'il est plus facile d'enregistrer la source de données vous-même que d'essayer de comprendre les propriétés de l'application. Autrement dit, nous voulons enregistrer nous-mêmes la source de données dans le bac reçu du démarreur. Que se passera-t-il alors?J'ai enregistré quelque chose moi-même et le démarreur m'a apporté ma source de données. En ai-je deux maintenant? Ou va-t-on en écraser un (et lequel?)Nous voulons vous montrer une autre condition qui permet au démarreur d'apporter une sorte de bac uniquement si la personne qui utilise le démarreur n'a pas un tel bac. Il s'est avéré que cela n'a rien de trivial.De nombreuses conditions nous ont déjà été posées: @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ...
En principe, @ConditionalOnMissingBean
il y en a aussi, alors utilisez simplement celui prêt à l'emploi. Entrons dans la configuration, où nous indiquons qu'il ne doit être créé que si personne n'a créé un tel bac auparavant. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnMissingBean</b> public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } }
Si vous ouvrez la plupart des démarreurs, vous verrez que chaque bin, chaque configuration est suspendue avec un tel ensemble d'annotations. Nous essayons juste de faire un analogue.Lorsque vous essayez de lancer le corbeau ne va pas, mais l'événement est apparu, que nous avons écrit dans notre nouvel auditeur - MyRavenListener
.
Il y a deux points importants ici.Le premier point est que nous nous sommes accrochés à notre écouteur existant et n'avons écrit aucun écouteur là-bas: @Component public class MyRavenListener implements ApplicationListener { public MyRavenListener(RavenProperties ravenProperties) { super(ravenProperties); } @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println("event = " + event); }); } }
Deuxièmement - nous l'avons fait avec l'aide du composant. Si nous l'avons fait dans une configuration Java, c'est-à-dire enregistrerait la même classe en tant que bean de configuration, rien ne fonctionnerait pour nous.Si je nettoie extends
et fais juste une sorte d'écouteur d'application, cela @ConditionalOnMissingBean
ne fonctionnera pas. Mais depuis la classe est également appelée, lorsque nous essayons de la créer, nous pouvons écrire ravenListener
- tout comme nous l'avons fait dans notre configuration. Ci-dessus, nous nous sommes concentrés sur le fait que le nom du bean dans la configuration Java sera par le nom de la méthode. Et dans ce cas, nous créons un bac appelé ravenListener
.Pourquoi avez-vous besoin de tout savoir? Avec Spring Boot, tout est généralement super, mais seulement au début. Lorsque le projet avance, un démarreur apparaît, le second, le troisième. Vous commencez à écrire certaines choses avec vos mains, car même le meilleur démarreur ne donnera pas ce dont vous avez besoin. Et les conflits de poubelles commencent. Par conséquent, c'est bien si vous avez au moins une idée générale de la façon de vous assurer qu'un bean n'est pas créé, et comment enregistrer le bean dans votre maison afin que le démarreur n'entraîne pas de conflit (ou si vous avez deux entrées qui apportent une seule et même chose la même poubelle afin qu'ils ne soient pas en conflit les uns avec les autres). Pour résoudre le conflit, j'écris mon bean, qui s'assurera que ni le premier ni le second ne sont créés.De plus, un conflit de haricots est une bonne situation parce que vous le voyez. Si nous avons spécifié les mêmes noms de bacs, nous n'aurons pas de conflit. Un bean en remplacera simplement un autre. Et vous comprendrez longtemps où est ce qui était là. Par exemple, si nous créons une sorte de dataSource @Bean
, il écrasera la dataSource existante @Bean
.Soit dit en passant, si le démarreur porte ce dont vous n'avez pas besoin, faites simplement un bac avec le même ID et c'est tout. Vrai, alors si le démarreur dans une version change le nom de la méthode, alors c'est tout, votre bac sera à nouveau deux.ConditionalOnPuzzler
Nous avons @ConditionalOnClass
, @ConditionalOnMissingBean
il peut y avoir des cours d' écriture. Par exemple, considérons la configuration d'exécution.Il y a du savon, il y a une corde - nous nous accrochons à la potence. Il y a une chaise et un courant - il est logique de mettre une personne sur une chaise. Il y a une guillotine et une bonne humeur - cela signifie que vous devez couper les têtes. @Configuration public class { @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public () { return new ("..."); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public c() { return new (" "); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public () { return new (" "); } }
Comment allons-nous exécuter?Question: Comment les annotations de type peuvent-elles @ConditionalOnMissingClass
fonctionner en général ?Supposons que j'ai une méthode qui créera une potence. Mais une poubelle ne doit être créée que s'il y a du savon et de la corde. Mais il n'y a pas de savon. Comment puis-je comprendre qu'il n'y a pas de savon ou juste de la corde. Que se passe-t-il si j'essaie de lire des annotations à partir d'une méthode et que ces annotations se réfèrent à des classes qui ne le sont pas? Puis-je prendre de telles annotations?Options de réponse:- ClassDefNotFound? , . - , ClassDefNotFound , reflection- , conditional as long as;
- , . reflection . , .
- ;
- .
: , . reflection . exception, , — . reflection? , , , , — ClassDefNotFound
.
Cela fonctionnera en utilisant ASM. Voyant cela à travers la réflexion - rien du tout, Spring analysera le bytecode, conditionnellement, manuellement. Il lit le fichier afin de ne pas télécharger prématurément ce fichier, et comprend ce qu'il y a @Conditional
avec du savon, de la corde. Il peut déjà vérifier la présence de ces classes dans leur contexte séparément. Mais l'ASM n'est pas, comme on dit, une question de vitesse. C'est l'occasion de lire une classe sans la charger et de comprendre les informations sur la méthode.Mais Juergen Hoeller recommande de ne pas être lié aux noms de classe, prescrivant des conditions, malgré le fait qu'il existe une annotation OnMissingClass
qui peut prendre le nom de classe (String) comme paramètre. Si vous suivez cette recommandation, tout fonctionne plus rapidement et l'ASM n'est pas nécessaire. Mais à en juger par la source, personne ne fait ça.Loi du fer 1.5. Allumez et éteignez le corbeau
Nous avions besoin d'une autre propriété - la possibilité d'activer ou de désactiver manuellement le corbeau. Afin de ne pas envoyer quelqu'un garanti. Ceci est la dernière condition que nous vous montrons.Notre entrée ne donne rien d'autre qu'un corbeau. Par conséquent, vous pouvez vous demander pourquoi vous pouvez l'activer / le désactiver, pouvez-vous simplement ne pas le prendre? Mais dans la deuxième partie, des choses utiles supplémentaires seront insérées dans ce démarreur. Plus précisément, un corbeau peut ne pas être nécessaire - il est cher, il peut être désactivé. En même temps, ce n'est pas très bien de retirer le dernier point où l'envoyer - cela ressemble à une béquille.Par conséquent, nous ferons tout à travers @ConditionalOnProperty(".")
. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } }
Et il nous jure que cela ne peut pas être fait: annotation en double. Le problème est que si nous avons une annotation avec certains paramètres, elle n'est pas répétable. Nous ne pouvons pas le faire sur deux propriétés.Nous avons des méthodes pour cette annotation, il y a String, et ceci est un tableau - vous pouvez spécifier plusieurs propriétés là-bas. @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { String[] value() default {}; String prefix() default ""; String[] name() default {}; String havingValue() default ""; boolean matchIfMissing() default false; boolean relaxedNames() default true; }
Et tout va bien, jusqu'à ce que vous essayiez de personnaliser la valeur de chaque élément de ce tableau séparément. Nous avons une propriété
, qui devrait être false
et est une autre propriété, qui devrait avoir string
une certaine valeur. Mais vous ne pouvez spécifier qu'une seule valeur sur toutes les propriétés.Autrement dit, vous ne pouvez pas faire ceci: @ConditionalOnProduction @ConditionalOnProperty(name = ".", havingValue="true") @ConditionalOnProperty(name = ".", havingValue="true") @ConditionalOnProperty(name = ".",havingValue="false") public IronBankApplicationListener applicationListener() { ... }
@ConditionalOnProduction @ConditionalOnProperty( name = { ".", ".", "." }, havingValue = "true" ) public IronBankApplicationListener applicationListener() { ... }
Le surligné ici n'est pas un tableau.Il existe une perversion qui vous permet de travailler avec plusieurs propriétés, cependant, avec une seule valeur pour elles: AllNestedConditions
et AnyNestedCondition
.
Cela a l'air franchement étrange. Mais ça marche. Essayons de faire une configuration - une nouvelle condition qui prendra en compte à la fois .
et .
. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnRaven public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } }
Nous mettons notre annotation @Conditional()
et ici nous devons enregistrer une classe. @Retention(RUNTIME) @Conditional({OnRavenCondional.class}) public @interface CondionalOnRaven { }
Nous le créons. public class OnRavenCondional implements Condition { }
De plus, nous avons dû implémenter une sorte de conditionnement, mais nous ne pouvons pas le faire car nous avons l'annotation suivante: public class CompositeCondition extends AllNestedConditions { @ConditionalOnProperty( name = ".", havingValue = "false") public static class OnRavenProperty { } @ConditionalOnProperty( name = ".enabled", havingValue = "true", matchIfMissing = true) public static class OnRavenEnabled { } ... }
Nous en avons un composite Conditional
, qui sera également hérité d'une autre classe - soit AllNestedConditions
, soit AnyNestedCondition
- et il contiendra d'autres classes contenant les annotations habituelles avec des condiments. C'est-à-dire
à la place, @Condition
nous devons spécifier: public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } }
Dans ce cas, vous devez créer un constructeur à l'intérieur.Maintenant, nous devons créer des classes statiques ici. Nous faisons une sorte de classe (appelons-la R). public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } public static class R {} }
Nous faisons notre valeur
(elle doit être exactement true
). public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnProperty(".") public static class R {} @ConditionalOnProperty(value= ".", havingValue = "true") public static class C {} }
Pour répéter cela, n'oubliez pas le nom de la classe. Le printemps a de bons quais Java. Vous pouvez quitter IDEA, lire le dock Java et comprendre ce qui doit être fait.Nous fixons le nôtre @ConditionalOnRaven
. En principe, vous pouvez envelopper les deux @ConditionalOnProduction
, et , et @ConditionalOnMissingBean
, mais maintenant nous ne le ferons pas. Voyez ce qui s'est passé. @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnRaven @ConditionalOnMissingBean public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } }
En l'absence d'un
corbeau ne devrait pas voler. Il n'a pas volé.Je ne veux pas parier
, car nous devons d'abord effectuer une saisie semi-automatique - c'est l'une de nos exigences. @Data @ConfigurationalProperties("") public class RavenProperties { List<String> ; boolean ; }
C’est tout.
par défaut false
, mettez true
dansapplication.yml: jpa.hibernate.ddl-auto: validate ironbank: ---: - : : , : true
Nous lançons et notre corbeau vole.Ainsi, nous pouvons faire des annotations composites et y mettre n'importe quel nombre d'annotations existantes, même si elles ne sont pas répétables. Cela fonctionne dans n'importe quel Java. Cela nous sauvera.Dans la deuxième partie de l'article, qui sortira dans les prochains jours, nous nous concentrerons sur les profils et subtilités de lancement de l'application.
Minute de publicité.
La conférence Joker 2018 aura lieu les 19 et 20 octobre, au cours de laquelle Evgeny Borisov, avec Baruch Sadogursky, fera une présentation intitulée «Les aventures de Senor Holmes et Junior Watson dans le monde du développement logiciel [Édition Joker]» , et Kirill Tolkachev et Maxim Gorelikov présenteront le rapport «Micronaut vs Spring Boot Ou qui est le plus petit ici? " . En général, il y aura de nombreux rapports plus intéressants et dignes de mention chez Joker. Les billets peuvent être achetés sur le
site officiel de la conférence.
Et nous avons aussi une petite enquête pour vous!