BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Articles Devenez Natif Avec Spring Boot Et GraalVM

Devenez Natif Avec Spring Boot Et GraalVM

Points Clés

  • Spring Boot 3 et Spring Framework 6, attendus fin 2022, auront un support intégré pour Java natif.
  • Pour les utilisateurs de Spring Framework 5.x et Spring Boot 2.x, Spring Native est la solution.
  • Spring Native fournit des intégrations pour un vaste écosystème de bibliothèques.
  • Mais Spring Native fournit également un modèle de composant qui vous permet d'étendre la prise en charge de la compilation native pour d'autres bibliothèques.
  • La compilation GraalVM AOT offre de nombreuses possibilités avec des coûts (négociables).

Cet article d'InfoQ fait partie de la série "Les compilations natives boostent Java". Vous pouvez vous abonner pour recevoir des notifications via RSS.

Java domine les applications d'entreprise. Mais dans le cloud, Java est plus cher que certains concurrents. La compilation native rend Java dans le cloud moins cher : elle crée des applications qui démarrent beaucoup plus rapidement et utilisent moins de mémoire.

La compilation native soulève donc de nombreuses questions pour tous les utilisateurs de Java : comment Java natif change-t-il le développement ? Quand faut-il passer au Java natif ? Quand ne devrions-nous pas ? Et quel framework devrions-nous utiliser pour Java natif ? Cette série apportera des réponses à ces questions.

 

La communauté Java est très hétérogène

Vous savez, Java est un écosystème fantastique, et il est difficile d'énumérer toutes les choses pour lesquelles il est parfaitement adapté. La liste semble interminable. Mais il n'est pas non plus si difficile de nommer au moins quelques-unes de ses verrues. Comme les articles de cette série l'ont montré, les applications exécutées sur le JRE ont souvent besoin de dix secondes ou plus pour démarrer et prendre des centaines ou - arfff ! - des milliers de mégaoctets de RAM.

Cette performance n'est pas la meilleure solution dans le monde d'aujourd'hui. Il y a de nouvelles frontières, de nouvelles opportunités : les offres de Function-as-a-service. Conteneurisation et orchestrateurs de conteneurs. Ils ont une chose en commun : la vitesse de démarrage et l'empreinte mémoire sont importantes.

Allez, allez, GraalVM !

GraalVM offre une voie à suivre, avec certains coûts. GraalVM est un remplacement d'OpenJDK avec un utilitaire supplémentaire (appelé Native Image) qui prend en charge la compilation ahead-of-time (AOT).

La compilation AOT est un peu différente de la compilation Java classique. Comme le dit si succinctement le premier article de cette série, Native Image "élimine tout ce qui est inutile" dans votre application Java. Alors, comment Native Image sait-il ce qui est inutile dans Java ou Spring Boot ?

Native Image examine votre code source et détermine tout le code accessible — que vous pouvez lier par invocation ou utilisation à votre code. Tout le reste, que ce soit dans le classpath de votre application ou dans le JRE, est inutile et jeté.

Le problème survient lorsque vous faites quelque chose que Native Image ne peut pas suivre de manière déterministe. Après tout, Java est un langage très dynamique. Il est possible de créer une application Java qui, au moment de l'exécution, compile une chaîne dans un fichier de classe Java valide sur le système de fichiers, la charge dans le ClassLoader, puis en crée une instance de manière réflexive ou en crée un proxy. Il est possible de sérialiser cette instance sur le disque, puis de la charger dans une autre JVM. Il est possible de faire tout cela sans jamais lier à un type concret plus spécifique que java.lang.Object ! Mais tout cela ne fonctionnera pas en Java natif si les types ne sont pas dans le heap exécutable natif.

Cependant, tout n'est pas perdu. Vous pouvez indiquer à Native Image quels types conserver dans un fichier de configuration afin que, si vous décidez de faire des choses comme la réflexion, les proxies, le chargement des ressources du classpath, JNI, etc., au moment de l'exécution, cela fonctionne toujours.

