/ FEST-Reflect

AKKA Actor Dependency Injection Using Spring

Abstract : Dependency Injection (DI) is refine flavor of Inversion of Control (IoC) design pattern. One of the software frameworks out there which provides DI implementation is Spring. AKKA on the other hand is "a toolkit and runtime for building highly concurrent, distributed, and fault tolerant event-driven applications on the JVM". In this article we are going to concentrate on the actor model implemented by AKKA and more specifically on dependency injection in the UntypedActor class, as well as, injecting AKKA actor in a Spring enabled service.

Goal : Create a software bridge between Spring and an AKKA actor to ensure basic dependency injection

Acknowledgement : My gratitude goes to the open source community

We are going to use the following components to reach our goal: AKKA version 2.0.2, Spring 3.1.1.RELEASE, FEST Reflect 1.4, Logback 1.0.6, CGLIB 2.2, and Maven to glue everything together. All programming will be done in Java and you can find all files on GitHub. Don't hesitate to fork the project and improve it. Here is the Maven POM file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.honeysoft.akka</groupId>
    <artifactId>akka-di</artifactId>
    <version>1.0</version>

    <properties>
        <akka.version>2.0.2</akka.version>
        <spring.version>3.1.1.RELEASE</spring.version>
        <fest-reflect.version>1.4</fest-reflect.version>
        <logback.version>1.0.6</logback.version>
    </properties>

    <dependencies>
        <!-- Akka dependencies  -->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor</artifactId>
            <version>${akka.version}</version>
        </dependency>

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

        <!-- Tools -->
        <dependency>
            <groupId>org.easytesting</groupId>
            <artifactId>fest-reflect</artifactId>
            <version>${fest-reflect.version}</version>
        </dependency>

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib-nodep</artifactId>
            <version>2.2</version>
        </dependency>

        <!-- Logging -->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>${logback.version}</version>
        </dependency>

    </dependencies>

    <repositories>
        <repository>
            <id>akka.repository</id>
            <name>Akka Maven Repository</name>
            <url>http://repo.akka.io/releases/</url>
        </repository>
    </repositories>
 </project>

Good. Once you have all what's needed we can start by building our simple business service which we are going to inject in our actor, here it is:

package org.honeysoft.akka.service;

public interface IBusinessService {

    void doBusiness(Object o);
}

... and it's implementation:

package org.honeysoft.akka.service.impl;

import org.honeysoft.akka.service.IBusinessService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

@Service
public class BusinessService implements IBusinessService {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public void doBusiness(Object o) {
        logger.info("Doing business with {}", o);
    }
}

As we can seen there is nothing fancy in our simple business service. All that it's doing is logging an information about doing business with someone. Note, however, that it's annotated with @Service this allows it to be discovered, created and injected by Spring.

Now, let's see our actor:

package org.honeysoft.akka.actor;

import akka.actor.UntypedActor;
import org.honeysoft.akka.service.IBusinessService;
import org.springframework.beans.factory.annotation.Autowired;

public class BusinessActor extends UntypedActor {

    @Autowired
    private IBusinessService businessService;

    @Override
    public void onReceive(Object o) throws Exception {
        businessService.doBusiness(o);
    }
}

Once more, nothing special, just injecting our business service using the @Autowired annotation. Now comes the interesting part. We are going to need a custom implementation of akka.actor.Props class:

package org.honeysoft.akka.di;

import akka.actor.Props;
import akka.actor.UntypedActorFactory;
import org.springframework.context.ApplicationContext;

public class DependencyInjectionProps extends Props {
    /**
     * No-args constructor that sets all the default values.
     */
    public DependencyInjectionProps(ApplicationContext applicationContext, Class<?> actorClass) {
        super(new SpringUntypedActorFactory(actorClass, applicationContext));
    }

    /**
     * Java API.
     */
    public DependencyInjectionProps(ApplicationContext applicationContext, UntypedActorFactory factory) {
        super(new SpringUntypedActorFactory(factory, applicationContext));
    }
}

The interesting part here is our SpringUntypedActorFactory which is implemented as follows:

package org.honeysoft.akka.di;

import akka.actor.UntypedActor;
import akka.actor.UntypedActorFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

import java.lang.reflect.Field;

import static org.fest.reflect.util.Accessibles.setAccessible;
import static org.fest.reflect.util.Accessibles.setAccessibleIgnoringExceptions;

public class SpringUntypedActorFactory implements UntypedActorFactory {

    private final DependencyInjectionFactory dependencyInjectionFactory;

    private final ApplicationContext applicationContext;

    public SpringUntypedActorFactory(Class<?> actorClass, ApplicationContext applicationContext) {
        this.dependencyInjectionFactory = new DefaultUntypedActorFactory(actorClass);
        this.applicationContext = applicationContext;
    }

