Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/grails/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何基于租户在运行时选择spring配置?_Spring_Grails_Multi Tenant - Fatal编程技术网

如何基于租户在运行时选择spring配置?

如何基于租户在运行时选择spring配置?,spring,grails,multi-tenant,Spring,Grails,Multi Tenant,我希望能够根据用户在运行时所属的租户选择特定的Spring(或Grails)上下文配置。假设我使用Spring安全性,并在登录期间检索tenantId。 想象一下,现在我有两个租户,他们支付不同的佣金。如何在没有太多管道的情况下将特定服务注入控制器?这里有两种不同的背景。所以,我应该根据租户注入不同的ExchangeService @Configuration public class FooTenant{ @Bean public ExchangeService bar() { retur

我希望能够根据用户在运行时所属的租户选择特定的Spring(或Grails)上下文配置。假设我使用Spring安全性,并在登录期间检索tenantId。 想象一下,现在我有两个租户,他们支付不同的佣金。如何在没有太多管道的情况下将特定服务注入控制器?这里有两种不同的背景。所以,我应该根据租户注入不同的ExchangeService

@Configuration
public class FooTenant{
@Bean
public ExchangeService bar() {
  return new ZeroCommisionExchangeService ();
  }
}


@Configuration
public class BarTenant{
@Bean
public ExchangeService bar() {
  return new StandardCommisionExchangeService ();
  }
}
编辑:
我知道我可以获得对Spring上下文的引用并“手动”请求服务,但我正在寻找一种更通用的解决方案,在这种解决方案中,这个问题可以通过IoC框架来解决。

假设您已经定义了不同的服务,您可以从上下文中获取它们的bean并使用它。在我的示例中,所有服务都实现了
serviceMethod
,并根据一些标准选择合适的服务。我唯一不确定的是,
多租户
会如何影响这一点

import org.springframework.context.ApplicationContext

class ServiceManagerController {
    def serviceManager

    def index() {
        ApplicationContext ctx = grails.util.Holders.grailsApplication.mainContext
        serviceManager = ctx.getBean(params.serviceName); //firstService or secondService
        render serviceManager.serviceMethod()
    }
}
第一次服务

class FirstService {

    def serviceMethod() {
        return "first"
    }
}
第二服务:

class SecondService {

    def serviceMethod() {
        return "second"
    }
}

在回答中转换我的评论,一个可能的解决方案是创建一个spring工厂bean,它接收所有他需要的信息,以决定在创建实例时需要返回哪个服务

转换为Grails:

public interface ChoosableServiceIntf {
  String getName();
}

class NormalService implements ChoosableServiceIntf {
  public String getName() {
    return getClass().name;
  }
}

class ExtendedService implements ChoosableServiceIntf {
  public String getName() {
    return getClass().name
  }
}

class ChoosableServiceFactory {

  static ChoosableServiceIntf getInstance(String decisionParam) {

    if(decisionParam == 'X') {
      return applicationContext.getBean('extendedService')
    }
    return applicationContext.getBean('normalService')
  }

  static ApplicationContext getApplicationContext() {
    return Holders.grailsApplication.mainContext
  }
}
这里我们有两个服务,
ChoosableServiceFactory
负责知道哪个是正确的

然后需要使用方法
ApplicationContext#getBean(String,Object[])
返回正确的实例,并且由于运行时参数的原因,还将使工厂
成为原型范围

要测试它的控制器:

class MyController {

  def grailsApplication

  def index() {
    ChoosableServiceIntf service = grailsApplication.mainContext.getBean('choosableServiceFactory', ["X"] as Object[])
    ChoosableServiceIntf serviceNormal = grailsApplication.mainContext.getBean('choosableServiceFactory', ["N"] as Object[])

    render text: "#1 - ${service.class.name} , #2 - ${serviceNormal.class.name}"
  }
}
这将打印
#1-dummy.ExtendedService,#2-dummy.NormalService

bean的声明将是:

choosableServiceFactory(ChoosableServiceFactory) { bean ->
  bean.scope = 'prototype'
  bean.factoryMethod = 'getInstance'
}
normalService(NormalService)
extendedService(ExtendedService)

我使用以下代码:

public class ConfigurableProxyFactoryBean implements FactoryBean<Object>, BeanNameAware {
    @Autowired
    private ApplicationContextProvider applicationContextProvider;

    private Class<?> proxyType;
    private String beanName;
    private Object object;
    private Object fallbackObject;
    private Object monitor = new Object();
    private ConfigurableProxy proxy;

    public ConfigurableProxyFactoryBean(Class<?> proxyType) {
        this.proxyType = proxyType;
    }

    public Object getFallbackObject() {
        return fallbackObject;
    }

    public void setFallbackObject(Object fallbackObject) {
        synchronized (monitor) {
            this.fallbackObject = fallbackObject;
            if (proxy != null) {
                proxy.setFallbackObject(fallbackObject);
            }
        }
    }

    @Override
    public void setBeanName(String name) {
        beanName = name;
    }

    @Override
    public Object getObject() throws Exception {
        synchronized (monitor) {
            if (object == null) {
                @SuppressWarnings("unchecked")
                Class<Object> type = (Class<Object>)proxyType;
                proxy = new ConfigurableProxy(applicationContextProvider, beanName);
                proxy.setFallbackObject(fallbackObject);
                object = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                        new Class<?>[] { type }, proxy);
            }
            return object;
        }
    }

    @Override
    public Class<?> getObjectType() {
        return proxyType;
    }

    @Override
    public boolean isSingleton() {
        return true;
   }
}

class ConfigurableProxy implements InvocationHandler {
    public ConfigurableProxy(ApplicationContextProvider appContextProvider, String beanName) {
        this.appContextProvider = appContextProvider;
        this.beanName = beanName;
    }

    private ApplicationContextProvider appContextProvider;
    private String beanName;
    private Object fallbackObject;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        ApplicationContext appContext = appContextProvider.getApplicationContext();
        String name = "$&&#" + beanName;
        Object bean = appContext.containsBean(name) ? appContext.getBean(name) : fallbackObject;
        return method.invoke(bean, args);
    }

    public void setFallbackObject(Object fallbackObject) {
        this.fallbackObject = fallbackObject;
    }
}
在坦能配置中:

