带有Guice Custom Scope和Jersey的多租户

cho

我正在开发使用Guice for DI与Jersey合作的多租户应用程序(我也使用Dropwizard,但我认为这并不重要)。

让我困扰的一件事是tenancy_id,我的应用程序中到处都有某种形式的事实我的大多数网址如下所示:/:tenancy_id/some_resource/do_stuff因此,我的Jersey资源中的方法用调用,tenancy_id并将其移交给调用其他服务的服务,依此类推。这些服务针对不同的租户配置不同。

我设法通过使用解决了这个问题@RequestScoped TenancyIdProdiver

public class TenancyIdProvider {

    @Inject
    public TenancyIdProvider(HttpServletRequest request) {
        this.request = request;
    }

    public TenancyId getTenancyId() {
        // extract the tenancy id from the request
    }
}

`

我的GuiceModule包含以下方法:

@RequestScoped 
public TenancyId getTenancyId(TenancyIdProvider tenancyIdFactory) { 
    return tenancyIdFactory.getTenancyId(); 
}

public SomeTenancyService getTenancyId(TenancyId tenancyId, Configuration configuration) { 
    return new SomeTenancyService(configuration.getConfiguration(tenancyId)); 
}

因此,现在我无需担心服务的正确配置。所有这些都由DI容器处理,并且该应用程序与租户无关,在这里它不关心租户。

我的问题是:所有这些服务和资源都是在每个请求上创建的,因为它们都具有@RequestScoped依赖性。这根本不可行。所以我的想法是用guice创建一个自定义范围。因此,每个租户都将获得其自己的对象图,其中所有资源和服务均已正确配置(但仅配置一次)。我按照此处的示例进行尝试,但是我不确定使用Guice的自定义范围是否可以实现这一点。从Jersey的角度来看,我需要在哪里输入自定义范围?ContainerRequestFilter正确的方法吗?

cho

我终于自己弄清楚了。关于自定义范围的Guice页面是一个很好的起点。我需要稍微调整一下。

首先,我创建了一个@TenancyScoped注释:

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@ScopeAnnotation
public @interface TenancyScoped { }

然后,我使用了请求过滤器:

@PreMatching
public class TenancyScopeRequestFilter implements ContainerRequestFilter {

   private final TenancyScope      scope;

   @Inject
   public TenancyScopeRequestFilter(TenancyScope scope) {
      this.scope = scope;
   }

   @Override
   public void filter(ContainerRequestContext requestContext) throws IOException {
      Optional<TenancyId> tenancyId = getTenancyId(requestContext);

      if (!tenancyId.isPresent()) {
         scope.exit();
         return;
      }
      scope.enter(tenancyId.get());
   }

   private Optional<TenancyId> getTenancyId(ContainerRequestContext requestContext) {
   }
}

请注意@PreMatching注释。过滤每个请求很重要,否则您的代码可能行为异常(范围可能设置不正确)。

这是TenancyScope实现:

 public class TenancyScope implements Scope, Provider<TenancyId> {

     private final Logger                                        logger             = LoggerFactory.getLogger(TenancyScope.class);

     private final ThreadLocal<Map<TenancyId, Map<Key<?>, Object>>> tenancyIdScopedValues = new ThreadLocal<>();
     private final ThreadLocal<TenancyId>                           tenancyId             = new ThreadLocal<>();

     public void enter(TenancyId tenancyId) {
        logger.debug("Enter scope with tenancy id {}", tenancyId);

        if (this.tenancyIdScopedValues.get() == null) {
           this.tenancyIdScopedValues.set(new HashMap<>());
        }

        this.tenancyId.set(tenancyId);
        Map<Key<?>, Object> values = new HashMap<>();
        values.put(Key.get(TenancyId.class), tenancyId);
        this.tenancyIdScopedValues.get().putIfAbsent(tenancyId, values);
     }

     public void exit() {
        logger.debug("Exit scope with tenancy id {}", tenancyId.get());

        this.tenancyId.set(null);
     }

     public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
        return new Provider<T>() {
           public T get() {
              logger.debug("Resolve object with key {}", key);
              Map<Key<?>, Object> scopedObjects = getScopedObjectMap(key);

              @SuppressWarnings("unchecked")
              T current = (T) scopedObjects.get(key);
              if (current == null && !scopedObjects.containsKey(key)) {
                 logger.debug("First time instance with key {} is in tenancy id scope {}", key, tenancyId.get());
                 current = unscoped.get();

                 // don't remember proxies; these exist only to serve circular dependencies
                 if (Scopes.isCircularProxy(current)) {
                    return current;
                 }
                 logger.debug("Remember instance with key {} in tenancy id scope {}", key, tenancyId.get());
                 scopedObjects.put(key, current);
              }
              return current;
           }
        };
     }

     private <T> Map<Key<?>, Object> getScopedObjectMap(Key<T> key) {
        Map<TenancyId, Map<Key<?>, Object>> values = this.tenancyIdScopedValues.get();
        if (values == null || tenancyId.get() == null) {
           throw new OutOfScopeException("Cannot access " + key + " outside of a scoping block with id " + tenancyId.get());
        }
        return values.get(tenancyId.get());
     }

     @Override
     public TenancyId get() {
        if (tenancyId.get() == null) {
           throw new OutOfScopeException("Cannot access tenancy id outside of a scoping block");
        }
        return tenancyId.get();
     }

  }

最后一步是在Guice模块中将所有内容连接在一起:

@Override
protected void configure() {
   TenancyScope tenancyScope = new TenancyScope();
   bindScope(TenancyScoped.class, tenancyScope);
   bind(TenancyScope.class).toInstance(tenancyScope);
   bind(TenancyId.class).toProvider(tenancyScope).in(TenancyScoped.class);
}

您现在拥有的是在每个请求之前设置的作用域,并且Guice提供的所有实例都按租户ID(也按线程)进行缓存,但是可以轻松更改。基本上,每个租户ID有一个对象图(类似于每个会话有一个)。

还要注意,TenancyScope该类同时充当ScopeTenancyId提供者。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

带有Cloudflare和Heroku的多租户SSL

带有Postgresql和Unicorn的多租户Rails应用

Azure AD多租户登录-带有microsoftonline.com和microsoftonline.de中的租户

带有DbContext池和多租户的EF Core-每个租户的数据库

带有多租户的Spring Batch

带有Play2.4和scala的Google Guice的循环依赖性错误

带有ASP.NET Core 3.0和EF Core的多租户应用程序

带有Custom ng-repeat指令的Custom Filter和LimitTo选项

带有MongoDB的Django多数据库多租户

带有Web App API的Azure AD Auth的多租户

带有代码优先EF6的多租户

带有弹簧数据弹性搜索的多租户

具有多租户和多域 JHipster 后端的 Ionic 中的 CSRF

Xamarin带有2个按钮和输入项的Forms Custom Stepper

带有PHP和jQuery Custom选择菜单的下拉列表

具有Spring Boot和Hibernate的多租户和中央数据库

Guice + Jersey:添加所有资源和提供程序而不绑定到Jersey Servlet

多个数据源和多租户之间有什么区别?

具有DbContext和TenantId的多租户-拦截器,过滤器,EF代码优先

配置Jetty,Jersey和Guice

带有Guice的现代Akka DI

Azure AD-具有守护程序服务和授权代码授予流的多租户,目标租户可以生成client_secret吗?

带有JWT令牌的Azure AD多租户.Net核心Web API

多租户如何在带有Objectify的App Engine中工作?

带有多租户的Rails 4 has_and_belongs_to_many(公寓)

具有 API 端点的多租户 CMS

具有多租户的Firestore规则?

具有联合和多对多关系的 Laravel Builder Scope

带有Jersey和嵌入式Jetty的CrossOriginFilter