Désormais, les écosystèmes Java et Spring sont vastes. Tout configurer pourrait être très pénible ! Il y a donc deux options : 1) apprendre à Spring à éviter certains de ces mécanismes dans la mesure du possible, ou 2) apprendre à Spring à fournir le fichier de configuration autant que possible. Ce fichier de configuration inclurait nécessairement Spring Framework et Spring Boot et, dans une certaine mesure, les intégrations tierces prises en charge par Spring Boot. Alerte spoiler : Nous avons besoin des deux options !

Vous avez besoin de GraalVM en cours d'exécution sur votre machine pour les exemples de projets. Le site Web GraalVM contient les instructions d'installation. Si vous avez un Mac, vous pouvez également installer GraalVM avec SDKMAN !

Spring Native

L'équipe Spring a lancé le projet Spring Native en 2019 qui introduit la compilation exécutable native dans l'écosystème Spring Boot. Il a servi de projet de recherche pour plusieurs approches différentes. Mais Spring Native ne change pas radicalement Spring Framework 5.x ou Spring Boot 2.x. Et ce n'est pas la fin, simplement la première étape d'un voyage plus long : il a prouvé de nombreux concepts pour les prochaines générations de Spring Framework (6.x) et Spring Boot (3.x), tous deux attendus plus tard en 2022. Ces nouvelles générations peuvent faire plus d'optimisations, donc l'avenir s'annonce radieux ! Comme ces versions ne sont pas encore sorties, nous examinerons Spring Native dans cet article.

Spring Native transforme le code source envoyé à Native Image. Par exemple, Spring Native transforme le mécanisme de type service-loader spring.factories en classes statiques que l'application Spring Native résultante sait consulter à la place. Il transpile également toutes vos classes de configuration Java (celles annotées avec @Configuration) dans la configuration fonctionnelle de Spring, éliminant ainsi des pans entiers de réflexion pour votre application et ses dépendances.

Spring Native analyse également automatiquement votre code, détecte les scénarios qui nécessiteront une configuration GraalVM et le fournit programmatiquement. Spring Native est livré avec des classes d'indications pour Spring & Spring Boot et des intégrations tierces.

Votre première application Spring Native : JPA, Spring MVC et H2

Vous démarrez avec Spring Native de la même manière qu'avec tout ce qui concerne Spring : accédez à Spring Initializr. Appuyez sur cmd + B ou Ctrl + B ou cliquez sur Add Dependencies et sélectionnez Spring Native.

Spring Initializr configure des builds Apache Maven et Gradle. Ensuite, ajoutez simplement les dépendances requises. Commençons par quelque chose de typique. Remplacez le nom de l'artefact par jpa. Ensuite, ajoutez les dépendances suivantes : Spring Native, Spring Web, Lombok, H2 Database et Spring Data JPA. Assurez-vous de spécifier Java 17. Vous pouvez utiliser Java 11, tout comme vous pouvez tourner en rond en brandissant un poulet en caoutchouc. Mais alors vous auriez l'air idiot, n'est-ce pas ? Cliquez sur Generate. Décompressez le projet et importez le projet dans votre IDE préféré.

Cet exemple est un exemple trivial mais typique. Modifiez la classe JpaApplication.java pour qu'elle ressemble à ceci :

package com.example.jpa;

import lombok.*;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;
import javax.persistence.*;
import java.util.Collection;
import java.util.stream.Stream;


@SpringBootApplication
public class JpaApplication {

    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }
}

@Component
record Initializr(CustomerRepository repository)
       implements ApplicationRunner {

   @Override
   public void run(ApplicationArguments args) throws Exception {
       Stream.of("A", "B", "C", "D")
               .map(c -> new Customer(null, c))
               .map(this.repository::save)
               .forEach(System.out::println);
   }
}

@RestController
record CustomerRestController(CustomerRepository repository) {

    @GetMapping("/customers")
    Collection<Customer> customers() {
        return this.repository.findAll();
    }
}

interface CustomerRepository extends JpaRepository<Customer, Integer> {
}

@Entity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Table (name = "customer")
class Customer {
    @Id
    @GeneratedValue
    private Integer id;
    private String name;
}

Il est également possible de compiler et d'exécuter vos tests en tant qu'exécutable natif. Remarquez que certaines choses, comme Mockito, ne fonctionnent pas encore particulièrement bien pour le moment. Modifiez le test, JpaApplicationTests.java, pour qu'il ressemble à ceci :

package com.example.jpa;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;

@SpringBootTest
class JpaApplicationTests {

    private final CustomerRepository customerRepository;

    @Autowired
    JpaApplicationTests(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    @Test
    void contextLoads() {
        var size = this.customerRepository.findAll().size();
        Assert.isTrue(size > 0, () -> "there should be more than one result!");
    }

}

Je vais montrer les commandes pour macOS dans cet article. Veuillez les ajuster pour Windows et Linux de manière appropriée.

Vous pouvez exécuter l'application et les tests sur le JRE de la manière habituelle, avec mvn spring-boot:run depuis un terminal. C'est une bonne idée d'exécuter les exemples pour s'assurer que l'application fonctionne. Mais ce n'est pas la raison pour laquelle nous sommes ici. Au lieu de cela, nous voulons compiler l'application et ses tests dans une application native GraalVM.

Si vous avez inspecté le pom.xml, vous aurez déjà remarqué la configuration supplémentaire étendue qui configure GraalVM Native Image et ajoute un profil Maven (appelé native) pour prendre en charge la création d'exécutables natifs. Vous pouvez compiler l'application comme d'habitude avec mvn clean package. Compilez l'application nativement avec mvn -Pnative clean package. N'oubliez pas que vous devez avoir défini GraalVM comme JDK à ce moment ! Ce processus prendra plusieurs minutes, alors c'est le bon moment pour prendre une tasse de thé ou de café ou d'eau ou autre. Je l'ai fait. J'en avais besoin. Quand je suis revenu, j'ai vu ceci dans la sortie :

...
13.9s (16.9% of total time) in 71 GCs | Peak RSS: 10.25GB | CPU load: 5.66
...
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  03:00 min
[INFO] Finished at: 2022-04-28T17:57:56-07:00
[INFO] ------------------------------------------------------------------------

Wahou! Trois minutes pour compiler les tests natifs puis, s'ils réussissent, l'application native elle-même. Native Image a utilisé jusqu'à 10,25 Go de RAM pendant le processus. Pour faire avancer les choses plus rapidement, je vais ignorer la compilation et l'exécution des tests dans cet article. Ainsi, lorsque nous compilons les exemples suivants, utilisez :

mvn -Pnative -DskipTests clean package

Les temps de compilation varient en fonction du classpath de l'application. Pour l'anecdote, la plupart de mes builds prennent entre une minute et 90 secondes si je saute la compilation des tests. Par exemple, cette application qui inclut JPA (et Hibernate), Spring Data, la base de données H2, Apache Tomcat et Spring MVC.

Exécutez l'application :

./target/jpa 

Sur ma machine, je vois :

…Started TraditionalApplication in 0.08 seconds (JVM running for 0.082)

Pas mal! 80 millisecondes, soit 80 millièmes de seconde ! Encore mieux, cette application ne prend presque rien en mémoire. J'utilise le script suivant pour mesurer le RSS (resident set size) de l'application.

#!/usr/bin/env bash  
PID=$1
RSS=`ps -o rss ${PID} | tail -n1`
RSS=`bc <<< "scale=1; ${RSS}/1024"`
echo "RSS memory (PID: ${PID}): ${RSS}M"

Vous aurez besoin de l'ID de processus (PID) de l'application en cours d'exécution. Je peux l'obtenir sur macOS en exécutant pgrep jpa. J'utilise le script comme ceci :

~/bin/RSS.sh $(pgrep jpa)
RSS memory (PID: 35634): 96.9M

Près de 97 mégaoctets de RAM ! Ces chiffres peuvent varier en fonction du système d'exploitation et de l'architecture que vous utilisez. Les chiffres sur Linux sur Intel seront différents de ceux de macOS sur M1. Une nette amélioration par rapport à n'importe quelle application JRE, certes, mais pas la meilleure que nous puissions obtenir.

J'adore la programmation réactive, et je pense que c'est beaucoup mieux adapté à mes charges de travail aujourd'hui. J'ai écrit une application réactive analogue. Cela a non seulement pris beaucoup moins d'espace (pour de nombreuses raisons, notamment le fait que Spring Data R2DBC prend en charge la glorieuse syntaxe des records de Java 17), mais l'application a été compilée en 1:14 (presque deux minutes plus vite !) et a démarré en 0,044 seconde. Et il faut 35% de RAM en moins, soit 63,5 mégaoctets. Beaucoup mieux. Cette application traitera également plus de requêtes par seconde. Il est donc plus rapide à compiler et à exécuter, plus économe en mémoire, plus rapide à démarrer et gère plus de trafic. Une bonne affaire globalement, je dirais.

Une application d'intégration

Spring est bien plus que de simples endpoints HTTP. D'innombrables autres frameworks, y compris Spring Batch, Spring Integration, Spring Security, Spring Cloud et une liste sans cesse croissante d'autres, offrent tous un bon support de Spring Native.

Examinons rapidement un exemple d'application Spring Integration. Spring Integration est un framework qui prend en charge l'intégration d'applications d'entreprise ( enterprise-application integration ou EAI). Le livre fondateur de Gregor Hohpe et Bobby Woolf Enterprise Integration Patterns a donné au monde la lingua franca des modèles d'intégration. Et Spring Integration lui a donné l'abstraction dans laquelle implémenter ces modèles.

Accédez à Spring Initializr, nommez le projet integration, choisissez Java 17, ajoutez Spring Native, Spring Integration, Spring Web, et cliquez sur Generate. Vous devrez ajouter manuellement une dépendance à votre pom.xml :

<dependency>
  <groupId>org.springframework.integration</groupId>
  <artifactId>spring-integration-file</artifactId>
  <version>${spring-integration.version}</version>
</dependency>

Modifiez le code de IntegrationApplication.java :

package com.example.integration;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.dsl.IntegrationFlow;
import org.springframework.integration.dsl.IntegrationFlows;
import org.springframework.integration.file.dsl.Files;
import org.springframework.integration.file.transformer.FileToStringTransformer;
import org.springframework.integration.transformer.GenericTransformer;

import java.io.File;

@SpringBootApplication
public class IntegrationApplication {

