知識庫 / Reactive RSS 訂閱

Spring 與 Akka 入門

Reactive,Spring
HongKong
8
02:49 PM · Dec 06 ,2025

1. 引言

本文將重點介紹如何將 Akka 集成到 Spring Framework 中,以便將 Spring 類型的服務注入到 Akka 演員中。

在閲讀本文之前,建議您對 Akka 的基本知識有所瞭解。

2. Akka 中的依賴注入

Akka 是基於 Actor 併發模型的強大應用程序框架。該框架使用 Scala 編寫,當然也完全可用於 Java 應用程序中。因此,我們經常會希望將 Akka 集成到現有的 Spring 應用程序中,或者僅僅使用 Spring 來將 Bean 注入到 Actor 中

Spring/Akka 集成的問題在於 Spring 中 Bean 的管理與 Akka 中 Actor 的管理之間的差異:Actor 具有與典型的 Spring Bean 生命週期不同的特定生命週期

此外,Actor 被分為 Actor 本身(這是一種內部實現細節,不能由 Spring 管理)和 Actor 引用,後者可通過客户端代碼訪問,並且在不同 Akka 運行時之間是可序列化和可移植的。

幸運的是,Akka 提供了機制,即 Akka 擴展,這使得使用外部依賴注入框架變得相對容易。

3. Maven 依賴項

為了在我們的 Spring 項目中演示 Akka 的使用,我們需要至少一個 Spring 依賴項——spring-context 庫,以及 akka-actor 庫。 庫的版本可以從 pom<properties> 部分提取:

<properties>
    <spring.version>5.3.25</spring.version>
    <akka.version>2.4.14</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.12</artifactId>
        <version>${akka.version}</version>
    </dependency>

</dependencies>

請務必查閲 Maven Central 以獲取 spring-contextakka-actor 依賴項的最新版本。

請注意,akka-actor 依賴項的名稱中包含 _2.11 後綴,這表明該 Akka 框架版本是在 Scala 2.11 上構建的。相應的 Scala 庫版本將作為傳遞依賴項包含在您的構建中。

4. 將 Spring Bean 注入到 Akka 演員中

讓我們創建一個簡單的 Spring/Akka 應用程序,其中包含一個可以根據請求回覆人名的單個演員。問候邏輯將被提取到一個單獨的服務中。我們希望自動注入這個服務到演員實例中。 Spring 集成將幫助我們完成這項任務。

4.1. 定義 Actor 和 Service

為了演示將 Service 注入到 Actor 中的方法,我們將創建一個簡單的 Actor 類 GreetingActor,該類作為未類型 Actor (擴展 Akka 的 UntypedActor 基類) 定義。 Akka 中每個 Actor 的主要方法是 onReceive 方法,該方法接收一個消息並根據某些指定邏輯進行處理。

在我們的例子中,GreetingActor 的實現會檢查消息是否為預定義的類型 Greet,然後從 Greet 實例中獲取人的姓名,然後使用 GreetingService 接收該人的問候語,並以接收到的問候語字符串回覆發送者。 如果消息的類型為其他未知的類型,則會將該消息傳遞給 Actor 的預定義的 unhandled 方法。

讓我們看看:

@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管理Bean,具有prototype作用域。

作用域非常重要,因為每個Bean檢索請求都應導致一個新的實例創建,因為這種行為與Akka的行為模型相符。如果您使用其他作用域實現此Bean,那麼Akka中重啓行為的典型情況很可能無法正常工作。

最後,請注意我們不需要顯式地@AutowireGreetingService實例——這要歸功於Spring 4.3中引入的新特性Implicit Constructor Injection

GreeterService的實現相當簡單,請注意我們將其定義為Spring管理Bean,通過添加@Component註解(具有默認的singleton作用域):

@Component
public class GreetingService {

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

4.2. 通過 Akka 擴展添加 Spring 支持

最簡單的方法是通過 Akka 擴展集成 Spring。

擴展是一個在每個 Actor 系統中創建的單例實例。它包含一個擴展類本身,該類實現標記接口 Extension,以及一個擴展 ID 類,通常繼承 AbstractExtensionId

由於這兩個類緊密耦合,因此在 ExtensionId 類內實現 Extension 類更合理。

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

第一步SpringExtension 實現了一個從 AbstractExtensionId 類中單的 createExtension 方法,該方法負責創建 SpringExt 對象的實例。

SpringExtension 類還具有靜態字段 SPRING_EXTENSION_PROVIDER,它持有其唯一實例的引用。 經常有必要添加私有構造函數,以明確説明 SpringExtention 應該是一個單例類,但為了清晰起見,我們將其省略。

第二步,靜態內部類 SpringExt 是擴展本身。 由於 Extension 僅僅是一個標記接口,我們可以根據需要定義該類的內容。

在我們的情況下,我們需要 initialize 方法來保持 Spring ApplicationContext 實例——此方法僅在擴展的初始化期間調用一次。

此外,我們還需要 props 方法來創建 Props 對象。 Props 實例是演員的藍圖,在我們的案例中,Props.create 方法接收一個 SpringActorProducer 類和該類的構造函數參數。 這些是該類構造函數將被調用的參數。

props 方法將在我們每次需要 Spring 管理的 Actor 引用時執行。

第三步 也是最後一塊拼圖是 SpringActorProducer 類。 它實現了 Akka 的 IndirectActorProducer 接口,允許通過實現 produceactorClass 方法來覆蓋 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. 整合所有組件

剩下的僅僅是創建一個 Spring 配置類(帶有 @Configuration 註解標記),讓 Spring 掃描當前包以及所有嵌套包(這通過 @ComponentScan 註解保證),並創建一個 Spring 容器。

我們只需要添加一個額外的 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. 檢索 Spring-Wired 演員

為了驗證一切是否正常工作,我們可以將 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));

我們這裏使用典型的 akka.pattern.Patterns.ask 模式,它返回 Scala 的 Future 實例。計算完成後,Future 會被解析,並返回我們在 GreetingActor.onMessasge 方法中返回的值。

我們可以通過將 Scala 的 Await.result 方法應用於 Future 來等待結果,或者,更理想的做法是使用異步模式構建整個應用程序。

結論

在本文中,我們演示瞭如何將 Spring Framework 集成到 Akka 中,並將 Bean 自動注入到 Actor 中。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.