Introduction au printemps avec Akka

Introduction au printemps avec Akka

1. introduction

Dans cet article, nous nous concentrerons sur l'intégration d'Akka avec Spring Framework - pour permettre l'injection de services basés sur Spring dans les acteurs Akka.

Avant de lire cet article, une connaissance préalable des bases d’Akka est recommandée.

Lectures complémentaires:

Introduction aux acteurs Akka en Java

Apprenez à créer des applications simultanées et distribuées à l'aide d'Akka Actors en Java.

Read more

Guide des cours d'eau Akka

Guide rapide et pratique sur les transformations de flux de données en Java à l'aide de la bibliothèque Akka Streams.

Read more

Botte de printemps et Kotlin

Apprenez à utiliser Kotlin avec Spring Boot 2.x.

Read more

2. Injection de dépendance à Akka

Akka est un framework d'application puissant basé sur le modèle de concurrence Actor. Le framework est écrit en Scala, ce qui le rend bien sûr pleinement utilisable dans les applications basées sur Java. Et doncit’s very often we will want to integrate Akka with an existing Spring-based application ou utilisez simplement Spring pour câbler les beans en acteurs.

Le problème avec l'intégration Spring / Akka réside dans la différence entre la gestion des beans dans Spring et la gestion des acteurs dans Akka:actors have a specific lifecycle that differs from typical Spring bean lifecycle.

De plus, les acteurs sont divisés en un acteur lui-même (ce qui est un détail d'implémentation interne et ne peut pas être géré par Spring) et une référence d'acteur, accessible par un code client, ainsi que sérialisable et portable entre différentes exécutions d'Akka.

Heureusement, Akka fournit un mécanisme, à savoirAkka extensions, qui rend l'utilisation de frameworks d'injection de dépendances externes une tâche assez facile.

3. Dépendances Maven

Pour démontrer l'utilisation d'Akka dans notre projet Spring, nous aurons besoin d'une dépendance Spring minimum - la bibliothèquespring-context, ainsi que la bibliothèqueakka-actor. Les versions de la bibliothèque peuvent être extraites dans la section<properties> despom:


    4.3.1.RELEASE
    2.4.8



    
        org.springframework
        spring-context
        ${spring.version}
    

    
        com.typesafe.akka
        akka-actor_2.11
        ${akka.version}
    

Assurez-vous de vérifier Maven Central pour les dernières versions des dépendancesspring-context etakka-actor.

Et remarquez comment, que la dépendanceakka-actor a un suffixe_2.11 dans son nom, ce qui signifie que cette version du framework Akka a été construite contre la version 2.11 de Scala. La version correspondante de la bibliothèque Scala sera incluse de manière transitoire dans votre construction.

4. Injecter des haricots printaniers dans des acteurs Akka

Créons une application Spring / Akka simple composée d'un seul acteur qui peut répondre au nom d'une personne en lui adressant une salutation. La logique d’accueil sera extraite vers un service séparé. Nous voudrons autoriser ce service à une instance d'acteur. L'intégration printanière nous aidera dans cette tâche.

4.1. Définir un acteur et un service

Pour illustrer l'injection d'un service dans un acteur, nous allons créer une classe simpleGreetingActor définie comme un acteur non typé (étendant la classe de base de l'AkkaUntypedActor). La méthode principale de chaque acteur Akka est la méthodeonReceive qui reçoit un message et le traite selon une logique spécifiée.

Dans notre cas, l'implémentationGreetingActor vérifie si le message est d'un type prédéfiniGreet, puis prend le nom de la personne de l'instanceGreet, puis utilise lesGreetingService pour recevoir un message d'accueil pour cette personne et répond à l'expéditeur avec la chaîne de message d'accueil reçue. Si le message est d’un autre type inconnu, il est transmis à la méthodeunhandled prédéfinie de l’acteur.

Regardons:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class GreetingActor extends UntypedActor {

    private GreetingService greetingService;

    // constructor

    @Override
    public void onReceive(Object message) throws Throwable {
        if (message instanceof Greet) {
            String name = ((Greet) message).getName();
            getSender().tell(greetingService.greet(name), getSelf());
        } else {
            unhandled(message);
        }
    }

    public static class Greet {

        private String name;

        // standard constructors/getters

    }
}

Notez que le type de messageGreet est défini comme une classe interne statique à l'intérieur de cet acteur, ce qui est considéré comme une bonne pratique. Les types de message acceptés doivent être définis aussi près que possible d'un acteur afin d'éviter toute confusion quant aux types de message que cet acteur peut traiter.

Also notice the Spring annotations @Component and @Scope - ceux-ci définissent la classe comme un bean géré par Spring avec la portéeprototype.

La portée est très importante, car chaque demande de récupération de bean doit aboutir à une instance nouvellement créée, car ce comportement correspond au cycle de vie de l'acteur d'Akka. Si vous implémentez ce bean avec une autre portée, le cas typique du redémarrage des acteurs dans Akka fonctionnera probablement de manière incorrecte.

Enfin, notez que nous n'avons pas eu à explicitement@Autowire l'instanceGreetingService - cela est possible grâce à la nouvelle fonctionnalité de Spring 4.3 appeléeImplicit Constructor Injection.

L'implémentation deGreeterService est assez simple, notez que nous l'avons défini comme un bean géré par Spring en y ajoutant l'annotation@Component (avec la portée par défautsingleton):

@Component
public class GreetingService {

    public String greet(String name) {
        return "Hello, " + name;
    }
}

4.2. Ajout de Spring Support via l'extension Akka

Le moyen le plus simple d'intégrer Spring à Akka consiste à utiliser une extension Akka.

An extension is a singleton instance created per actor system. Il se compose d'une classe d'extension elle-même, qui implémente l'interface de marqueurExtension, et d'une classe d'id d'extension qui hérite généralement deAbstractExtensionId.

Comme ces deux classes sont étroitement couplées, il est logique d'implémenter la classeExtension imbriquée dans la classeExtensionId:

public class SpringExtension
  extends AbstractExtensionId {

    public static final SpringExtension SPRING_EXTENSION_PROVIDER
      = new SpringExtension();

    @Override
    public SpringExt createExtension(ExtendedActorSystem system) {
        return new SpringExt();
    }

    public static class SpringExt implements Extension {
        private volatile ApplicationContext applicationContext;

        public void initialize(ApplicationContext applicationContext) {
            this.applicationContext = applicationContext;
        }

        public Props props(String actorBeanName) {
            return Props.create(
              SpringActorProducer.class, applicationContext, actorBeanName);
        }
    }
}

First -SpringExtension implémente une seule méthodecreateExtension de la classeAbstractExtensionId - qui tient compte de la création d'une instance d'extension, l'objetSpringExt.

La classeSpringExtension a également un champ statiqueSPRING_EXTENSION_PROVIDER qui contient une référence à sa seule instance. Il est souvent judicieux d’ajouter un constructeur privé pour déclarer explicitement queSpringExtention est censé être une classe singleton, mais nous l’omettons pour plus de clarté.

Secondly, la classe interne statiqueSpringExt est l'extension elle-même. CommeExtension est simplement une interface de marqueur, nous pouvons définir le contenu de cette classe comme bon nous semble.

Dans notre cas, nous allons avoir besoin de la méthodeinitialize pour conserver une instance SpringApplicationContext - cette méthode ne sera appelée qu'une seule fois par initialisation d'extension.

Nous aurons également besoin de la méthodeprops pour créer un objetProps. L'instance deProps est un modèle pour un acteur, et dans notre cas, la méthodeProps.create reçoit une classeSpringActorProducer et des arguments de constructeur pour cette classe. Ce sont les arguments avec lesquels le constructeur de cette classe sera appelé.

La méthodeprops sera exécutée chaque fois que nous aurons besoin d'une référence d'acteur gérée par Spring.

The third et la dernière pièce du puzzle est la classeSpringActorProducer. Il implémente l’interfaceIndirectActorProducer d’Akka qui permet de surcharger le processus d’instanciation d’un acteur en implémentant les méthodesproduce etactorClass.

Comme vous l'avez probablement déjà deviné,instead of direct instantiation, it will always retrieve an actor instance from the Spring’s ApplicationContext. Comme nous avons fait de l'acteur un bean de portéeprototype, chaque appel à la méthodeproduce renverra une nouvelle instance de l'acteur:

public class SpringActorProducer implements IndirectActorProducer {

    private ApplicationContext applicationContext;

    private String beanActorName;

    public SpringActorProducer(ApplicationContext applicationContext,
      String beanActorName) {
        this.applicationContext = applicationContext;
        this.beanActorName = beanActorName;
    }

    @Override
    public Actor produce() {
        return (Actor) applicationContext.getBean(beanActorName);
    }

    @Override
    public Class actorClass() {
        return (Class) applicationContext
          .getType(beanActorName);
    }
}

4.3. Mettre tous ensemble

La seule chose qui reste à faire est de créer une classe de configuration Spring (marquée de l'annotation@Configuration) qui indiquera à Spring d'analyser le package actuel avec tous les packages imbriqués (ceci est assuré par l'annotation@ComponentScan ) et créez un conteneur Spring.

Il suffit d'ajouter un seul bean supplémentaire - l'instanceActorSystem - et d'initialiser l'extension Spring sur ceActorSystem:

@Configuration
@ComponentScan
public class AppConfiguration {

    @Autowired
    private ApplicationContext applicationContext;

    @Bean
    public ActorSystem actorSystem() {
        ActorSystem system = ActorSystem.create("akka-spring-demo");
        SPRING_EXTENSION_PROVIDER.get(system)
          .initialize(applicationContext);
        return system;
    }
}

4.4. Récupération des acteurs à ressort

Pour tester que tout fonctionne correctement, nous pouvons injecter l'instanceActorSystem dans notre code (soit un code d'application géré par Spring, soit un test basé sur Spring), créer un objetProps pour un acteur en utilisant notre extension, récupérez une référence à un acteur via l'objetProps et essayez de saluer quelqu'un:

ActorRef greeter = system.actorOf(SPRING_EXTENSION_PROVIDER.get(system)
  .props("greetingActor"), "greeter");

FiniteDuration duration = FiniteDuration.create(1, TimeUnit.SECONDS);
Timeout timeout = Timeout.durationToTimeout(duration);

Future result = ask(greeter, new Greet("John"), timeout);

Assert.assertEquals("Hello, John", Await.result(result, duration));


Ici, nous utilisons le modèle typiqueakka.pattern.Patterns.ask qui renvoie une instanceFuture d'une Scala. Une fois le calcul terminé, leFuture est résolu avec une valeur que nous avons renvoyée dans notre méthodeGreetingActor.onMessasge.

Nous pouvons soit attendre le résultat en appliquant la méthodeAwait.result de Scala auxFuture, soit, plus préférablement, construire l’application entière avec des modèles asynchrones.

5. Conclusion

Dans cet article, nous avons montré comment intégrer Spring Framework avec Akka et les haricots autowire aux acteurs.

Le code source de l'article est disponibleon GitHub.