Akka-SpringBoot集成
阅读原文时间:2021年04月20日阅读:1

当我们已经了解Akka的基础知识之后,我们更多的是想知道在实际项目中,我们应该怎样使用起来。按照目前业界使用的技术来看,spring应该占主流,由于spring本身需要太多的配置文件,流程繁杂,这里采用较轻量的springboot来向大家介绍,只是省去诸多配置文件,操作过程都一样。

与springboot集成,是因为我们想把ActorSystem、Actor等组件的创建纳入SpringBoot容器中,方便管理。大家都知道,ActorSystem的创建不是依赖new方式,而是通过create方法,所以我们需要写一个Bean来生产ActorSystem。另外Actor,它也是通过actorOf()方法创建的,所以我们也需要写生产Actor引用的方法,Akka提供了IndirectActorProducer接口,通过实现该接口,我们就可以实现DI(依赖注入)。集成springboot之后,ActorSystem范围内的依赖都会交给SpringBoot来管理,并且每个ActorSystem都会持有一个ApplicationContext。

下面我们开始来看看集成是怎样实现的:

在实现之前,我们需要加入相关依赖,如下:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>com.typesafe.akka</groupId>
   <artifactId>akka-slf4j_2.11</artifactId>
   <version>2.5.16</version>
</dependency>
<dependency>
   <groupId>com.typesafe.akka</groupId>
   <artifactId>akka-actor_2.11</artifactId>
   <version>2.5.16</version>
</dependency>

第一步,实现IndirectActorProducer,用于生产Actor,既然是交给Spring管理,肯定少不了ApplicationContext对象和bean名称:

public class DIProducer implements IndirectActorProducer {
    private ApplicationContext context;
    private String beanName;

    public DIProducer(ApplicationContext context,String beanName){
        this.context=context;
        this.beanName=beanName;
    }

    @Override
    public Actor produce() {
        return (Actor) context.getBean(beanName);
    }

    @Override
    public Class<? extends Actor> actorClass() {
        return (Class<? extends Actor>) context.getType(beanName);
    }
}

第二步,实现了DI之后,我们就需要构造Props对象,用来创建ActorRef:

/**
 * @author php
 * @date 2018/12/16
 * 扩展组件,ApplicationContext会在SpringBoot初始化的时候加载进来
 * 构造Props,用于生产ActorRef
 */
public class SpringExt implements Extension {
    private ApplicationContext context;

    public void init(ApplicationContext context) {
        System.out.println("applicationContext初始化...");
        this.context = context;
    }

    /**
     * 该方法用来创建Props对象,依赖前面创建的DI组件,获取到Props对象,我们就可以创建Actor bean对象
     *
     * @param beanName actor bean 名称
     * @return props
     */
    public Props create(String beanName) {
        return Props.create(DIProducer.class, this.context, beanName);
    }
}

第三步,创建SpringExtProvider继承AbstractExtensionId:

/**
 * @author php
 * @date 2018/12/16
 * 通过继承AbstractExtensionId,我们可以在ActorSystem范围内创建并查找SpringExt
 */
public class SpringExtProvider extends AbstractExtensionId<SpringExt> {
    private static SpringExtProvider provider = new SpringExtProvider();

    public static SpringExtProvider getInstance() {
        return provider;
    }

    @Override
    public SpringExt createExtension(ExtendedActorSystem extendedActorSystem) {
        return new SpringExt();
    }
}

第四步,通过SpringExtProvider我们可以获取到SpringExt,通过SpringExt我们可以使用Props创建ActorRef对象,那么现在我们怎么来初始化ActorSystem,并扫描到纳入到容器的Actor呢?我们可以通过@Configuration来创建一个配置类:

/**
 * @author php
 * @date 2018/12/16
 * 创建ActorSystem,并将其放入到spring管理,初始化ApplicationContext
 */
@Configuration
public class ScanConfig {
    private final ApplicationContext context;

    @Autowired
    public ScanConfig(ApplicationContext context) {
        this.context = context;
    }

    @Bean
    public ActorSystem createSystem() {
        ActorSystem system = ActorSystem.create("system");
        SpringExtProvider.getInstance().get(system).init(context);
        return system;
    }
}

下面,我们就来创建一个Actor示例,来验证集成是否成功,如下:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TestActor extends AbstractActor {
    @Override
    public Receive createReceive() {
        return receiveBuilder().matchAny(o -> {
            System.out.println("接受到消息:" + o);
        }).build();
    }
}

特别注意,使用依赖注入框架时,Actor不能配置为单例的,否则程序会出现问题。这里我们简单输出接受到的消息。

测试:

@RunWith(SpringRunner.class)
@SpringBootTest
public class PackApplicationTests {

    @Autowired
    private ActorSystem actorSystem;

   @Test
   public void contextLoads() {
        ActorRef ref = actorSystem.actorOf(SpringExtProvider.getInstance().get(actorSystem).create("testActor"), "testActor");
        ref.tell("hello",ActorRef.noSender());
    }
}

启动测试结果:

2018-12-16 21:23:03.405  INFO 24832 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@51549490: startup date [Sun Dec 16 21:23:03 SGT 2018]; root of context hierarchy
applicationContext初始化...
2018-12-16 21:23:04.900  INFO 24832 --- [           main] com.release.util.PackApplicationTests    : Started PackApplicationTests in 2.052 seconds (JVM running for 3.141)
2018-12-16 21:23:04.947  INFO 24832 --- [       Thread-3] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@51549490: startup date [Sun Dec 16 21:23:03 SGT 2018]; root of context hierarchy
接受到消息:hello

大家可以清楚的看到,集成成功了,oh yeah.