    @Bean
    IntegrationFlow integration(@Value("file://${user.home}/Desktop/integration") File root) {
        var in = new File(root, "in");
        var out = new File(root, "out");
        var inboundFileAdapter = Files
                .inboundAdapter(in)
                .autoCreateDirectory(true)
                .get();
        var outboundFileAdapter = Files
                .outboundAdapter(out)
                .autoCreateDirectory(true)
                .get();
        return IntegrationFlows //
                .from(inboundFileAdapter, spec -> spec.poller(pm -> pm.fixedRate(1000)))//
                .transform(new FileToStringTransformer())
                .transform((GenericTransformer<String, String>) source -> new StringBuilder(source)
                        .reverse()
                        .toString()
                        .trim())
                .handle(outboundFileAdapter)
                .get();
    }

    public static void main(String[] args) {
        SpringApplication.run(IntegrationApplication.class, args);
    } 
} 

Cette application est triviale : elle surveille un répertoire ($HOME/Desktop/integration/in) pour tout nouveau fichier. Dès qu'il en voit, il crée une copie avec son contenu String inversé et l'écrit dans $HOME/Desktop/integration/out. L'application démarre en 0.429 seconde sur le JRE ! C'est plutôt bien, mais voyons ce que cela nous rapporte d'en faire un exécutable natif GraalVM.

mvn -Pnative -DskipTests clean package 

L'application a été compilée en 55.643 secondes ! Elle a démarré (./target/integration) en 0,029 seconde et utilise 35,5 Mo de RAM. Pas mal!

Comme vous pouvez le voir, il n'y a pas de résultat typique. Ce que vous introduisez dans le processus de compilation compte beaucoup pour déterminer ce qui en sortira.

Mettre l'application en production

Nous voudrons mettre ces applications en production à un moment donné, et de nos jours, la production c'est Kubernetes. Kubernetes fonctionne en termes de conteneurs. Il nous faut un conteneur. Le concept de base derrière le projet Buildpacks est de centraliser et de réutiliser les formules pour transformer les artefacts d'application en conteneurs. Vous pouvez utiliser les Buildpacks via l'interface en ligne de commande du pack, depuis votre cluster Kubernetes avec le KPack, ou via les plug-ins de génération Spring Boot. Nous utiliserons cette dernière option car elle ne nécessite rien de plus que Docker Desktop. Veuillez vous référer ici pour Docker Desktop.

mvn spring-boot:build-image 

Cette commande construira l'exécutable natif à l'intérieur du conteneur, vous obtiendrez donc des binaires Linux natifs dans un conteneur Linux. Vous pouvez ensuite utiliser docker tag et docker push vers le registre de conteneurs de votre choix. Au moment où j'écris ceci en mai 2022, les Docker Buildpacks sur les Mac M1 sont encore un peu fragiles. Mais je suis sûr que ça va bientôt s'arranger !

Donner des indications à l'image native

Jusqu'à présent, dans les exemples, vous n'aviez rien à faire pour que l'application fonctionne comme un exécutable natif. Cela a juste fonctionné. Cette facilité d'utilisation est le genre de résultat auquel vous vous attendez la plupart du temps. Mais parfois, il faut donner des indications à Native Image, comme je l'ai déjà mentionné dans la partie "Allez, allez, GraalVM!" au début.

Prenons un autre exemple. Tout d'abord, accédez au Spring Initializr, nommez le projet extensions, choisissez Java 17, ajoutez Spring Native et cliquez sur Generate. Ensuite, nous ajouterons manuellement une dépendance qui n'est pas sur Initialzr :

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-json</artifactId>
</dependency>

Le but ici est de regarder ce qui se passe quand les choses tournent mal. Spring Native fournit un ensemble d'indications qui facilitent l'augmentation de la configuration par défaut. Remplacez ExtensionsApplication.java par ce qui suit :

package com.example.extensions;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aopalliance.intercept.MethodInterceptor;
import org.springframework.aop.framework.ProxyFactoryBean;

import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.io.ClassPathResource;
import org.springframework.nativex.hint.*;
import org.springframework.stereotype.Component;
import org.springframework.util.*;

import java.io.InputStreamReader;
import java.util.List;
import java.util.function.Supplier;

@SpringBootApplication
public class ExtensionsApplication {
    public static void main(String[] args) {
        SpringApplication.run(ExtensionsApplication.class, args);
    }
}

@Component
class ReflectionRunner implements ApplicationRunner {
    
