akka

Injecting dependencies into an actor

Spring-wired actor

Due to very specific way of actor instantiation, injecting dependencies into an actor instance is not trivial. In order to intervene in actor instantiation and allow Spring to inject dependencies one should implement a couple of akka extensions. First of those is an IndirectActorProducer:

import akka.actor.Actor;
import akka.actor.IndirectActorProducer;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;

/**
 * An actor producer that lets Spring autowire dependencies into created actors.
 */
public class SpringWiredActorProducer implements IndirectActorProducer {

    private final ApplicationContext applicationContext;
    private final Class<? extends Actor> actorBeanClass;
    private final Object[] args;

    public SpringWiredActorProducer(ApplicationContext applicationContext, Class<? extends Actor> actorBeanClass, Object... args) {
        this.applicationContext = applicationContext;
        this.actorBeanClass = actorBeanClass;
        this.args = args;
    }

    @Override
    public Actor produce() {
        Class[] argsTypes = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] == null) {
                argsTypes[i] = null;
            } else {
                argsTypes[i] = args[i].getClass();
            }
        }
        Actor result = null;
        try {
            if (args.length == 0) {
                result = (Actor) actorBeanClass.newInstance();
            } else {
                try {
                    result = (Actor) actorBeanClass.getConstructor(argsTypes).newInstance(args);
                } catch (NoSuchMethodException ex) {
                    // if types of constructor don't match exactly, try to find appropriate constructor
                    for (Constructor<?> c : actorBeanClass.getConstructors()) {
                        if (c.getParameterCount() == args.length) {
                            boolean match = true;
                            for (int i = 0; match && i < argsTypes.length; i++) {
                                if (argsTypes[i] != null) {
                                    match = c.getParameters()[i].getType().isAssignableFrom(argsTypes[i]);
                                }
                            }
                            if (match) {
                                result = (Actor) c.newInstance(args);
                                break;
                            }
                        }
                    }
                }
            }
            if (result == null) {
                throw new RuntimeException(String.format("Cannot find appropriate constructor for %s and types (%s)", actorBeanClass.getName(), Arrays.toString(argsTypes)));
            } else {
                applicationContext.getAutowireCapableBeanFactory().autowireBeanProperties(result, AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE, true);
            }
        } catch (ReflectiveOperationException e) {
            throw new RuntimeException("Cannot instantiate an action of class " + actorBeanClass.getName(), e);
        }
        return result;
    }

    @Override
    public Class<? extends Actor> actorClass() {
        return (Class<? extends Actor>) actorBeanClass;
    }

}

This producer instantiates an actor and injects dependencies before returning the actor instance.

We can prepare Props for creation an actor using the SpringWiredActorProducer the following way:

Props.create(SpringWiredActorProducer.class, applicationContext, actorBeanClass, args);

However it would be better to wrap that call into following spring bean:

import akka.actor.Extension;
import akka.actor.Props;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * An Akka Extension to inject dependencies to {@link akka.actor.Actor}s with
 * Spring.
 */
@Component
public class SpringProps implements Extension, ApplicationContextAware {

    private volatile ApplicationContext applicationContext;

    /**
     * Creates a Props for the specified actorBeanName using the
     * {@link SpringWiredActorProducer}.
     *
     * @param actorBeanClass The class of the actor bean to create Props for
     * @param args arguments of the actor's constructor
     * @return a Props that will create the named actor bean using Spring
     */
    public Props create(Class actorBeanClass, Object... args) {
        return Props.create(SpringWiredActorProducer.class, applicationContext, actorBeanClass, args);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

}

You can autowire SpringProps anywhere an actor gets created (even in the actor itself) and create spring-wired actors the following way:

@Autowired
private SpringProps springProps;
//...
actorSystem.actorOf(springProps.create(ActorClass.class), actorName);
//or inside an actor
context().actorOf(springProps.create(ActorClass.class), actorName);

Assuming that ActorClass extends UntypedActor and has properties annotated with @Autowired, those dependencies will be injected right after instantiation.


This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow