Introduzione alla primavera con Akka

1. Introduzione

In questo articolo ci concentreremo sull'integrazione di Akka con Spring Framework, per consentire l'iniezione di servizi basati su Spring negli attori Akka.

Prima di leggere questo articolo, si consiglia una conoscenza preliminare delle basi di Akka.

2. Dependency Injection in Akka

Akka è un potente framework applicativo basato sul modello di concorrenza degli attori. Il framework è scritto in Scala, il che ovviamente lo rende completamente utilizzabile anche in applicazioni basate su Java. E quindi molto spesso vorremo integrare Akka con un'applicazione esistente basata su Spring o semplicemente utilizzare Spring per collegare i bean agli attori.

Il problema con l'integrazione Spring / Akka sta nella differenza tra la gestione dei bean in Spring e la gestione degli attori in Akka: gli attori hanno un ciclo di vita specifico che differisce dal tipico ciclo di vita dei bean Spring .

Inoltre, gli attori sono suddivisi in un attore stesso (che è un dettaglio di implementazione interna e non può essere gestito da Spring) e un riferimento attore, che è accessibile da un codice client, così come serializzabile e portabile tra diversi runtime Akka.

Fortunatamente, Akka fornisce un meccanismo, vale a dire le estensioni Akka, che rende l'utilizzo di framework di iniezione delle dipendenze esterne un compito abbastanza semplice.

3. Dipendenze di Maven

Per dimostrare l'uso di Akka nel nostro progetto Spring, avremo bisogno di una dipendenza Spring minima: la libreria spring-context e anche la libreria akka-actor . Le versioni della libreria possono essere estratte insezione del pom :

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

Assicurati di controllare Maven Central per le ultime versioni delle dipendenze spring-context e akka-actor .

E si noti come la dipendenza akka-attore ha un suffisso _2.11 nel suo nome, il che significa che questa versione del framework Akka è stata compilata contro la versione 2.11 di Scala. La versione corrispondente della libreria Scala sarà inclusa in modo transitorio nella tua build.

4. Iniezione di fagioli primaverili in Akka Actors

Creiamo una semplice applicazione Spring / Akka composta da un singolo attore che possa rispondere al nome di una persona lanciando un saluto a questa persona. La logica del saluto verrà estratta in un servizio separato. Vorremo collegare automaticamente questo servizio a un'istanza dell'attore. L'integrazione primaverile ci aiuterà in questo compito.

4.1. Definizione di un attore e di un servizio

Per dimostrare l'iniezione di un servizio in un attore, creeremo una semplice classe GreetingActor definita come attore non tipizzato (estendendo la classe base UntypedActor di Akka ). Il metodo principale di ogni attore Akka è il metodo onReceive che riceve un messaggio e lo elabora secondo una logica specificata.

Nel nostro caso, l' implementazione di GreetingActor controlla se il messaggio è di un tipo predefinito Greet , quindi prende il nome della persona dall'istanza di Greet , quindi utilizza il GreetingService per ricevere un saluto per questa persona e risponde al mittente con la stringa di saluto ricevuta. Se il messaggio è di un altro tipo sconosciuto, viene passato al metodo non gestito predefinito dell'attore .

Diamo un'occhiata:

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

Si noti che il tipo di messaggio Greet è definito come una classe interna statica all'interno di questo attore, che è considerata una buona pratica. I tipi di messaggio accettati devono essere definiti il ​​più vicino possibile a un attore per evitare confusione sui tipi di messaggio che questo attore può elaborare.

Notare anche le annotazioni Spring @Component e @Scope : definiscono la classe come un bean gestito da Spring con l' ambito del prototipo .

L'ambito è molto importante, perché ogni richiesta di recupero del bean dovrebbe risultare in un'istanza appena creata, poiché questo comportamento corrisponde al ciclo di vita dell'attore di Akka. Se si implementa questo bean con un altro ambito, il tipico caso di riavvio degli attori in Akka molto probabilmente funzionerà in modo errato.

Infine, nota che non dovevamo esplicitamente @Autowire l' istanza di GreetingService - questo è possibile grazie alla nuova funzionalità di Spring 4.3 chiamata Implicit Constructor Injection .

L'implementazione di GreeterService è piuttosto semplice, si noti che lo abbiamo definito come un bean gestito da Spring aggiungendovi l' annotazione @Component (con ambito singleton predefinito ):

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

4.2. Aggiunta del supporto primaverile tramite l'estensione Akka

Il modo più semplice per integrare Spring con Akka è attraverso un'estensione Akka.

Un'estensione è un'istanza singleton creata per sistema di attori. Consiste di una classe di estensione stessa, che implementa l'interfaccia del marker Extension , e di una classe ID di estensione che di solito eredita AbstractExtensionId .

Poiché queste due classi sono strettamente accoppiate, ha senso implementare la classe Extension nidificata all'interno della 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); } } }

Primo : SpringExtension implementa un singolo metodo createExtension dalla classe AbstractExtensionId , che tiene conto della creazione di un'istanza di estensione, l' oggetto SpringExt .

La classe SpringExtension ha anche un campo statico SPRING_EXTENSION_PROVIDER che contiene un riferimento alla sua unica istanza. Spesso ha senso aggiungere un costruttore privato per affermare esplicitamente che SpringExtention dovrebbe essere una classe singleton, ma la ometteremo per chiarezza.

In secondo luogo , la classe interna statica SpringExt è l'estensione stessa. Poiché Extension è semplicemente un'interfaccia marker, possiamo definire i contenuti di questa classe come riteniamo opportuno.

Nel nostro caso, avremo bisogno del metodo initialize per mantenere un'istanza Spring ApplicationContext : questo metodo verrà chiamato solo una volta per l'inizializzazione dell'estensione.

Inoltre richiederemo il metodo props per creare un oggetto Props . L' istanza Props è un progetto per un attore e nel nostro caso il metodo Props.create riceve una classe SpringActorProducer e gli argomenti del costruttore per questa classe. Questi sono gli argomenti con cui verrà chiamato il costruttore di questa classe.

Il metodo props verrà eseguito ogni volta che avremo bisogno di un riferimento ad un attore gestito da Spring.

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.

Possiamo attendere che il risultato applicando della Scala Await.result metodo per il futuro , o, più preferibilmente, costruire l'intera applicazione con i modelli asincroni.

5. conclusione

In questo articolo abbiamo mostrato come integrare Spring Framework con Akka e fagioli autowire negli attori.

Il codice sorgente dell'articolo è disponibile su GitHub.