    private final ObjectMapper objectMapper ;

    ReflectionRunner(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    record Customer(Integer id, String name) {
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        var json = """
                [
                 { "id" : 2, "name": "Dr. Syer"} ,
                 { "id" : 1, "name": "Jürgen"} ,
                 { "id" : 4, "name": "Olga"} ,
                 { "id" : 3, "name": "Violetta"}  
                ]
                """;
        var result = this.objectMapper.readValue(json, new TypeReference<List<Customer>>() {
        });
        System.out.println("there are " + result.size() + " customers.");
        result.forEach(System.out::println);
    }
}

@Component
class ResourceRunner implements ApplicationRunner {

    @Override
    public void run(ApplicationArguments args) throws Exception {
        var resource = new ClassPathResource("Log4j-charsets.properties");
        Assert.isTrue(resource.exists(), () -> "the file must exist");
        try (var in = new InputStreamReader(resource.getInputStream())) {
            var contents = FileCopyUtils.copyToString(in);
            System.out.println(contents.substring(0, 100) + System.lineSeparator() + "...");
        }
    }
}

@Component
class ProxyRunner implements ApplicationRunner {

    private static Animal buildAnimalProxy(Supplier<String> greetings) {
        var pfb = new ProxyFactoryBean();
        pfb.addInterface(Animal.class);
        pfb.addAdvice((MethodInterceptor) invocation -> {
            if (invocation.getMethod().getName().equals("speak"))
                System.out.println(greetings.get());

            return null;
        });
        return (Animal) pfb.getObject();
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        var cat = buildAnimalProxy(() -> "meow!");
        cat.speak();

        var dog = buildAnimalProxy(() -> "woof!");
        dog.speak();
    }

