Вступ до Весни з Аккою

1. Вступ

У цій статті ми зосередимося на інтеграції Akka з Spring Framework - щоб дозволити введення весняних сервісів в акторів Akka.

Перш ніж читати цю статтю, рекомендується попереднє знання основ Akka.

2. Ін’єкція залежності в Акка

Akka - це потужна прикладна програма, заснована на моделі одночасності Actor. Фреймворк написаний на Scala, що, звичайно, робить його повністю придатним і для програм на базі Java. Тому дуже часто ми хочемо інтегрувати Akka з існуючим додатком на основі Spring або просто використовувати Spring для підключення квадроциклів до акторів.

Проблема інтеграції Spring / Akka полягає в різниці між управлінням квасолею весною та управлінням акторами Akka: актори мають певний життєвий цикл, який відрізняється від типового життєвого циклу зерна Spring .

Більше того, актори поділяються на самого актора (який є внутрішньою деталлю реалізації, і ним не може керувати Spring) та посилання на актор, яке доступне за допомогою клієнтського коду, а також серіалізується та переноситься між різними середовищами виконання Akka.

На щастя, Akka пропонує механізм, а саме розширення Akka, що робить використання механізмів введення зовнішніх залежностей досить простим завданням.

3. Залежності Мейвена

Щоб продемонструвати використання Akka у нашому проекті Spring, нам потрібна мінімальна залежність Spring - бібліотека spring-context , а також бібліотека akka-актор . Бібліотечні версії можна отримати врозділ пом :

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

Не забудьте перевірити Maven Central на наявність останніх версій залежностей spring-context та akka-актор .

І зверніть увагу на те, що залежність akka-актор має в назві постфікс _2.11 , що означає, що ця версія фреймворку Akka була побудована проти версії Scala 2.11. Відповідна версія бібліотеки Scala буде перехідно включена у вашу збірку.

4. Впорскування весняних зерен в Акка Актори

Давайте створимо простий додаток Spring / Akka, що складається з одного актора, який може відповісти на ім’я людини, передавши цій людині привітання. Логіка привітання буде виділена в окрему службу. Ми хочемо автоматично підключити цю службу до екземпляра актора. У цьому завданні нам допоможе весняна інтеграція.

4.1. Визначення актора та послуги

Щоб продемонструвати введення служби в актора, ми створимо простий клас GreetingActor, визначений як нетипізований актор (розширюючи базовий клас Akka UntypedActor ). Основним методом кожного актора Akka є метод onReceive, який отримує повідомлення та обробляє його відповідно до певної логіки.

У нашому випадку реалізація GreetingActor перевіряє, чи є повідомлення заздалегідь визначеним типом Greet , потім бере ім’я особи з інстанції Greet , а потім використовує GreetingService, щоб отримати привітання для цієї особи та відповідає відправнику отриманим рядком привітання. Якщо повідомлення має інший невідомий тип, воно передається заздалегідь визначеному не оброблюваному методу актора .

Давай подивимось:

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

Зверніть увагу, що тип повідомлення Greet визначається як статичний внутрішній клас всередині цього актора, що вважається гарною практикою. Прийняті типи повідомлень слід визначати якомога ближче до актора, щоб уникнути плутанини щодо типів повідомлень, які цей актор може обробляти.

Також зверніть увагу на анотації Spring @Component та @Scope - вони визначають клас як керований Spring процесором із областю прототипу .

Обсяг дуже важливий, оскільки кожен запит на отримання компонента повинен призводити до новоствореного екземпляра, оскільки така поведінка відповідає життєвому циклу актора Akka. Якщо ви реалізуєте цей компонент з іншим обсягом, типовий випадок перезапуску акторів в Akka, швидше за все, буде функціонувати неправильно.

Нарешті, зверніть увагу , що ми не повинні явно @Autowire в GreetingService екземпляр - це можливо завдяки новій функції Spring 4.3 називається неявної Constructor Injection .

Реалізація GreeterService досить проста, зверніть увагу , що ми визначили його як Spring-керований компонент шляхом додавання @Component анотації до нього (з невиконанням одноплідних об'ємом):

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

4.2. Додавання весняної підтримки через розширення Akka

Найпростіший спосіб інтегрувати Spring з Akka - за допомогою розширення Akka.

Розширення - це одинарний екземпляр, створений для кожної системи акторів. Він складається з самого класу розширення, який реалізує маркерний інтерфейс Extension , та класу ідентифікатора розширення, який зазвичай успадковує AbstractExtensionId .

Оскільки ці два класи тісно пов'язані між собою, має сенс реалізувати клас Extension, вкладений у клас 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); } } }

По-перше - SpringExtension реалізує єдиний метод createExtension із класу AbstractExtensionId - який враховує створення екземпляра розширення, об’єкта SpringExt .

Клас SpringExtension також має статичне поле SPRING_EXTENSION_PROVIDER, яке містить посилання на свій єдиний екземпляр. Часто має сенс додати приватний конструктор, щоб явно зазначити, що SpringExtention повинен бути однокласним класом, але ми його опустимо для ясності.

По-друге , статичний внутрішній клас SpringExt - це саме розширення. Оскільки Extension - це просто маркерний інтерфейс, ми можемо визначити вміст цього класу, як вважаємо за потрібне.

У нашому випадку нам знадобиться метод ініціалізації для збереження екземпляра Spring ApplicationContext - цей метод буде викликаний лише один раз при ініціалізації розширення.

Також нам знадобиться метод props для створення об'єкта Props . Реквізит екземпляр є основою для актора, і в нашому випадку Props.create метод отримує SpringActorProducer класу і аргументи конструктора для цього класу. Це аргументи, з якими буде викликаний конструктор цього класу.

Метод props буде виконуватися кожного разу, коли нам знадобиться посилання на актора, керованого 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.

Ми можемо або дочекатися результату, застосувавши метод Scala Await.result до майбутнього , або, більш бажано, побудувати весь додаток з асинхронними шаблонами.

5. Висновок

У цій статті ми показали, як інтегрувати Spring Framework з Akka та автопровідними бобами в актори.

Вихідний код статті доступний на GitHub.