<bean class="my.package.service.ServiceImplementationA" name="$&&#beanName"/>

您还将实现
ApplicationContextProvider
,我将不与您分享我的。实施起来并不难。例如,您的实现可以只在
ThreadLocal
中存储上下文。您可以创建自己的
ServletContextListener
,它为每个查询获取当前的tennant,并将其存储到您的
ApplicationContextProvider
实现中。

几年前,我们需要这样的东西,但只适用于
数据源
视图解析程序
。我们使用spring'
TargetSource
解决方案开发了一个解决方案。(最初我们使用的是
热插拔TargetSource
,但这不适合我们的用例

我们开发的代码可在多租户目录中找到

它是完全可配置和灵活的

基本上,您要做的是配置一个
ContextSwappableTargetSource
,并告诉它需要返回什么类型的接口/类

<bean id="yourTentantBasedServiceId"  class="biz.deinum.multitenant.aop.target.ContextSwappableTargetSource">
    <constructor-arg value="ExchangeService" />
</bean>
现在对于foo,它将查找名为
fooExchangeService
的bean,对于bar
barExchangeService

tenantId存储在一个
ThreadLocal
中,它被包装在
ContextHolder
中。您需要找到一种方法来填充和清除这个线程本地(通常servlet
过滤器会执行此操作)

在您的代码中,您现在只需使用接口
ExchangeService
,并且在运行时基于
tenantId
将查找正确的实现


另请参见新租户范围和servicelocator可以提供帮助

租户范围将保证为租户创建一次以上的服务

示例代码:

<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="tenant" value="foo.TenantScope"/>
        </map>
    </property>
</bean>

<bean id="service" class="foo.Service" factory-bean="tenantServiceLocator" factory-method="createInstance" scope="tenant"/>

<bean id="fooService" class="FooService">
<bean id="barService" class="BarService">

<bean id="tenantServiceLocator" class="foo.TenantServiceLocator">
    <property name="services">
        <map>
            <entry key="foo" value-ref="fooService"/>
            <entry key="bar" value-ref="barService"/>
        </map>
    </property>
</bean>

TenantServiceLocator应该知道用户tenantId

public class TenantServiceLocator {
    private Map<String, Service> services;

    public String getTenantId() {
        return "foo"; // get it from user in session
    }

    public Map<String, Service> getServices() {
        return services;
    }

    public void setServices(Map<String, Service> services) {
        this.services = services;
    }

    public Service createInstance(){
        return services.get(tenantId);
    }
}

public class FooController{
    @Autowired
    private Service service;
}
公共类租户服务定位器{
私人地图服务;
公共字符串getTenantId(){
返回“foo”;//在会话中从用户处获取
}
公共地图服务(){
返回服务;
}
公共void设置服务(地图服务){
这是服务=服务;
}
公共服务createInstance(){
返回服务。获取(租户ID);
}
}
公共类FooController{
@自动连线
私人服务;
}
一个示例TenantScope实现

public class TenantScope implements Scope {
    private static Map<String, Map<String, Object>> scopeMap = new HashMap<String, Map<String, Object>>();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> scope = getTenantScope(getTenantId());

        Object object = scope.get(name);
        if(object == null){
            object = objectFactory.getObject();
            scope.put(name, object);
        }

        return object;
    }

    private Map<String, Object> getTenantScope(String tenantId) {
        if (!scopeMap.containsKey(tenantId)) {
            scopeMap.put(tenantId, new HashMap<String, Object>());
        }

        return scopeMap.get(tenantId);
    }

    private String getTenantId() {
        return "foo"; // load you tenantId
    }

    @Override
    public Object remove(String name) {
        Map<String, Object> scope = getTenantScope(getTenantId());
        return scope.remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return null;
    }
}
公共类租户作用域实现作用域{
私有静态映射scopeMap=newhashmap();
@凌驾
公共对象获取(字符串名称,ObjectFactory ObjectFactory){
Map scope=gettenatscope(getTenantId());
Object=scope.get(name);
if(object==null){
object=objectFactory.getObject();
范围.放置(名称、对象);
}
返回对象;
}
私有映射getTenantScope(字符串tenantId){
如果(!scopeMap.containsKey(tenantId)){
put(tenantId,newhashmap());
}
返回scopeMap.get(tenantId);
}
私有字符串getTenantId(){
返回“foo”;//加载您的租户
}
@凌驾
公共对象删除(字符串名称){
Map scope=gettenatscope(getTenantId());
返回范围。删除(名称);
}
@凌驾
public void registerDestructionCallback(字符串名称,可运行回调){
}
@凌驾
公共对象ResolveContextaObject(字符串键){
返回null;
}
@凌驾
公共字符串getConversationId(){
返回null;
}
}

