在运行时通过条件进行依赖注入

Dependency injection by the condition in the runtime

假设有如下代码:

@RestController
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class Controller {

    /* inject here */
    public @NonNull GenericService service;

    public void doo(List<GenericDTO> list, String type) {
        service.doo(list);
    }

}

class GenericDTO { ... }
class GenericService { ... }

class OfferService extends GenericService { ... }
class OtherService extends GenericService { ... }

如果 doo 方法的 type 等于 "offer",我想向 service 注入一个 OfferService 实例。如果我收到 "other",我必须使用 OtherService,依此类推。这可能吗?

我假设你所有的 bean 都是单例。

单例 bean 在应用程序启动时发生的上下文初始化期间全部初始化和注入,即。您的 doo 方法(我假设它是一个处理程序方法)尚未被调用。 Spring 到 guess/know 无法调用某些方法的可能值是什么,或者这些值应该如何以任何方式影响目标注入点。

也许代理可以在调用 doo 期间帮助确定要获取的真实 bean。这很困难,因为您必须以某种方式将 type 的值暴露给执行解析的任何组件。 Spring 开箱即用。您必须编写自己的作品来执行此操作,可能需要借助您自己的 Scope 实现。 (这不是微不足道的。)

最简单的解决方案是在 type 上同时注入两个服务和 if-else/switch,然后相应地选择一个。或者将两个服务放在 Map 中,type 是键。

每当我需要在运行时选择实现时,我都会使用以下 2 种模式之一来完成此操作。这绝对不是您要的 - bean 不是有条件地注入的,而是有条件地调用的。

此外,如果您有一小部分 GenericService 实现,这将很优雅。如果它是一个更大的集合或者如果 class 层次结构更深,那么这可能不是那么优雅的解决方案。

但是,这实现了在添加新的 GenericService 实现时不必更改调用代码的目标,同时还使您能够将不同的服务实现子集注入到不同的控制器中。

Class MyController {

    Map<String, GenericService> servicesMap = new HashMap<String, GenericService>(); 

    @Autowired
    public setServices(List<GenericService> serviceList) {
        for (GenericService service : serviceList) {
             if (service instanceof OfferService) {
                serviceMap.put("offer", service);
             }
             if (service instanceof OtherService) {
                serviceMap.put("other", service);
             }
    }

    public void doo(List<GenericDTO> list, String type) {
        GenericService service = serviceMap.get(type);
        if (null != service) {
             service.doo(list);
        }
    }
}

另一种可能且可能更优雅的解决方案是让实现 class 指明它实际支持的类型。

Class GenericService {

     // This method could very well be an abstract method or a simple
     // getter for a property set through ctor

     public String getType() {
          return "unknown"; // or "generic" if you please
     }

}

Class OfferClass extends GenericService {
     @Override
     public String getType() {
          return "offer";
     }
}

Class OtherClass extends GenericService {
     @Override
     public String getType() {
          return "other";
     }
}

Class MyController {

    Map<String, GenericService> servicesMap = new HashMap<String, GenericService>(); 
    // This is just there so that we do not have to process the 
    // list everytime we need to invoke the service

    @Autowired
    public setServices(List<GenericService> serviceList) {
        for (GenericService service : serviceList) {
            serviceMap.put(service.getType(), service)
        }
    }

    public void doo(List<GenericDTO> list, String type) {
        GenericService service = serviceMap.get(type);
        if (null != service) {
             service.doo(list);
        }
    }
}