将运行时(Meta)数据传递给CDI中的生产者方法

时间:2015-12-08 14:59:52

标签: java java-ee ejb cdi

我正在维护一个多租户应用程序,其中有关请求(标题,参数)的特殊元数据标识特定租户。每个租户都在系统中具有覆盖某些默认值的自定义配置。配置来自EJB前端的缓存增强型数据库。要成功查找一个此类自定义配置,需要密钥和租户标识符。如果租户标识符不存在,则仅使用密钥来检索密钥条目的默认值。

从接收这些请求的远程接口(servlet,Web服务等),我想要检索这些标识符和设置上下文(例如将属性放在EJBContext中),以便生产者方法可以利用来设置适当的bean为每个租户的客户提供服务。理想情况下,我也希望在这种情况下支持CDI而不是合理的。

我正在考虑以下策略,但我陷入困境。

  1. 创建@Config限定符,以便CDI容器解析为配置生成器。
  2. 创建@Key(String)配置注释,通过该注释可以获取所需配置条目的查找键。
  3. 创建一个以InjectionPoint为参数的Producer方法。 InjectionPoint允许获取@Key注释,被定义的Field的声明类型以及声明此注入字段的类(封闭类)。如果InjectionPoint允许我获取封闭类的实例,那么一个甜蜜的场景就是如此。但是考虑到这一点,这没有意义,因为实例在创建/定位和注入所有依赖项之前还没有准备好。
  4. 这是CDI不适合的情况吗?怎么能最好地实现呢?

1 个答案:

答案 0 :(得分:1)

一种可能的解决方案是在请求处理中提取重要的租户值,例如ServletFilter或某些拦截器并将其存储在ThreadLocal持有者中。这只有在两个组件(例如过滤器和CDI生成器)在同一个线程中执行时才会起作用 - 因此,您可能会遇到EJB的问题。
您可以在@Produces方法中检索租户标识符,并根据@Key注释值和租户ID返回配置条目。

一些伪解决方案:

ThreadLocal holder

public class ThreadLocalHolder {

  private static ThreadLocal<String> tenantIdThreadLocal = new ThreadLocal<>();

  public static String getTenantId(){
    return tenantIdThreadLocal.get();
  }
  public static void setTenantId(String tenantid){
    return tenantIdThreadLocal.set(tenantid);
  }
}

请求过滤器提取租户

@WebFilter(value = "/*")
public class TenantExtractorFilter implements Filter {
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    //obtain tenant id, and store in threadlocal
    ThreadLocalHolder.setTenantId(req.getHeader("X-TENANT"));
    chain.doFilter(request, response);
  }
}

配置条目制作者

public class Producer {

  //get a hold of some DAO or other repository of you config
  private ConfigRepository configRepo;

  @Produces
  @Config
  public String produceConfigEntry(InjectionPoint ctx) {
    Key anno = //get value of @Key annotation from the injection point, bean, property...
    String tenantId = ThreadLocalHolder.getTenantId();
    // adjust to your needs
    return configRepo.getConfigValueForTenant(anno.value(), tenantId);
  }
}

如果ThreadLocal不是一个选项,请查看javax.transaction.TransactionSynchronizationRegistry - 无论线程池如何都可以使用,但显然需要事务存在。

2015年12月14日更新
使用请求范围bean作为数据持有者的替代方法

RequestScoped holder

@RequestScoped
public class RequestDataHolder {
  private String tenantId;

  public String getTenantId() {
    return this.tenantId;
  }

  public void setTenantId(String tenantId) {
    this.tenantId = tenantId;
  }
}

网页过滤

从请求中提取值并将其存储在我们的持有者中。

@WebFilter(value = "/*")
public class TenantExtractorFilter implements Filter {

  @Inject private RequestDataHolder holder;

  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    //obtain tenant id, and store in threadlocal
    holder.setTenantId(req.getHeader("X-TENANT"));
    chain.doFilter(request, response);
  }
}

CDI制作人 使用数据持有者并产生注入点的预期值。

public class Producer {

  //get a hold of some DAO or other repository of you config
  private ConfigRepository configRepo;
  @Inject
  private RequestDataHolder dataHolder; 

  @Produces
  @Config
  public String produceConfigEntry(InjectionPoint ctx) {
    Key anno = //get value of @Key annotation from the injection point, bean, property...
    String tenantId = holder.getTenantId();
    // adjust to your needs
    return configRepo.getConfigValueForTenant(anno.value(), tenantId);
  }
}

我们的RequestDataHolder bean可以注入任何CDI,EJB,JAXRS或Servlet组件,从而允许将变量从WEB上下文传递到其他上下文。

注意:此解决方案需要根据CDI规范将CDI容器与EJB和WEB容器正确集成。