虽然可以在运行时交换spring上下文中实例化的bean(
HotswappableTargetSource
),但它并不适用于像您这样的用例

请记住,应用程序只有一个Spring上下文,所有线程都使用相同的实例(在大多数情况下),这意味着当您交换bean实现时,您实际上是在为应用程序的所有用户执行此操作。为了防止这种情况,您遇到了确保线程安全、使用线程局部变量的问题,如另一个答案中所列

虽然这是可能的
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
    <property name="scopes">
        <map>
            <entry key="tenant" value="foo.TenantScope"/>
        </map>
    </property>
</bean>

<bean id="service" class="foo.Service" factory-bean="tenantServiceLocator" factory-method="createInstance" scope="tenant"/>

<bean id="fooService" class="FooService">
<bean id="barService" class="BarService">

<bean id="tenantServiceLocator" class="foo.TenantServiceLocator">
    <property name="services">
        <map>
            <entry key="foo" value-ref="fooService"/>
            <entry key="bar" value-ref="barService"/>
        </map>
    </property>
</bean>
public class TenantServiceLocator {
    private Map<String, Service> services;

    public String getTenantId() {
        return "foo"; // get it from user in session
    }

    public Map<String, Service> getServices() {
        return services;
    }

    public void setServices(Map<String, Service> services) {
        this.services = services;
    }

    public Service createInstance(){
        return services.get(tenantId);
    }
}

public class FooController{
    @Autowired
    private Service service;
}
public class TenantScope implements Scope {
    private static Map<String, Map<String, Object>> scopeMap = new HashMap<String, Map<String, Object>>();

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        Map<String, Object> scope = getTenantScope(getTenantId());

        Object object = scope.get(name);
        if(object == null){
            object = objectFactory.getObject();
            scope.put(name, object);
        }

        return object;
    }

    private Map<String, Object> getTenantScope(String tenantId) {
        if (!scopeMap.containsKey(tenantId)) {
            scopeMap.put(tenantId, new HashMap<String, Object>());
        }

        return scopeMap.get(tenantId);
    }

    private String getTenantId() {
        return "foo"; // load you tenantId
    }

    @Override
    public Object remove(String name) {
        Map<String, Object> scope = getTenantScope(getTenantId());
        return scope.remove(name);
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {
    }

    @Override
    public Object resolveContextualObject(String key) {
        return null;
    }

    @Override
    public String getConversationId() {
        return null;
    }
}