    public SpringUntypedActorFactory(UntypedActorFactory customFactory, ApplicationContext applicationContext) {
        this.dependencyInjectionFactory = new SpecificUntypedActorFactory(customFactory);
        this.applicationContext = applicationContext;
    }

    private interface DependencyInjectionFactory {
        UntypedActor createAndInject();
    }


    private abstract class AbstractUntypedActorFactory implements DependencyInjectionFactory {

        @Override
        public final UntypedActor createAndInject() {
            try {
                UntypedActor untypedActor = create();

                Class<?> aClass = getActorClass();
                for (Field field : aClass.getDeclaredFields()) {

                    if (field.getAnnotation(Autowired.class) != null) {
                        boolean accessible = field.isAccessible();
                        try {
                            setAccessible(field, true);
                            field.set(untypedActor, applicationContext.getBean(field.getType()));
                        } catch (IllegalAccessException e) {
                            throw new IllegalStateException("Unable to create actor instance", e);
                        } finally {
                            setAccessibleIgnoringExceptions(field, accessible);
                        }
                    }
                }
                return untypedActor;

            } catch (InstantiationException e) {
                throw new IllegalStateException("Unable to create actor instance", e);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException("Unable to create actor instance", e);
            }

        }

        protected abstract Class<?> getActorClass();

        protected abstract UntypedActor create() throws InstantiationException, IllegalAccessException;

    }

    private final class SpecificUntypedActorFactory extends AbstractUntypedActorFactory {

        private final UntypedActorFactory specificFactory;
        private volatile Class<?> actorClass;

        private SpecificUntypedActorFactory(UntypedActorFactory specificFactory) {
            this.specificFactory = specificFactory;
        }

        @Override
        protected Class<?> getActorClass() {
            return actorClass;
        }

        @Override
        protected UntypedActor create() throws InstantiationException, IllegalAccessException {
            UntypedActor untypedActor = (UntypedActor) specificFactory.create();
            actorClass = untypedActor.getClass();
            return untypedActor;
        }
    }

    private final class DefaultUntypedActorFactory extends AbstractUntypedActorFactory {
        private final Class<?> actorClass;

        public DefaultUntypedActorFactory(Class<?> actorClass) {
            this.actorClass = actorClass;
        }

        @Override
        protected Class<?> getActorClass() {
            return actorClass;
        }

        @Override
        protected UntypedActor create() throws InstantiationException, IllegalAccessException {
            return (UntypedActor) actorClass.newInstance();
        }
    }

    /**
     * This method must return a different instance upon every call.
     */
    @Override
    public UntypedActor create() {
        return dependencyInjectionFactory.createAndInject();
    }
}

Inside our custom SpringUntypedActorFactory is a Strategy pattern which allows us to inject Spring context available beans in an actor fields annotated with @Autowired.

Well, that's almost all. Now we need a bootstrap component and we are going to provide one using XML-less Spring configuration:

package org.honeysoft.akka;

import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import org.honeysoft.akka.actor.BusinessActor;
import org.honeysoft.akka.di.DependencyInjectionProps;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

@Configuration
@ComponentScan({"org.honeysoft.akka.service"})
public class Bootstrap {

    public static final String BUSINESS_ACTOR = "honeysoft-business-actor";
    public static final String ACTOR_SYSTEM = "honeysoft-actor-actorSystem";
    private ActorSystem actorSystem;

    @Autowired
    private ApplicationContext applicationContext;

    @Bean(name = ACTOR_SYSTEM, destroyMethod = "shutdown")
    public ActorSystem actorSystem() {
        actorSystem = ActorSystem.create(ACTOR_SYSTEM);
        return actorSystem;
    }

    @Bean(name = BUSINESS_ACTOR)
    @DependsOn({ACTOR_SYSTEM})
    public ActorRef businessActor() {
        return actorSystem.actorOf(//
                new DependencyInjectionProps(applicationContext, BusinessActor.class), BUSINESS_ACTOR);
    }
}

Oh, you can also inject our BusinessActor in another Spring enabled service (or component in general) like this:


import akka.actor.ActorRef;
import org.honeysoft.akka.Bootstrap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service
public class MyCoolService {

    @Autowired
    @Qualifier(Bootstrap.BUSINESS_ACTOR)
    private ActorRef businessActor;
    
    public void doSomething() {
       businessActor.tell("message"); 
       // more logic goes here ...
    }
}

That was all folks. Now it's up to you to leave a comment or contribute on GitHub.

Ivan Hristov

Ivan Hristov

I am a lead software engineer working on software topics related to cloud, machine learning, and site reliability engineering.

Read More