Introduction to Spring with Akka – 使用Akka的Spring介绍

最后修改: 2016年 7月 30日

1. Introduction

1.介绍

In this article we’ll focus on integrating Akka with the Spring Framework – to allow injection of Spring-based services into Akka actors.

在这篇文章中,我们将关注Akka与Spring框架的集成–允许将基于Spring的服务注入到Akka行为体。

Before reading this article, a prior knowledge of Akka’s basics is recommended.

在阅读本文之前,建议先对Akka的基础知识有所了解。

2. Dependency Injection in Akka

2.Akka中的依赖注入

Akka is a powerful application framework based on the Actor concurrency model. The framework is written in Scala which of course makes it fully usable in a Java-based applications as well. And so it’s very often we will want to integrate Akka with an existing Spring-based application or simply use Spring for wiring beans into actors.

Akka是一个基于Actor并发模型的强大应用框架。该框架是用Scala编写的,这当然使得它也可以完全用于基于Java的应用程序。因此,我们经常希望将Akka与现有的基于Spring的应用程序相集成,或者简单地使用Spring将Bean连接到Actors。

The problem with Spring/Akka integration lies in the difference between the management of beans in Spring and the management of actors in Akka: actors have a specific lifecycle that differs from typical Spring bean lifecycle.

Spring/Akka集成的问题在于Spring中Bean的管理和Akka中Actor的管理之间的差异:Actor有一个特定的生命周期,与典型的Spring Bean的生命周期不同

Moreover, actors are split into an actor itself (which is an internal implementation detail and cannot be managed by Spring) and an actor reference, which is accessible by a client code, as well as serializable and portable between different Akka runtimes.

此外,actor被拆分为actor本身(这是一个内部实现细节,不能由Spring管理)和actor引用,可以由客户端代码访问,也可以在不同的Akka运行时之间序列化和移植。

Luckily, Akka provides a mechanism, namely Akka extensions, that makes using external dependency injection frameworks a fairly easy task.

幸运的是,Akka提供了一种机制,即Akka扩展,这使得使用外部依赖注入框架成为一项相当容易的任务。

3. Maven Dependencies

3.Maven的依赖性

To demonstrate the usage of Akka in our Spring project, we’ll need a bare minimum Spring dependency — the spring-context library, and also the akka-actor library. The library versions can be extracted to the <properties> section of the pom:

为了演示Akka在Spring项目中的应用,我们需要一个最基本的Spring依赖–spring-context库,以及akka-actor库。这些库的版本可以提取到<properties>部分的pom中。

<properties>
    <spring.version>4.3.1.RELEASE</spring.version>
    <akka.version>2.4.8</akka.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>com.typesafe.akka</groupId>
        <artifactId>akka-actor_2.11</artifactId>
        <version>${akka.version}</version>
    </dependency>

</dependencies>

Make sure to check Maven Central for the latest versions of spring-context and akka-actor dependencies.

请确保检查Maven中心,查看spring-contextakka-actor依赖的最新版本。

And notice how, that the akka-actor dependency has a _2.11 postfix in its name, which signifies that this version of Akka framework was built against the Scala version 2.11. The corresponding version of Scala library will be transitively included in your build.

请注意,akka-actor依赖关系的名称中有一个_2.11后缀,这意味着这个版本的Akka框架是针对Scala 2.11版本构建的。相应版本的Scala库将被包含在你的构建中。

4. Injecting Spring Beans into Akka Actors

4.将Spring Bean注入Akka Actors中

Let’s create a simple Spring/Akka application consisting of a single actor that can answer to a person’s name by issuing a greeting to this person. The logic of greeting will be extracted to a separate service. We will want to autowire this service to an actor instance. Spring integration will help us in this task.

让我们创建一个简单的Spring/Akka应用程序,它由一个单独的角色组成,可以回答一个人的名字,向这个人发出问候。问候的逻辑将被提取到一个单独的服务中。我们希望将这个服务自动连接到一个角色实例上。Spring集成将帮助我们完成这项任务。

4.1. Defining an Actor and a Service

4.1.定义一个角色和一个服务

To demonstrate injection of a service into an actor, we’ll create a simple class GreetingActor defined as an untyped actor (extending the Akka’s UntypedActor base class). The main method of every Akka actor is the onReceive method which receives a message and processes it according to some specified logic.

为了演示将服务注入到角色中,我们将创建一个简单的类GreetingActor,定义为一个非类型的角色(扩展了Akka的UntypedActor基类)。每个Akka行为体的主要方法是onReceive方法,它接收一个消息并根据一些指定的逻辑来处理它。

In our case, the GreetingActor implementation checks if the message is of a predefined type Greet, then takes the name of the person from the Greet instance, then uses the GreetingService to receive a greeting for this person and answers sender with the received greeting string. If the message is of some other unknown type, it is passed to the actor’s predefined unhandled method.

在我们的例子中,GreetingActor实现检查消息是否是预定义的Greet类型,然后从Greet实例中获取人的名字,然后使用GreetingService来接收这个人的问候,并用收到的问候字符串回答发送者。如果消息是其他未知的类型,它将被传递给角色预定义的unhandled方法。

Let’s have a look:

让我们看一看。

@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

    }
}

Notice that the Greet message type is defined as a static inner class inside this actor, which is considered a good practice. Accepted message types should be defined as close to an actor as possible to avoid confusion on which message types this actor can process.

请注意,Greet消息类型被定义为这个角色内部的静态类,这被认为是一个好的做法。接受的消息类型应该尽可能地定义在靠近角色的地方,以避免混淆这个角色可以处理哪些消息类型。

Also notice the Spring annotations @Component and @Scope – these define the class as a Spring-managed bean with the prototype scope.

