如何在运行时选择Spring bean实例

10
根据传递给方法的参数,我需要从许多Spring bean中选择一个,这些bean是相同类的实现,但使用不同的配置参数。
例如,如果用户A调用该方法,则需要在A bean上调用,但如果是用户B,则需要调用完全相同的方法,只不过是在B bean上调用。
除了将所有bean放入映射中并从传递给我的方法的参数中派生密钥之外,是否有更“Springier”的方法来完成此操作?

一位同事提出的一个解决方案是使用@Autowired Map<String, BaseInterface>,这将以bean ID作为键进行填充 - 也许这可能是最不糟糕的解决方案。 - DeejUK
看一下类似的帖子。 http://stackoverflow.com/a/24525715/3796723 - linusdsunil
4个回答

14

在我们的项目中遇到这个问题,我们通过一个类似于工厂的类来解决它。客户端类 - 需要运行时获取bean的类 - 拥有一个工厂的实例,该实例是通过Spring注入的:

@Component
public class ImTheClient{

    @Autowired
    private ImTheFactory factory;

    public void doSomething(
            Parameters parameters) throws Exception{        
        IWantThis theInstance = factory.getInstance(parameters);        

    }

}

所以,IWantThis 实例依赖于 parameters 参数的运行时值。工厂实现如下:

@Component
public class ImTheFactoryImpl implements
        ImTheFactory {

    @Autowired
    private IWantThisBadly anInstance;
    @Autowired
    private IAlsoWantThis anotherInstance;

    @Override
    public IWantThis getInstance(Parameters parameters) {
        if (parameters.equals(Parameters.THIS)) {
            return anInstance;
        }

        if (parameters.equals(Parameters.THAT)) {
            return anotherInstance;
        }

        return null;
    }
}

因此,工厂实例持有对 IWantThis 类的两个可能值的引用,即 IWantThisBadlyIAlsoWantThis,它们都是 IWantThis 的实现。


一个好的解决方案! :) - Andrei G

2
似乎你想使用应用程序上下文作为注册表来创建一个 ServiceLocator。
请参考 ServiceLocatorFactoryBean 支持类,以创建将键映射到 bean 名称的 ServiceLocators,而不将客户端代码与 Spring 耦合。
另一个选择是使用基于命名约定或注释的配置。
例如,假设你使用 @ExampleAnnotation("someId") 注释 Services,你可以使用类似以下的 Service Locator 来检索它们。
public class AnnotationServiceLocator implements ServiceLocator {

    @Autowired
    private ApplicationContext context;
    private Map<String, Service> services;

    public Service getService(String id) {
        checkServices();
        return services.get(id);
    }

    private void checkServices() {
        if (services == null) {
            services = new HashMap<String, Service>();
            Map<String, Object> beans = context.getBeansWithAnnotation(ExampleAnnotation.class);
            for (Object bean : beans.values()) {
                ExampleAnnotation ann = bean.getClass().getAnnotation(ExampleAnnotation.class);
                services.put(ann.value(), (Service) bean);
            }
        }
    }   
}

checkServices(); 方法只能在构造函数或初始化方法中调用一次吗? - Gerson Sosa

2
将它们放在地图上听起来不错。如果是Spring管理的地图(使用util:map或Java配置),那么比在其他地方创建更好,因为这样Spring拥有所有对象引用,并可以正确管理它们的生命周期。

-1
如果你所说的豆子(A,B)是SessionScope,那就没有问题了,它们会被正确地选择。
public class BusinessLogic {

  private BaseClassOfBeanAandB bean;

  public void methodCalledByUserAorB() {
    bean.doFoo();
  }

}

谢谢。在这种情况下没有HTTP会话,并且每个请求实例化的bean太重了。 - DeejUK
那么你如何区分用户A和B呢?是根据传递给方法的参数吗? - Manuel
是的。有一些预认证,所以我们会收到用户ID(实际上是几个参数组合而成的身份,但这不是重点)。 - DeejUK
谢谢你毫无理由地给我点踩。原帖并没有说明没有HTTP会话,所以在那个时候我怎么知道我的回答是不正确的呢? - Manuel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接