    interface Animal {
        void speak();
    }
}

L'exemple comprend trois instances d'ApplicationRunner que Spring exécute au démarrage de l'application. Chaque bean fait quelque chose qui va embéter GraalVM Native Image. Mais cela fonctionne très bien sur la JVM : mvn spring-boot:run

Le premier ApplicationRunner, ReflectionRunner, lit les données JSON et mappe de manière réflexive la structure sur une classe Java, Customer. Cela ne fonctionnera pas car Native Image supprimera la classe Customer ! Compilez-le avec mvn -Pnative -DskipTests clean package et exécutez ./target/extensions. Ensuite, vous verrez vous-même l'erreur "com.oracle.svm.core.jdk.UnsupportedFeatureError : Proxy class defined by interfaces".

Vous pouvez utiliser l'annotation @TypeHint pour résoudre ce problème. Ajoutez ce qui suit à la classe ExtensionsApplication :

@TypeHint(types =  ReflectionRunner.Customer.class, access = { TypeAccess.DECLARED_CONSTRUCTORS, TypeAccess.DECLARED_METHODS })

Ici, nous disons que nous voulons un accès réflexif sur les constructeurs et les méthodes de ReflectionRunner.Customer. Il existe d'autres valeurs TypeAccess pour différents types de réflexion.

Le second ApplicationRunner, ResourceRunner, charge un fichier à partir du .jar de l'une des dépendances sur le classpath. Cela ne fonctionnera pas et vous donnera une erreur "java.lang.IllegalArgumentException : the file must exist" ! La raison en est que cette ressource réside dans un autre .jar, pas dans le code de notre application. Le chargement de cette ressource fonctionnerait si elle était dans src/main/resources. Vous pouvez utiliser l'annotation @ResourceHint pour le faire fonctionner. Ajoutez ce qui suit à la classe ExtensionsApplication :

@ResourceHint(patterns = "Log4j-charsets.properties", isBundle = false)

Le troisième ApplicationRunner, ProxyRunner, crée un proxy grâce au JDK. Les proxys créent des sous-classes ou des implémentations de types. Spring en connaît deux types : les proxys JDK et les proxys AOT. Les proxys JDK sont limités aux interfaces en utilisant java.lang.reflect.Proxy de Java. Les proxys AOT sont un Spring-isme, ne font pas partie du JRE. Ces proxys JDK sont généralement des sous-classes d'une classe concrète donnée et peuvent inclure des interfaces. Native Image a besoin de savoir quelles interfaces et classes concrètes votre proxy utilisera.

Allez-y et compilez la troisième application dans un exécutable natif. Native Image vous donnera à nouveau le message d'erreur convivial "com.oracle.svm.core.jdk.UnsupportedFeatureError : Proxy class defined by interfaces" et répertoriera toutes les interfaces que Spring essaie de proxyfier. Notez ces types : com.example.extensions.ProxyRunner.Animal, org.springframework.aop.SpringProxy, org.springframework.aop.framework.Advised et org.springframework.core.DecoratingProxy . Nous les utiliserons pour créer le hint suivant pour la classe ExtensionsApplication :

@JdkProxyHint(types = {
    com.example.extensions.ProxyRunner.Animal.class,
    org.springframework.aop.SpringProxy.class,
    org.springframework.aop.framework.Advised.class,
    org.springframework.core.DecoratingProxy.class
})

Tout devrait fonctionner parfaitement maintenant si vous construisez et exécutez l'exemple : mvn -DskipTests -Pnative clean package et exécutez ./target/extensions.

Processors à la construction et d'exécution

Spring a beaucoup d'implémentations de Processor. Spring Native apporte de nouvelles interfaces Processor qui ne sont actives qu'au moment de la construction. Elles fournissent dynamiquement des indications à la construction. Idéalement, ces implémentations de processor vivront dans une bibliothèque réutilisable. Accédez à Spring Initializr, nommez le projet processors et ajoutez Spring Native. Ouvrez le projet généré dans votre IDE et supprimez toute la configuration du plugin Maven en supprimant le nœud build de votre pom.xml. Vous devrez ajouter manuellement une nouvelle bibliothèque :

