我正在维护一个多租户应用程序,其中有关请求(标题,参数)的特殊元数据标识特定租户。每个租户都在系统中具有覆盖某些默认值的自定义配置。配置来自EJB前端的缓存增强型数据库。要成功查找一个此类自定义配置,需要密钥和租户标识符。如果租户标识符不存在,则仅使用密钥来检索密钥条目的默认值。
从接收这些请求的远程接口(servlet,Web服务等),我想要检索这些标识符和设置上下文(例如将属性放在EJBContext
中),以便生产者方法可以利用来设置适当的bean为每个租户的客户提供服务。理想情况下,我也希望在这种情况下支持CDI而不是合理的。
我正在考虑以下策略,但我陷入困境。
@Config
限定符,以便CDI容器解析为配置生成器。@Key(String)
配置注释,通过该注释可以获取所需配置条目的查找键。InjectionPoint
为参数的Producer方法。 InjectionPoint
允许获取@Key
注释,被定义的Field的声明类型以及声明此注入字段的类(封闭类)。如果InjectionPoint
允许我获取封闭类的实例,那么一个甜蜜的场景就是如此。但是考虑到这一点,这没有意义,因为实例在创建/定位和注入所有依赖项之前还没有准备好。这是CDI不适合的情况吗?怎么能最好地实现呢?
答案 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容器正确集成。