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.

2. Injection de dépendance à Akka

Akka est un puissant framework d'application basé sur le modèle de concurrence Actor. Le framework est écrit en Scala, ce qui le rend bien sûr pleinement utilisable dans des applications basées sur Java. Et c'est donc très souvent que nous voudrons intégrer Akka à une application existante basée sur Spring ou simplement utiliser Spring pour câbler les beans dans les 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: les acteurs ont un cycle de vie spécifique qui diffère du cycle de vie typique des beans Spring .

De plus, les acteurs sont divisés en un acteur lui-même (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, qui est accessible par un code client, ainsi que sérialisable et portable entre différents runtimes Akka.

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

3. Dépendances de Maven

Pour démontrer l'utilisation d'Akka dans notre projet Spring, nous aurons besoin d'une dépendance Spring minimale - la bibliothèque spring-context , ainsi que la bibliothèque akka-actor . Les versions de la bibliothèque peuvent être extraites vers lesection du pom :

 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 consulter Maven Central pour les dernières versions des dépendances spring-context et akka-actor .

Et remarquez comment, que la dépendance akka-acteur 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 build.

4. Injection de haricots printaniers dans les 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 adressant un message d'accueil à cette personne. La logique de salutation sera extraite vers un service séparé. Nous voudrons transférer automatiquement ce service à une instance d'acteur. L'intégration de printemps 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 simple GreetingActor définie comme un acteur non typé (étendant la classe de base UntypedActor d'Akka ). La méthode principale de chaque acteur Akka est la méthode onReceive qui reçoit un message et le traite selon une logique spécifiée.

Dans notre cas, l' implémentation de GreetingActor vérifie si le message est d'un type prédéfini Greet , puis prend le nom de la personne de l' instance de Greet , puis utilise le GreetingService pour recevoir un message d' accueil pour cette personne et répond à l'expéditeur avec la chaîne d'accueil reçue. Si le message est d'un autre type inconnu, il est passé à la méthode non gérée 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 message Greet 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 messages acceptés doivent être définis aussi près que possible d'un acteur pour éviter toute confusion sur les types de messages que cet acteur peut traiter.

Notez également les annotations Spring @Component et @Scope - celles-ci définissent la classe comme un bean géré par Spring avec la portée du prototype .

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 de redémarrage d'acteurs dans Akka fonctionnera probablement de manière incorrecte.

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

L'implémentation de GreeterService 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 singleton par défaut ):

@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.

Une extension est une instance de singleton créée par système d'acteur. Il se compose d'une classe d'extension elle-même, qui implémente l'interface de marqueur Extension , et d'une classe d'id d'extension qui hérite généralement de AbstractExtensionId .

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

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); } } }

Premièrement - SpringExtension implémente une seule méthode createExtension de la classe AbstractExtensionId - qui prend en compte la création d'une instance d'extension, l' objet SpringExt .

La classe SpringExtension a également un champ statique SPRING_EXTENSION_PROVIDER qui contient une référence à sa seule instance. Il est souvent judicieux d'ajouter un constructeur privé pour indiquer explicitement que SpringExtention est censé être une classe singleton, mais nous l'omettons pour plus de clarté.

Deuxièmement , la classe interne statique SpringExt est l'extension elle-même. Comme Extension 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éthode initialize pour conserver une instance Spring ApplicationContext - cette méthode ne sera appelée qu'une seule fois par initialisation d'extension.

Also we’ll require the props method for creating a Props object. Props instance is a blueprint for an actor, and in our case the Props.create method receives a SpringActorProducer class and constructor arguments for this class. These are the arguments that this class’ constructor will be called with.

The props method will be executed every time we’ll need a Spring-managed actor reference.

The third and last piece of the puzzle is the SpringActorProducer class. It implements Akka’s IndirectActorProducer interface which allows overriding the instantiation process for an actor by implementing the produce and actorClass methods.

As you probably already have guessed, instead of direct instantiation, it will always retrieve an actor instance from the Spring’s ApplicationContext. As we’ve made the actor a prototype-scoped bean, every call to the produce method will return a new instance of the actor:

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. Putting It All Together

The only thing that’s left to do is to create a Spring configuration class (marked with @Configuration annotation) which will tell Spring to scan the current package together with all nested packages (this is ensured by the @ComponentScan annotation) and create a Spring container.

We only need to add a single additional bean — the ActorSystem instance — and initialize the Spring extension on this ActorSystem:

@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. Retrieving Spring-Wired Actors

To test that everything works correctly, we may inject the ActorSystem instance into our code (either some Spring-managed application code, or a Spring-based test), create a Props object for an actor using our extension, retrieve a reference to an actor via Props object and try to greet somebody:

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));

Here we use the typical akka.pattern.Patterns.ask pattern that returns a Scala's Future instance. Once the computation is completed, the Future is resolved with a value that we returned in our GreetingActor.onMessasge method.

Nous pouvons soit attendre le résultat en appliquant la méthode Await.result de Scala à Future , 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 beans autowire dans les acteurs.

Le code source de l'article est disponible sur GitHub.