 <dependency>
  <groupId>org.springframework.experimental</groupId>
  <artifactId>spring-aot</artifactId>
  <version>${spring-native.version}</version>
  <scope>provided</scope>
</dependency>

Ce build Maven produit un artefact Java ".jar" standard. Vous pouvez l'installer et le déployer comme vous le feriez avec n'importe quel `.jar` Maven : mvn -DskipTests clean install 

Cette nouvelle bibliothèque introduit de nouveaux types, notamment :

  • BeanFactoryNativeConfigurationProcessor : utilisé lorsque vous voulez un build time équivalent à BeanFactoryPostProcessor
  • BeanNativeConfigurationProcessor : utilisé lorsque vous voulez un build time équivalent à BeanPostProcessor

Je me retrouve à travailler dans ces deux interfaces la plupart du temps. Dans chaque interface, vous recevez une référence à un élément que vous pouvez inspecter et une référence à un registre que vous pouvez utiliser pour contribuer par programmation à des indications. Si vous utilisez le BeanNativeConfigurationProcessor, vous recevez une instance de métadonnées de bean pour un bean dans la fabrique de beans. Si vous utilisez BeanFactoryNativeConfigurationProcessor, vous recevez une référence à l'ensemble du potentiel BeanFactory lui-même. Attention : vous devez veiller à ne travailler qu'avec des noms de bean et des instances de BeanDefinition, jamais avec les beans eux-mêmes. La BeanFactory connaît tous les objets qui existeront au moment de l'exécution, mais elle ne les instancie pas. Au lieu de cela, il est là pour nous aider à comprendre la forme - les classes, les méthodes, etc. - de l'application en cours d'exécution pour en déduire les indications appropriés.

Vous enregistrez ces types Processor non pas en tant que beans Spring classiques, mais dans le service loader spring.factories. Donc, étant donné une implémentation de BeanFactoryNativeConfigurationProcessor appelée com.example.nativex.MyBeanFactoryNativeConfigurationProcessor, et une implémentation de BeanNativeConfigurationProcessor appelée com.example.nativex.MyBeanNativeConfigurationProcessor, le fichier spring.factories pourrait ressembler à ceci :

org.springframework.aot.context.bootstrap.generator.infrastructure.nativex.BeanFactoryNativeConfigurationProcessor=\
  com.example.nativex.MyBeanFactoryNativeConfigurationProcessor
org.springframework.aot.context.bootstrap.generator.infrastructure.nativex.BeanNativeConfigurationProcessor=\
  com.example.nativex.MyBeanNativeConfigurationProcessor

Ces types de Processor facilitent l'utilisation de votre intégration ou de votre bibliothèque dans leurs applications Spring Native. J'ai écrit une bibliothèque (com.joshlong:hints :0.0.1) plein d'intégrations diverses (client Java Kubernetes, le client Java Fabric8 Kubernetes, Spring GraphQL, Liquibase, etc.) qui ne correspondent pas tout à fait à la version officielle de Spring Native. C'est un méli-mélo pour le moment, mais le résultat est sympa : ajoutez simplement des éléments à votre classpath, un peu comme la configuration automatique de Spring Boot, et vous obtenez un résultat impressionnant !

L'étape suivante

J'espère que vous avez tiré quelque chose de cette brève introduction aux exécutables natifs avec Spring Native. Restez à l'écoute du blog Spring et de mon Twitter (@starbuxman) pour en savoir plus !

 

Cet article d'InfoQ fait partie de la série "Les compilations natives boostent Java". Vous pouvez vous abonner pour recevoir des notifications via RSS.

Java domine les applications d'entreprise. Mais dans le cloud, Java est plus cher que certains concurrents. La compilation native rend Java dans le cloud moins cher : elle crée des applications qui démarrent beaucoup plus rapidement et utilisent moins de mémoire.

La compilation native soulève donc de nombreuses questions pour tous les utilisateurs de Java : comment Java natif change-t-il le développement ? Quand faut-il passer au Java natif ? Quand ne devrions-nous pas ? Et quel framework devrions-nous utiliser pour Java natif ? Cette série apportera des réponses à ces questions.

 

Au sujet de l’Auteur

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT