我正在维护一个 Multi-Tenancy 应用程序,其中关于请求的特殊元数据( header 、参数)标识特定租户。每个租户在系统中都有覆盖某些默认值的自定义配置。配置来自以 EJB 为前端的缓存增强数据库。要成功查找此类自定义配置,需要 key 和租户标识符。如果租户标识符不存在,则单独使用 key 来检索 key 条目的默认值。
我想从接收这些请求的远程接口(interface)(servlet、web 服务等)中检索此类标识符和设置上下文(例如,将属性放入 EJBContext
中),以便生产者方法可以利用设置适当的 beans 来为每个租户的客户提供服务。理想情况下,在这种情况下,我也希望尽可能合理地支持 CDI 而不是 EJB。
我在考虑以下策略,但我卡住了。
- 创建一个
@Config
限定符,以便 CDI 容器解析为配置生产者。 - 创建一个
@Key(String)
配置注解,通过该注解可以获得所需配置条目的查找键。 - 创建一个将
InjectionPoint
作为参数的 Producer 方法。InjectionPoint
允许获取@Key
注释、目标字段的声明类型以及声明此注入(inject)字段的类(封闭类)。如果InjectionPoint
允许我获得封闭类的一个实例,那将是一个不错的场景。但是考虑一下,这没有意义,因为在创建/定位并注入(inject)所有依赖项之前,实例还没有准备好。
CDI 不适合这种情况吗?如何最好地实现?
最佳答案
一种可能的解决方案是在请求处理中提取重要的租户值,例如ServletFilter
或一些拦截器并将其存储在 ThreadLocal
持有者中。这仅在两个组件(例如过滤器和 CDI 生产者)在同一线程中执行时才有效 - 因此您可能会遇到 EJB 问题。
您可以在 @Produces
方法中检索租户标识符,并根据 @Key
注释值和租户 ID 返回配置条目。
一些伪解决方案:
ThreadLocal 持有者
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 持有者
@RequestScoped
public class RequestDataHolder {
private String tenantId;
public String getTenantId() {
return this.tenantId;
}
public void setTenantId(String tenantId) {
this.tenantId = tenantId;
}
}
网络过滤器
从请求中提取值并将它们存储在我们的 holder 中。
@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 制作人 使用数据持有者并为注入(inject)点生成预期值。
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 可以注入(inject)到任何 CDI、EJB、JAXRS 或 Servlet 组件中,从而允许将变量从 WEB 上下文传递到其他上下文。
注意:此解决方案需要根据 CDI 规范将 CDI 容器与 EJB 和 WEB 容器正确集成。
关于java - 将运行时(元)数据传递给 CDI 中的生产者方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34159078/