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 注释的属性,那么这些依赖关系将在实例化后立即注入。