我正在尝试找到一个配置服务器端Java应用程序的解决方案,以便系统的不同用户与系统交互,就好像它的配置不同(Multitenancy)。例如,当我的应用程序为来自user1的请求提供服务时,我希望我的应用程序能够在Klingon中进行响应,但对于所有其他用户,我希望它以英语回复。 (我选择了一个故意荒谬的例子,以避免具体细节:重要的是我希望应用程序针对不同的请求采取不同的行为)。
理想情况下,有一个通用解决方案(即允许我添加的解决方案) 用户特定的覆盖我的配置的任何部分,而无需更改代码。)
我已经查看过具有built in support for multitenant configuration的Apache Commons Configuration,但据我所知,这是通过将某些基本配置与组合在一起来实现的。覆盖。这意味着我有一个配置指定:
application.lang=english
,并说一个user1.properties
覆盖文件:
application.lang=klingon
不幸的是,对于我们的支持团队来说,如果他们能够在一个地方看到所有相关配置,并以某种方式内联指定覆盖,而不是为 base 与覆盖提供单独的文件
我认为Commons Config的多租户+类似Velocity template之类的东西的组合来描述底层配置中的条件元素是我的目标 - Commons Config易于与我的配置和Velocity进行交互用于在单一配置中非常明确地描述任何覆盖,例如:
#if ($user=="user1")
application.lang=klingon
#else
application.lang=english
#end
人们使用什么解决方案来解决这类问题?
答案 0 :(得分:3)
您是否可以对每个服务器操作进行编码,如下所示?
void op1(String username, ...)
{
String userScope = getConfigurationScopeForUser(username);
String language = cfg.lookupString(userScope, "language");
int fontSize = cfg.lookupInt(userScope, "font_size");
... // business logic expressed in terms of language and fontSize
}
(上面的伪代码假定用户的名称作为参数传递,但您可以通过其他机制传递它,例如,线程本地存储。)
如果上述内容可以接受,那么Config4*可以满足您的要求。使用Config4 *,上述伪代码中使用的getConfigurationScopeForUser()
方法可以实现如下(假设cfg
是先前通过解析配置文件初始化的Configuration对象):
String getConfigurationScopeForUser(String username)
{
if (cfg.type("user", username) == Configuration.CFG_SCOPE) {
return Configuration.mergeNames("user", username);
} else {
return "user.default";
}
}
这是一个用于处理上述内容的示例配置文件。大多数用户从“user.default”范围获取配置,但Mary和John有一些默认值的覆盖:
user.default {
language = "English";
font_size = "12";
# ... many other configuration settings
}
user.John {
@copyFrom "user.default";
language = "Klingon"; # override a default value
}
user.Mary {
@copyFrom "user.default";
font_size = "18"; # override a default value
}
如果以上听起来可能符合您的需求,那么我建议您阅读“入门指南”的第2章和第3章,以便对Config4 *语法和API有足够的了解,以便能够确认/反驳Config4 *是否适合您的需求。您可以在Config4 * website上找到该文档。
免责声明:我是Config4 *的维护者。
编辑:我提供了更多详细信息以回应bacar的评论。
我没有将Config4 *放在Maven存储库中。但是,使用捆绑的Ant构建文件构建Config4 *是微不足道的,因为Config4 * 不对第三方库有依赖性。
使用Config4 *在服务器应用程序中使用Config4 *(由bacar评论提示)的另一种方法如下......
实现每个服务器操作,如下面的伪代码:
void op1(String username, ...)
{
Configuration cfg = getConfigurationForUser(username);
String language = cfg.lookupString("settings", "language");
int fontSize = cfg.lookupInt("settings", "font_size");
... // business logic expressed in terms of language and fontSize
}
上面使用的getConfigurationForUser()
方法可以实现,如下面的伪代码所示:
HashMap<String,Configuration> map = new HashMap<String,Configuration>();
synchronized String getConfigurationForUser(String username)
{
Configuration cfg = map.get(username);
if (cfg == null) {
// Create a config object tailored for the user & add to the map
cfg = Configuration.create();
cfg.insertString("", "user", username); // in global scope
cfg.parse("/path/to/file.cfg");
map.put(username, cfg);
}
return cfg;
}
以下是使用上述内容的示例配置文件。
user ?= ""; // will be set via insertString()
settings {
@if (user @in ["John", "Sam", "Jane"]) {
language = "Klingon";
} @else {
language = "English";
}
@if (user == "Mary") {
font_size = "12";
} @else {
font_size = "10";
}
... # many other configuration settings
}
我对这两种方法的主要评论如下:
第一种方法(一个包含大量变量和范围的Configuration
对象)可能比第二种方法使用的内存略少(许多Configuration
个对象,每个对象的数量都很少变量)。但我的猜测是,任何一种方法的内存使用量都将以KB或数十KB来衡量,与服务器应用程序的整体内存占用量相比,这将是微不足道的。
我更喜欢第一种方法,因为单个Configuration
对象只初始化一次,然后通过只读lookup()
样式操作进行访问。这意味着您不必担心同步对Configuration
对象的访问,即使您的服务器应用程序是多线程的。相反,如果您的服务器应用程序是多线程的,则第二种方法要求您同步对HashMap
的访问。
lookup()
样式操作的开销大约为纳秒或微秒,而解析配置文件的开销大约为毫秒或数十。毫秒(取决于文件的大小)。第一种方法仅执行一次相对昂贵的配置文件解析,并且在应用程序的初始化中完成。相反,第二种方法执行相对昂贵的配置文件解析“N”次(每次“N”个用户一次),并且在服务器处理来自客户端的请求时发生重复费用。对于您的应用程序而言,性能损失可能会也可能不是问题。
我认为易用性比易于实施更重要。因此,如果您认为第二种方法可以更容易维护配置文件,那么我建议您使用该方法。
在第二种方法中,您可能想知道为什么我将大多数变量放在命名范围(settings
)中,而不是放在全局范围内以及“注入”user
变量。我这样做的原因超出了你的问题范围:将“注入”变量与应用程序可见变量分开,可以更容易地对应用程序可见变量执行模式验证。
答案 1 :(得分:0)
通常,用户配置文件将进入数据库,用户必须使用登录名打开会话。用户名可以进入HTTPSession(= Cookies),每次请求服务器都将获得用户名,并可以从DB读取配置文件。舒尔,DB可以是一些配置文件,如joe.properties,jim.properties,admin.properties等。