Einführung in den Frühling mit Akka

1. Einleitung

In diesem Artikel konzentrieren wir uns auf die Integration von Akka in das Spring Framework, um die Einführung von Spring-basierten Diensten in Akka-Akteure zu ermöglichen.

Bevor Sie diesen Artikel lesen, sollten Sie sich mit den Grundlagen von Akka vertraut machen.

2. Abhängigkeitsinjektion in Akka

Akka ist ein leistungsstarkes Anwendungsframework, das auf dem Actor-Parallelitätsmodell basiert. Das Framework ist in Scala geschrieben, was es natürlich auch in Java-basierten Anwendungen voll verwendbar macht. Daher möchten wir Akka häufig in eine vorhandene Spring-basierte Anwendung integrieren oder Spring einfach zum Verdrahten von Beans in Schauspieler verwenden.

Das Problem bei der Integration von Spring / Akka liegt im Unterschied zwischen der Verwaltung von Bohnen im Frühjahr und der Verwaltung von Akteuren in Akka: Akteure haben einen bestimmten Lebenszyklus, der sich vom typischen Lebenszyklus von Spring Bean unterscheidet .

Darüber hinaus werden die Akteure in einen Akteur selbst (ein internes Implementierungsdetail, das von Spring nicht verwaltet werden kann) und eine Akteursreferenz aufgeteilt, auf die über einen Clientcode zugegriffen werden kann, sowie zwischen verschiedenen Akka-Laufzeiten serialisierbar und portierbar.

Glücklicherweise bietet Akka einen Mechanismus, nämlich Akka-Erweiterungen, der die Verwendung externer Abhängigkeitsinjektions-Frameworks zu einer ziemlich einfachen Aufgabe macht.

3. Maven-Abhängigkeiten

Um die Verwendung von Akka in unserem Spring-Projekt zu demonstrieren, benötigen wir eine minimale Spring-Abhängigkeit - die Spring-Kontextbibliothek und auch die Akka-Actor- Bibliothek. Die Bibliotheksversionen können in die extrahiert werdenAbschnitt des Poms :

 4.3.1.RELEASE 2.4.8    org.springframework spring-context ${spring.version}   com.typesafe.akka akka-actor_2.11 ${akka.version}  

Stellen Sie sicher, dass Sie in Maven Central nach den neuesten Versionen der Abhängigkeiten von Spring-Kontext und Akka-Actor suchen .

Beachten Sie auch , dass die Akka-Actor- Abhängigkeit einen _2.11- Postfix im Namen hat, was bedeutet, dass diese Version des Akka-Frameworks gegen die Scala-Version 2.11 erstellt wurde. Die entsprechende Version der Scala-Bibliothek wird transitiv in Ihren Build aufgenommen.

4. Injizieren von Frühlingsbohnen in Akka-Schauspieler

Erstellen wir eine einfache Spring / Akka-Anwendung, die aus einem einzelnen Akteur besteht, der auf den Namen einer Person antworten kann, indem er dieser Person eine Begrüßung sendet. Die Begrüßungslogik wird in einen separaten Dienst extrahiert. Wir möchten diesen Dienst automatisch mit einer Akteurinstanz verdrahten. Die Frühjahrsintegration wird uns bei dieser Aufgabe helfen.

4.1. Definieren eines Schauspielers und eines Dienstes

Um die Injektion eines Dienstes in einen Akteur zu demonstrieren, erstellen wir eine einfache Klasse GreetingActor, die als untypisierter Akteur definiert ist (Erweiterung der Basisklasse UntypedActor von Akka ). Die Hauptmethode jedes Akka-Akteurs ist die onReceive- Methode, die eine Nachricht empfängt und gemäß einer bestimmten Logik verarbeitet.

In unserem Fall prüft die GreetingActor- Implementierung, ob die Nachricht vom vordefinierten Typ Greet ist , nimmt dann den Namen der Person aus der Greet- Instanz, verwendet dann den GreetingService , um eine Begrüßung für diese Person zu erhalten, und antwortet dem Absender mit der empfangenen Begrüßungszeichenfolge. Wenn die Nachricht von einem anderen unbekannten Typ ist, wird sie an die vordefinierte nicht behandelte Methode des Schauspielers übergeben .

