我可以在多个环境中使用单个 war 文件吗?我是否该?
Posted
技术标签:
【中文标题】我可以在多个环境中使用单个 war 文件吗?我是否该?【英文标题】:Can I use a single war file in multiple environments? Should I?我可以在多个环境中使用单个 war 文件吗?我是不是该? 【发布时间】:2010-12-10 16:55:44 【问题描述】:我的工作中有一个 Java Web 应用程序,我想简化我们部署到 DEV、QA 和 PROD 环境的方式。
应用程序在启动时会读入一系列属性,dev、qa 和 prod 的属性文件是不同的。每当我想部署到某个环境时,我都会将特定于环境的属性文件放到我的 app 文件夹中,构建 war,然后将其部署到三个 tomcat 5.5 服务器之一。
我想要做的是必须有一个具有所有环境属性的单个 .war,并让应用程序在初始化过程中询问网络服务器以确定应用程序所在的环境,以及哪些属性载入。有没有一种简单的方法(或者,如果没有,一种标准的方法)来做到这一点?
【问题讨论】:
退后一步,这甚至是一个理想的目标吗?我应该首先尝试创建一个万能的 .war 吗? 另请参阅此问题:***.com/questions/1568985/… 【参考方案1】:单一构建(战争)当然是正确的方法。 但是,当涉及到特定于环境的配置时,最好的方法是确保不应该将所有配置 .properties 文件推送到所有环境。例如PROD 属性文件应复制到 DEV 或 UAT。 还应避免使用 Spring 配置文件,因为它们会导致复杂的配置管理。
【讨论】:
【参考方案2】:这实际上取决于您使用这些属性的目的。
一些(例如数据源)可以在容器本身中进行配置(Tomcat 5.5. JNDI Resources,也请参阅 JDBC 源部分)。
其他(特定于应用程序的)可能确实需要是属性。在这种情况下,您的选择是:
-
在 WAR 文件中捆绑属性并根据某些外部开关(环境变量或 JVM 属性)加载适当的子集
在解压war的每台服务器上设置部署过程,并将属性文件(位于该服务器上的预定义位置并特定于该服务器)复制到
WEB-INF/classes
(或其他适当位置)。
就“这是一个理想的目标”而言——是的,我想是的。在 QA/staging 中测试一个 WAR,然后部署到生产环境中,减少了中间步骤,从而减少了出错的机会。
更新(基于评论):
上面的第 1 项指的是一个实际的环境变量(例如,您通过 Windows 中的 SET ENV_NAME=QA
或 Linux 中的 ENV_NAME=QA; export ENV_NAME
设置的东西)。您可以使用 System.getenv()
从代码中读取其值并加载相应的属性文件:
String targetEnvironment = System.getenv("TARGET_ENV");
String resourceFileName = "/WEB-INF/configuration-" + targetEnvironment + ".properties";
InputStream is = getServletContext().getResourceAsStream(resourceFileName);
Properties configuration = new Properties();
configuration.load(is);
但是,您可以改为通过 JNDI (see Environment Entries in Tomcat doc) 定义一个标量值:
<Context ...>
<Environment name="TARGET_ENV" value="DEV" type="java.lang.String" override="false"/>
</Context>
并通过
在您的应用中阅读Context context = (Context) InitialContext().lookup("java:comp/env");
String targetEnvironment = (String) context.lookup("TARGET_ENV");
// the rest is the same as above
问题是,如果您仍将使用 JNDI,那么您最好放弃您的属性文件并通过 JNDI 配置所有内容。您的数据源将作为实际资源提供给您,基本属性将保持为标量(尽管它们将是类型安全的)。
最终由您决定哪种方式更适合您的特定需求;各有利弊。
【讨论】:
一些属性是数据库属性,但我们也有像 ldap 服务器和 smtp 服务器地址这样的东西,它们会因环境而异。我承认我是一个 JNDI 菜鸟——我可以在里面存储那种信息吗?我想最终我需要的只是一个值为 dev|qa|prod|whatever 的变量(例如“environmentName”)。我可以使用它作为前缀来索引单个属性文件的特定于环境的部分。 JNDI 是一种选择吗? 我已经更新了上面的答案;评论空间太狭窄了。 很好的信息-非常感谢!我想我会选择 JNDI 方法。【参考方案3】:在启动时设置一个指向属性文件位置的 Systemproperty,然后在您的应用程序中拉入此属性并加载您的设置。我要做的另一件事是有两个属性文件,比如 default.properties 和 external.properties。它们包含相同的属性,但 default.properties 包含默认值(大部分时间设置有效),该文件进入战争。然后,如果您部署到环境。您查找 external.properties,如果发现已使用,如果没有,则回滚到 default.properties。这提供了一种在需要时覆盖属性的好方法,但也有默认设置。这适用于我的许多部署,但可能不适用于您的场景。
【讨论】:
【参考方案4】:我更喜欢一个 EAR 或一个 WAR 方法。从安全的角度来看,使用刚刚测试过的完全相同的 EAR 并将其直接移动到下一个状态(测试 > 生产),这是令人放心的事情,并且通常需要这样做。
除了容器提供的属性文件外,还有很多选项。当你死去时,容器通常有一个很好的 UI 来维护这些值和资源。
使用支持ResourceBundle 的数据库的例子不胜枚举。
例如,Spring 框架有多种机制可以轻松实现这一目标。以PropertyPlaceholderConfigurer开头
【讨论】:
【参考方案5】:绝对单一的 WAR 是最好的方法。在每个环境中使用相同的 JNDI 名称设置资源,如果出于业务逻辑目的需要检测您所在的环境,请在 Tomcat 启动时使用 System 属性。
【讨论】:
【参考方案6】:我认为单个 war 文件是一个不错的方法,因为可以确信您在 DEV 中测试的二进制文件与在生产中测试的二进制文件完全相同。我们这样做的方式是,将配置保存在单独的属性文件中,在战争之外,但在应用服务器的类路径中。
如果您想将所有属性保留在战争中(这确实使部署更容易,因为您不必同时部署属性文件),您可以在标识服务器的类路径中保留一个属性文件环境类型,并将其用于 .war 文件中的属性文件中的键值。外部属性文件也可能是一个很好的方法,可能是一些不会发生太大变化并且在许多战争文件中使用的高级配置。
【讨论】:
【参考方案7】:可能最简单的方法是设置一个在应用程序服务之间不同的环境变量,并使用它来确定要读取哪个属性文件。
另一种可能性是将属性存储在数据库中,并使用以标准 JNDI 名称存在但指向不同环境中不同位置的数据源。
【讨论】:
【参考方案8】:你所做的是一场等待发生的事故......有一天,一场 DEV 战争将在 de PROD 服务器中结束,并且根据某些优于所有自然法则的法律,该问题将在凌晨 2 点被发现。无法解释为什么会这样,但总有一天会发生。所以在我看来,一场战争绝对是个好主意。
您可以在相应的 JVM (-Dcom.yourdomain.configpath=/where/you/store/configfiles) 中设置系统属性并使用
String value = System.getProperty("com.yourdomain.configpath", "defaultvalue_if_any");
默认值可能指向战争中的某个地方(WEB-INF/...),或者如果没有默认值,则用于在加载期间发出一些日志噪音以警告配置错误)。另请注意,此技术不依赖于平台,因此您的开发机器可以是 Windows 机器,而服务器可以是 Linux 机器,它可以同时处理两者。我们通常在此配置路径中为每个应用程序创建一个子目录,因为多个应用程序在服务器上使用此系统,我们希望保持整洁。
作为额外的好处,您不必冒险以这种方式在 PROD 服务器上手动调整属性文件。只是不要忘记在备份方案中包含文件存储的路径。
【讨论】:
好信息 - 谢谢。也很有趣,或者至少如果它没有在离家这么近的地方发生的话。不久前,运维团队不小心将我们的 QA 战争加载到了 PROD 中。这个事件是我在这里提出问题的前兆;-)以上是关于我可以在多个环境中使用单个 war 文件吗?我是否该?的主要内容,如果未能解决你的问题,请参考以下文章