Spring - 如何考虑Spring中的Beans多租户(多客户端可操作性)

时间:2016-09-12 09:43:29

标签: java spring client multi-tenant thread-local

我必须编写一个需要支持多租户/多客户端的应用程序(即对于每个客户端上下文另一个数据库,不同的业务逻辑bean等)。必须能够从一个客户端环境切换到#34;因此,在运行时,应根据实际客户端上下文从spring上下文返回正确的bean。一些信息/ bean与客户端无关(即一些基本业务逻辑,默认实现)。

我想知道如何在不关闭应用程序并为每个客户端上下文使用不同的bean定义文件的情况下在春天实现这一目标?

我通过使用线程本地上下文看到了具有"Routed DataSource"的解决方案。但要写一个" Bean路由器"对于每个Bean类型,使其成为客户端上下文"具体看起来对我来说太多开销,因此我寻求对基础弹簧框架的一些支持。

  • 对于使用spring框架的bean,如何考虑运行时的多客户端/多租户支持是否有可行的方法/好例子?
  • 我不是,有没有办法扩展BeanFactory和BeanDefintions,以便从给定的"客户端环境中获取Bean"先前在本地线程中设置过哪个?见下面的例子

XML:

<bean name="silverClientDataSource" class="ch.megloff.common.datasource.DataSource"/>
<bean name="goldClientDataSource" class="ch.megloff.common.datasource.DataSource"/>

的java:

ClientContext clientContext = applicationContext.getBean(ClientContext.class);
clientContext.switchContext("silverClient");
...
// get the datasource for silver Client
DataSource ds = applicationContext.getBean(DataSource.class);


ClientContext clientContext = applicationContext.getBean(ClientContext.class);
clientContext.switchContext("goldClient");
...
// get the datasource for gold Client
DataSource ds = applicationContext.getBean(DataSource.class);
// get oter beans for gold Client or return default implementation in case no context specific bean has been implemented
XXX bean =  applicationContext.getBean(XXX.class);

2 个答案:

答案 0 :(得分:0)

我从第一个虚拟实现开始,这可能有助于说明我正在寻找的行为。这个例子还没有使用Spring的BeanFactory,只是根据给定的上下文显示了工厂应该做什么,请参阅我的虚拟类ContextAwareBeanFactory。工厂正在寻找实现ContextAware接口的特定类型的bean,并使用给定的上下文来比较值。使用注释而不是使用接口可以实现相同的方法。

下一个级别是从Spring覆盖ApplicationContext和BeanFactory,因此这种方法也可以用于bean注入和自动装配的bean。如果有人能指出我正确的方向并避免让我监督任何陷阱,我将不胜感激。如果你看到我不知道的其他方法或框架技巧,请随意发表评论。谢谢。

@Log4j2
public class TestContextAware extends TestBase {

  @Test
  public void testContextAware() {
    Context<String> clientContext = new ThreadLocalContext<String>();       
    ContextAwareBeanFactory beanFactory = context.getBean(ContextAwareBeanFactory.class);
    beanFactory.setContext(clientContext);

    clientContext.setValue("silver");
    AbstractTestBean bean = beanFactory.getBean(AbstractTestBean.class);
    log.info(bean);

    clientContext.setValue("gold");
    bean = beanFactory.getBean(AbstractTestBean.class);
    log.info(bean);

  }
}

控制台输出

23:47:37.393 INFO - TestBean1@566e142
23:47:37.589 INFO - TestBean2@3b24087d

进一步的课程:

public static abstract class AbstractTestBean implements ContextAware {
    Object contextValue;
    @Override
    public void setContextValue(Object value) {
        contextValue = value;
    }
    @Override
    public Object getContextValue() {
        return contextValue;
    }
}

@Component
public static class TestBean1 extends AbstractTestBean {
    public TestBean1() {
        setContextValue("silver");
    }
}

@Component
public static class TestBean2 extends AbstractTestBean {
    public TestBean2() {
        setContextValue("gold");
    }
}


@Component
public class ContextAwareBeanFactory {
  @Autowired
  private ApplicationContext applicationContext;
  @Setter
  private Context<?> context;
  public <T> T getBean(Class<T> type) {
    Object contextValue = context.getValue();
    // check if bean implements "ContextAware" interface and if it matches the value
    if (contextValue != null) {
        Map<String,T> beans = applicationContext.getBeansOfType(type);
        for(T bean : beans.values()) {
            if (bean instanceof ContextAware) {
                if (contextValue.equals(((ContextAware)bean).getContextValue())) {
                    return bean;
                }
            }
        }
    }
    return applicationContext.getBean(type);
  }
}

@Component
public class ThreadLocalContext<T> implements Context<T> {
  private static ThreadLocal<Object> holder =  new InheritableThreadLocal<Object>();
  @SuppressWarnings("unchecked")
  public T getValue() {
    return (T)holder.get();
  }
  public void setValue(T value) {
    holder.set(value);
  }
  public void clear() {
    holder.remove();
  }
  @Override
  public String toString() {
    return String.valueOf(getValue());
  }
}

答案 1 :(得分:0)

在研究了几种方法之后,我得出的结论是,对我的问题最简单的解决方案是使用Spring中的"Priority" / "Order"功能来影响您希望在运行时为您的bean检索哪些实现&#34; getBean (班级类型)&#34;。

不建议覆盖ApplicationContext,因为此类存在多种类型的实现,并且它们不提供设置BeanFactory。这意味着你需要覆盖所有类型的ApplicationContext类以保证安全,如果spring的核心框架正在改变或得到更新,这将是危险的。

我唯一复杂且最灵活的方法是使用自己实现的&#34; BeanFactoryPostProcessor&#34;。这允许在&#34; DefaultListableBeanFactory&#34;上设置"DependencyComparator"。用于评估&#34;优先级&#34; /&#34;订单&#34;候选人豆类。

请在此处找到一个示例:

spring.xml

<bean id="tenantContext" class="ThreadLocalContext"></bean>
<bean class="ContextAwareBeanFactoryPostProcessor">
    <property name="context" ref="tenantContext"></property>
</bean>

public class ContextAwareBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

  ContextAwareComparator ContextAwareComparator = new ContextAwareComparator(); 

  public void setContext(Context<?> context) {
    ContextAwareComparator.setContext(context);
  }

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
      if (beanFactory instanceof DefaultListableBeanFactory) {     
((DefaultListableBeanFactory)beanFactory).setDependencyComparator(ContextAwareComparator);
      }
  } 
}

public class ContextAwareComparator extends AnnotationAwareOrderComparator {

  @Setter
  private Context<?> context;

  public Integer getPriority(Object obj) {
      if (context != null && obj != null) {
        Object value = context.getValue();
        if (value !=  null) {
            if (obj instanceof ContextAware) {
                    if (value.equals(((ContextAware)obj).getContextValue())) {
                        return Ordered.HIGHEST_PRECEDENCE;
                    }
            }
        }
      }
      return super.getPriority(obj);
    }
}

我希望将来Spring&#34; DefaultListableBeanFactory&#34;获得extendend以获得租赁支持。