Spring-wired actor

由於 actor 例項化的非常具體的方式,將依賴項注入到 actor 例項中並非易事。為了干預 actor 例項化並允許 Spring 注入依賴項,應該實現幾個 akka 擴充套件。首先是 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;
    }

}

此生成器在返回 actor 例項之前例項化 actor 並注入依賴項。

我們可以通過以下方式使用 SpringWiredActorProducer 為建立演員準備 Props

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

但是最好將該呼叫包裝到以下 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;
    }

}

你可以在建立 actor 的任何地方(甚至在 actor 本身中)自動裝配 SpringProps,並通過以下方式建立 spring-wired actor:

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

假設 ActorClass 擴充套件了 UntypedActor 並且具有用 @Autowired 註釋的屬性,那麼這些依賴關係將在例項化後立即注入。