首页 > 解决方案 > 如何在运行时选择要调用的spring bean

问题描述

原标题:如何为同一个接口声明性地配置多个bean并在运行时选择正确的一个

我最近有一个项目,对要实现的 Web 应用程序有以下需求:

当用户登录到系统时,她将指定一个范围(B2C、B2B)。在她使用应用程序时,必须根据当前登录的范围将一些对后端系统的调用路由到后端系统的不同实例。

Web 应用程序由两部分组成(前面是 SPA,后面是带有 REST 端点的 Spring Boot 应用程序)和后端系统实例的集合,例如 B2C 存档和 B2B 存档。

当前实现处理 REST 请求并决定调用哪个存档的登录范围。这可行,但我想让它声明性而不是显式编码。因此,为不同的范围添加新的后端实例应该很容易。

对于初学者,我想定义两个 bean,一个用于访问 B2C 档案,一个用于访问 B2B 档案。两者都将实现相同的接口;实际上它们是具有不同配置值的同一类的实例。

处理 REST 调用时,我希望 spring 根据当前登录的范围(B2C 或 B2B)选择要调用的正确 bean。

我有不同的想法来解决这个问题(Scoped Proxies、AOP、custom AutowireCandidateResolver、Object Pools),但目前我被卡住了,不能说我是否在正确的轨道上。

有没有人做过类似的事情?

更新

感谢Fritz DuchardtKen Bekov提出使用 Factory + Scoped Proxies 并将它们返回给调用代码的想法。

更新 2

我刚刚实现了一个能够运行以下测试的弹簧扩展(这里的完整代码:https ://gitlab.com/thuri/service-provider-proxy )。

内部类只是为了保持测试在一起。当它们位于单独的文件中时,它也应该起作用。

要点是,接口由两个不同的 bean 实现,spring 在运行时通过评估注释中的 spEL 表达式来决定哪个 bean 应该实际处理调用


  @Test
  public void testProxiedResourceInjectionWithField() {

    caseSwitch.switchValue = "B2B";
    assertEquals("Well you know ... ", client.proxiedService.doCoolStuff());

    caseSwitch.switchValue = "B2C";
    assertEquals("This will do ...", client.proxiedService.doCoolStuff());
  }


  public static class ProxiedServiceClient {
    @Autowired 
    public ProxiedServiceInterface proxiedService;
  }

  @ServiceProviderProxy
  public static interface ProxiedServiceInterface {
    public String doCoolStuff();
  }

  @ProxiedService(expression = "#{switch.switchValue == 'B2B'}")
  public static class B2BServiceImpl implements ProxiedServiceInterface {
    @Override
    public String doCoolStuff() {
      return "Well you know ... ";
    }
  }

  @ProxiedService(expression = "#{switch.switchValue == 'B2C'}")
  public static class B2CServiceImpl implements ProxiedServiceInterface {
    @Override
    public String doCoolStuff() {
      return "This will do ...";
    }
  }

  public static class CaseSwitch {
    public String switchValue = "";
  }

标签: spring-bootspring-security

解决方案


这可以与已设置为request 或 session scope的Spring Factory Bean一起使用,例如:

@Bean(name = "archive")
@Scope(value= "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public ArchiveFactory archiveFactory() {
    ArchiveFactory factory = new ArchiveFactory();
    return factory;
}

Spring 将使用 AOP 代理在运行时创建新的 bean,例如在每次请求时,使用您的工厂 bean 实现。您可以使用本地线程来传递当前上下文。


推荐阅读