在 Play Framework 中根据参数注入依赖

Inject dependency based on parameter in Play Framework

我在数据库中跟踪 consumerId 的服务提供商列表。

我有一个 SchedulerActorconsumerId 启用的每个服务提供商创建 TaskActor

我想为列表中的每个服务提供商注入 ServiceProviderTaskActor 的实现。 ServiceProvider.

可以有不同的实现
interface ServiceProvider {
    void processRequest(Request request);
}

class SchedulerActor {
     @Override
     public void onReceive(Object consumerId) throws Exception {
         List<String> serviceProvidersList = getFromDatabase((String) consumerId);
         for (String serviceProviderStr : serviceProvidersList) {

              //serviceProviderStr could be "ServiceProvider1" or "ServiceProvider2" 
              ServiceProvider serviceProvider = getServiceProviderFromStr(serviceProviderStr); 

              ActorRef r = getContext().actorOf(Props.create(TaskActor.class, serviceProvider));
              getContext().watch(r);
              routees.add(new ActorRefRoutee(r));

         }
         router = new Router(new BroadcastRoutingLogic(), routees);
         router.route(message, getSelf());
     }
}

class TaskActor {
    private final ServiceProvider serviceProvider;
    public TaskActor(ServiceProvider serviceProvider) {
        this.serviceProvider = serviceProvider;
    }

    @Override
    public void onReceive(Object message) {
        if (message instanceof Request) {
            serviceProvider.processRequest((Request)message);
        }
    }
}
  1. 为了实现这一目标,getServiceProviderFromStr(String) 里面有什么?
  2. 我不想将 switchif else 语句添加到 return 所需的实现。
  3. 我可以在运行时提供提供程序绑定,将字符串传递给提供程序对象吗?

如果你不想使用 switchif else,我认为你必须使用反射来完成,其中 serviceProviderStr 是 class 名称.

    private ServiceProvider getServiceProviderFromStr(String serviceProviderClassName){
        try {
            Class<?> clazz = Class.forName(serviceProviderClassName);
            if(!ServiceProvider.class.isAssignableFrom(clazz)){
                throw new IllegalArgumentException("Argument is not a ServiceProvider class.");
            }
            Class<? extends ServiceProvider> serviceProviderClass = (Class<? extends ServiceProvider>) clazz;
            return serviceProviderClass.newInstance();
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
        } 

        return null;
    }

请记住,您可以通过调用 AnyClass.class.getName(); 获得 class 名称。

使用 Guice AssistedInject, namely the FactoryModuleBuilder 向 TaskActor 构造函数添加服务名称的字符串参数,并使用此参数注入带有 @Named 注释的 ServiceProvider:

  1. 在模块中定义一个工厂接口:

    public interface TaskActorFactory{
        TaskActor create(String serviceName);
    }
    
  2. 在模块 configure 方法中安装工厂并注册服务绑定:

    protected void configure() {
        bind(ServiceProvider.class).annotatedWith(Names.named("serviceA")).to(ServiceA.class);
        bind(ServiceProvider.class).annotatedWith(Names.named("serviceB")).to(ServiceB.class);
        bind(ServiceProvider.class).annotatedWith(Names.named("serviceC")).to(ServiceC.class);
        install(new FactoryModuleBuilder()
            .implement(TaskActor.class, TaskActor.class)
            .build(TaskActorFactory.class));
     }
    
  3. 实施 TaskActor:

    @Inject 
    class TaskActor extends UntypedActor {
    
        public static Props props(String serviceName) {
            return Props.create(TaskActor.class, ()->new TaskActor(serviceName));
        }
    
        private final ServiceProvider serviceProvider;
    
        public TaskActor(Injector injector, @Assisted String serviceName) {
            serviceProvider = injector.getInstance(Key.get(ServiceProvider.class, Names.named(serviceName)));
        }
        ...
    }
    
  4. 从 SchedulerActor 创建 TaskActor

    @Override
    public void onReceive(Object consumerId) throws Exception {
         List<String> serviceProvidersList = getFromDatabase((String) consumerId);
         for (String serviceProviderStr : serviceProvidersList) {
             ActorRef r = getContext().actorOf(TaskActor.props(serviceProviderStr));
             getContext().watch(r);
             routees.add(new ActorRefRoutee(r));
         }
         ...
     }
    

    }

另一种使用相同命名绑定而不是提议的@Assisted 的方法

@Inject 
public Injector injector;
...
ServiceProvider s = injector.getInstance(Key.get(Parent.class, Names.named(serviceName)));

其中 类 在 configure() 中绑定:

bind(ServiceProvider.class).annotatedWith(Names.named("Name1")).to(Child1.class);
bind(ServiceProvider.class).annotatedWith(Names.named("Name2")).to(Child2.class);