Werfen wir einen Blick:

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

Beachten Sie, dass die Greet Typ Nachricht als eine statische innere Klasse innerhalb dieser Akteur definiert, die eine gute Praxis betrachtet wird. Akzeptierte Nachrichtentypen sollten so nah wie möglich an einem Akteur definiert werden, um Verwirrung darüber zu vermeiden, welche Nachrichtentypen dieser Akteur verarbeiten kann.

Beachten Sie auch die Spring-Annotationen @Component und @Scope - diese definieren die Klasse als Spring-verwaltete Bean mit dem Prototypbereich .

Der Umfang ist sehr wichtig, da jede Bean-Abrufanforderung zu einer neu erstellten Instanz führen sollte, da dieses Verhalten dem Akka-Akteur-Lebenszyklus entspricht. Wenn Sie diese Bean mit einem anderen Bereich implementieren, funktioniert der typische Fall eines Neustarts der Akteure in Akka höchstwahrscheinlich nicht richtig.

Beachten Sie schließlich, dass wir die GreetingService- Instanz nicht explizit @Autowire ausführen mussten - dies ist aufgrund der neuen Funktion von Spring 4.3 namens Implicit Constructor Injection möglich .

Die Implementierung von GreeterService ist ziemlich einfach. Beachten Sie , dass wir es als Spring-verwaltete Bean definiert haben, indem wir die Annotation @Component hinzugefügt haben (mit Standard- Singleton- Bereich):

@Component public class GreetingService { public String greet(String name) { return "Hello, " + name; } }

4.2. Hinzufügen der Federunterstützung über die Akka-Erweiterung

Der einfachste Weg, Spring in Akka zu integrieren, ist eine Akka-Erweiterung.

Eine Erweiterung ist eine Singleton-Instanz, die pro Akteursystem erstellt wird. Er besteht aus einer Verlängerung Klasse selbst, die die Marker - Schnittstelle implementiert Verlängerung und Erweiterung id - Klasse , die in der Regel erbt AbstractExtensionId .

Da diese beiden Klassen eng miteinander verbunden sind, ist es sinnvoll, die in der ExtensionId- Klasse verschachtelte Extension- Klasse zu implementieren :

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

Erstens - SpringExtension implementiert eine einzelne createExtension- Methode aus der AbstractExtensionId- Klasse, die die Erstellung einer Erweiterungsinstanz, des SpringExt- Objekts , berücksichtigt .

Die SpringExtension- Klasse verfügt außerdem über ein statisches Feld SPRING_EXTENSION_PROVIDER, das einen Verweis auf ihre einzige Instanz enthält. Es ist oft sinnvoll, einen privaten Konstruktor hinzuzufügen, um explizit anzugeben, dass SpringExtention eine Singleton-Klasse sein soll, aber wir werden es aus Gründen der Übersichtlichkeit weglassen.

Zweitens ist die statische innere Klasse SpringExt die Erweiterung selbst. Da Extension lediglich eine Markierungsschnittstelle ist, können wir den Inhalt dieser Klasse nach Belieben definieren.

In unserem Fall benötigen wir die Initialisierungsmethode , um eine Spring ApplicationContext- Instanz beizubehalten. Diese Methode wird nur einmal pro Initialisierung der Erweiterung aufgerufen.

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.

Wir können entweder auf das Ergebnis warten, indem wir die Await.result- Methode der Scala auf die Zukunft anwenden , oder, bevorzugter, die gesamte Anwendung mit asynchronen Mustern erstellen.

5. Schlussfolgerung

In diesem Artikel haben wir gezeigt, wie Spring Framework mit Akka und Autowire Beans in Schauspieler integriert werden kann.

Der Quellcode für den Artikel ist auf GitHub verfügbar.