还要注意Spring注解@Component@Scope–这些注解将该类定义为具有prototype作用域的Spring管理的bean。

The scope is very important, because every bean retrieval request should result in a newly created instance, as this behavior matches Akka’s actor lifecycle. If you implement this bean with some other scope, the typical case of restarting actors in Akka will most likely function incorrectly.

这个范围非常重要,因为每个Bean的检索请求都应该导致一个新创建的实例,因为这个行为与Akka的actor生命周期相匹配。如果你用其他的作用域来实现这个Bean,那么Akka中重启actor的典型案例很可能会出现错误的功能。

Finally, notice that we didn’t have to explicitly @Autowire the GreetingService instance — this is possible due to the new feature of Spring 4.3 called Implicit Constructor Injection.

最后,注意到我们不必明确地@Autowire GreetingService实例–这是由于Spring 4.3的新特性Implicit Constructor Injection

The implementation of GreeterService is pretty straightforward, notice that we defined it as a Spring-managed bean by adding the @Component annotation to it (with default singleton scope):

GreeterService的实现非常直接,注意我们通过向它添加@Component注解,将其定义为Spring管理的Bean(带有默认的singleton范围)。

@Component
public class GreetingService {

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

4.2. Adding Spring Support via Akka Extension

4.2.通过Akka扩展添加Spring支持

The easiest way to integrate Spring with Akka is through an Akka extension.

将Spring与Akka集成的最简单方法是通过Akka扩展。

An extension is a singleton instance created per actor system. It consists of an extension class itself, which implements the marker interface Extension, and an extension id class which usually inherits AbstractExtensionId.

扩展是每个行为体系统创建的单例。它由扩展类本身和扩展ID类组成,前者实现了标记接口Extension,后者通常继承了AbstractExtensionId

As these two classes are tightly coupled, it makes sense to implement the Extension class nested within the ExtensionId class:

由于这两个类是紧密耦合的,实现Extension类嵌套在ExtensionId类中是有意义的。

public class SpringExtension 
  extends AbstractExtensionId<SpringExtension.SpringExt> {

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

FirstSpringExtension implements a single createExtension method from the AbstractExtensionId class – which accounts for creation of an extension instance, the SpringExt object.

第一SpringExtensionAbstractExtensionId类中实现了一个createExtension方法–它负责创建一个扩展实例,即SpringExt对象。

The SpringExtension class also has a static field SPRING_EXTENSION_PROVIDER which holds a reference to its only instance. It often makes sense to add a private constructor to explicitly state that SpringExtention is supposed to be a singleton class, but we’ll omit it for clarity.

SpringExtension类也有一个静态字段SPRING_EXTENSION_PROVIDER,它持有一个对其唯一实例的引用。添加一个私有构造函数来明确说明SpringExtention应该是一个单子类,这通常是有意义的,但为了清晰起见,我们将省略它。

Secondly, the static inner class SpringExt is the extension itself. As Extension is simply a marker interface, we may define the contents of this class as we see fit.

其次,静态内类SpringExt是扩展本身。由于Extension只是一个标记性的接口,我们可以按照我们认为合适的方式定义这个类的内容。

In our case, we’re going to need the initialize method for keeping a Spring ApplicationContext instance — this method will be called only once per extension’s initialization.

在我们的案例中,我们需要initialize方法来保持Spring ApplicationContext实例–这个方法在每个扩展的初始化中只被调用一次。

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.

此外,我们还需要props方法来创建Props对象。Props实例是一个演员的蓝图,在我们的例子中,Props.create方法接收一个SpringActorProducer类和这个类的构造参数。这些是这个类的构造器将被调用的参数。

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

每次我们需要一个Spring管理的角色引用时,props方法将被执行。

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.

第三块也是最后一块拼图是SpringActorProducer类。它实现了Akka的IndirectActorProducer接口,允许通过实现produceactorClass方法来覆盖一个角色的实例化过程。

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:

正如你可能已经猜到的,代替直接实例化,它总是从Spring的ApplicationContext中获取一个actor实例。由于我们已经把actor变成了一个prototype范围的bean,每次对produce方法的调用都会返回一个新的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<? extends Actor> actorClass() {
        return (Class<? extends Actor>) applicationContext
          .getType(beanActorName);
    }
}

4.3. Putting It All Together

4.3.把一切放在一起

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.

唯一要做的是创建一个Spring配置类(标有@Configuration注解),它将告诉Spring扫描当前包和所有嵌套包(这由@ComponentScan注解保证)并创建一个Spring容器。

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

我们只需添加一个额外的Bean–ActorSystem实例–并在此ActorSystem上初始化Spring扩展。

@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

4.4.检索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:

为了测试一切工作是否正常,我们可以将ActorSystem实例注入到我们的代码中(要么是一些Spring管理的应用程序代码,要么是基于Spring的测试),使用我们的扩展为演员创建一个Props对象,通过Props对象检索一个演员的引用,并尝试问候某人。

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

这里我们使用典型的akka.pattern.Patterns.ask模式,返回Scala的Future实例。一旦计算完成,Future就会被解析为一个我们在GreetingActor.onMessasge方法中返回的值。

We may either wait for the result by applying the Scala’s Await.result method to the Future, or, more preferably, build the entire application with asynchronous patterns.

我们可以通过对Future应用Scala的Await.result方法来等待结果,或者,更可取的是,用异步模式构建整个应用程序。

5. Conclusion

5.结论

In this article we’ve showed how to integrate Spring Framework with Akka and autowire beans into actors.

在这篇文章中,我们展示了如何将Spring框架与Akka和autowire beans集成到actors中。

The source code for the article is available on GitHub.

文章的源代码可在GitHub上获得