我想知道人们如何管理跨Tomcat应用服务器的多个实例维护JNDI资源。让我们以我的数据库JNDI资源为例。它在我的/conf/context.xml文件中声明,并从我的apps web.xml文件中引用。
必须在我的开发框,登台框和生产框中独立定义JNDI资源。如果我想设置开发人员/登台/生产箱的新实例怎么办?这意味着我必须在context.xml中为我提出的每个新实例重新创建资源名称?从设置角度来看,这是一个可能导致应用服务器开始指向错误数据库的人为错误的地方。
我发现这很麻烦且令人困惑,因为我扩大了项目开发人员的数量以及最终可能使用的生产服务器数量。
您是否只是将其设置为您的设置的一部分,或者每次重新安装tomcat并设置盒子时创建一个处理此问题的安装脚本?或者是否有一些其他级别的间接可以使这更容易?
答案 0 :(得分:3)
我假设对于给定的资源,您在每个环境中使用相同的JNDI名称。否则,您必须编辑代码以指向新资源(JNDI)名称。
第一次设置环境几乎不可能提前测试。没有办法验证某些字符串,如生产数据库连接字符串,在您实际必须使用它之前不会发胖。这是环境配置的本质。话虽如此,如果您想减少错误的可能性,首先需要确保为每个资源指定一个名称,无论托管在哪个环境中。在属性文件中创建指向jndi:/ jdbc / myproject / resources / dbConnectionString的dbConnectionString资源名称,并确保所有环境都声明相同的资源。下面是我们如何将代码与这些类型的环境依赖关系隔离开来。话虽这么说,您将始终必须手动验证特定服务器的配置是否正在使用所定义资源的适当值。
注意:从不创建资源名称,例如“ dbProdConnectionString ”,“ dbQAConnectionString ”,“ dbDevConnectionString ”。您将失去尝试消除手动干预的目的,因为您已经添加了一个间接步骤,需要更改代码(将代码指向正确的资源名称)并构建(将更改打包到.war文件中) )在环境之间移动时。
我们所做的是为特定于环境的属性创建文件夹结构。在该文件夹下,我们为每个特定的部署环境创建了文件夹,包括本地开发。它看起来像这样:
Project
\
-Properties
\
-Local (your PC)
-Dev (shared dev server)
-Test (pre-production)
-Prod (Production)
在每个文件夹中,我们放置属性/配置文件的并行副本,并将不同的配置仅放在相应文件夹中的文件中。秘诀是控制部署环境的类路径。我们在每台服务器上定义了一个PROPERTIES类路径条目。在Prod上,它将设置为“$ ProjectDir / Properties / Prod”,而在测试时,相同的变量将被设置为“$ ProjectDir / Properties / Test”。
这样我们可以为预先配置的dev / test / prod数据库建立数据库连接字符串,而不是每次我们想要为不同的环境构建时都必须在属性文件中签出/。
这也意味着我们可以将完全相同的.war / .ear文件部署到Test and Prod而无需重建。通过在每个环境中使用相同的JNDI名称但使用特定于该环境的值,以类似方式处理的属性文件中未声明的任何属性。
答案 1 :(得分:1)
您是否部署了必须使用共享资源的多个Web应用程序?
如果没有,绝对没有理由在/conf/context.xml中声明您的资源。它们应该在您的Web应用程序context.xml文件中单独声明,该文件将部署为WAR中的/META-INF/context.xml。该文件以及您的web.xml应该被检入您的源代码管理系统并作为构建的一部分进行部署 - 无需任何手动干预。
如果要使用共享资源部署多个Web应用程序,则可以编写自定义资源工厂,将相同资源公开给多个Web应用程序(请参阅页面底部的Tomcat documentation)并使用上述方法或者 - 至少对于开发环境 - 作为构建的一部分,您可以自动更改(甚至替换为默认情况下不执行任何操作)/conf/context.xml。当然,对于不可取的生产部署。
答案 2 :(得分:1)
如果您使用弹簧框架,则可以使用PropertyPlaceholderConfigurer轻松解决此问题。这个类让你将定义移动到外部属性文件中。数据源配置如下:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"><value>${jdbc.driver}</value></property>
<property name="url"><value>${jdbc.url}</value></property>
<property name="username"><value>${jdbc.user}</value></property>
<property name="password"><value>${jdbc.password}</value></property>
</bean>
属性本身在标准属性文件中定义:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://host/database
jdbc.username=user
jdbc.password=secret
对于实际属性,您有两个选择:
将属性文件放在文件系统外部,因此每台机器上的属性文件都不同:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">file:/etc/yourapp/jdbc.properties</property>
<!-- on windows, put the file in c:\etc\yourapp, the definition will work -->
</bean>
将以下系统属性添加到服务器-Denv = [development | test | production]。然后,将三个配置文件放在WEB-INF / classes目录中:jdbc-development.properties,test-development.properties和production-development.properties。上下文配置将是:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">classpath:jdbc-${env}.properties</property>
</bean>
答案 3 :(得分:0)
JNDI的重点是独立定义环境特定资源。您的开发,登台和生产环境不应共享同一个数据库(或者无论如何,JNDI旨在为每个环境提供单独的数据库)。
另一方面,如果你试图对多个Tomcat服务器进行负载均衡,并且你希望所有实例共享相同的配置,我认为你总是可以拆分你的context.xml并在共享文件。这是Tomcat documentation talking about context.xml。
您如何管理这些取决于您自己。它可以很简单,比如每次创建一个新的Tomcat实例时都会有一个“模板”context.xml文件(在源代码控制系统中有这些文件,特别是分布式实例,可能会有所帮助)。或者您可以编写一个脚本来执行此操作。
如果你想要更多,我相信有些产品可以在整个过程中提供一个很好的用户界面。我相信这是SpringSource tc Server。
答案 4 :(得分:0)
我的解决方案是将所有定义放入server-template.xml
文件中,然后使用聪明的XSLT转换为每个实例生成最终server.xml
。我正在使用ant构建文件来复制Tomcat安装的所有文件,并让它从模板中创建server.xml
。一切都保存在CVS中,因此我可以快速恢复安装,而不必担心某些东西可能无法正常工作。这是模板的样子:
<Server port="${tomcat.server.port}" shutdown="SHUTDOWN"
xmlns:x="http://my.company.com/tomcat-template">
<x:define name="Derby-DataSource" username="???" password="???" url="???"
auth="Container" type="javax.sql.DataSource"
maxActive="50" maxIdle="5" maxWait="300"
driverClassName="org.apache.derby.jdbc.ClientDriver"
testWhileIdle="true" timeBetweenEvictionRunsMillis="3600000"
removeAbandoned="true" removeAbandonedTimeout="60" logAbandoned="true" />
<x:define x:element="Resource" name="Derby/Embedded/TDB" auth="Container" type="javax.sql.DataSource"
maxActive="50" maxIdle="5" maxWait="300"
username="" password="" driverClassName="org.apache.derby.jdbc.EmbeddedDriver"
url="jdbc:derby:D:/tmp/TestDB" />
<x:define x:element="Resource" name="Derby/TDB" auth="Container" type="javax.sql.DataSource"
maxActive="50" maxIdle="5" maxWait="300"
username="junit" password="test" driverClassName="org.apache.derby.jdbc.ClientDriver"
url="jdbc:derby://localhost:1527/TDB" />
<x:define x:element="Resource" name="Derby/P6/TDB" auth="Container" type="javax.sql.DataSource"
maxActive="50" maxIdle="5" maxWait="300"
username="junit" password="test" driverClassName="com.p6spy.engine.spy.P6SpyDriver"
url="jdbc:derby://localhost:1527/TDB" />
... lots of Tomcat stuff ...
<!-- Global JNDI resources -->
<GlobalNamingResources>
<x:if server="local">
<!-- Local with Derby Network Server -->
<x:use name="Derby/TDB"><x:override name="jdbc/DB" maxIdle="1" /></x:use>
<x:use name="Derby/TDB"><x:override name="jdbc/DB_APP" maxIdle="1" /></x:use>
<x:use name="Derby/TDB"><x:override name="jdbc/DB2" maxIdle="1" /></x:use>
</x:if>
<x:if env="test"> ... same for test </x:if>
<x:if env="prod"> ... same for test </x:if>
</GlobalNamingResources>
</Server>
如您所见,我定义了默认值,然后专门设置。在环境中,我会覆盖一些东西(本地系统比生产和集成测试获得更小的池)。
过滤器脚本如下所示:
<!--
This XSLT Stylesheet transforms the file server-template.xml into server-new.xml.
It will perform the following operations:
- All x:define elements are removed
- All x:use elements will be replaces with the content and attributes
of the respective x:define element. The name of the new element is
specified with the attribute "x:element".
- All attributes in the x:override elements will overwrite the respective
attributes from the x:define element.
- x:if allows to suppress certain parts of the file altogether.
Example:
<x:define element="Resource" name="Derby/Embedded/TDB" auth="Container" ... />
<x:use name="Derby/Embedded/TDB"><x:override name="NewTDB" /></x:use>
becomes:
<Resource name="NewTDB" auth="Container" ... />
i.e. the attribute x:element="Resource" in x:define becomes the name of the
new element, name="Derby/Embedded/ABSTDB" in x:use specifies which x:define
to use and name="NewTDB" in x:override replaces the value of the "name"
attribute in x:define.
-->
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:x="http://my.company.com/tomcat-template">
<xsl:output method="xml"/>
<!-- Key for fast lookup of x:defines -->
<xsl:key name="def" match="//x:define" use="@name" />
<!-- Parameters which can be used in x:if -->
<xsl:param name="server" />
<xsl:param name="env" />
<xsl:param name="instance" />
<!-- Copy everything by default -->
<xsl:template match="node()|@*">
<xsl:copy><xsl:apply-templates select="node()|@*"/></xsl:copy>
</xsl:template>
<!-- filter x:defines -->
<xsl:template match="x:define"></xsl:template>
<!-- x:use is replaced by the matching x:define -->
<xsl:template match="x:use">
<!-- Find the x:define -->
<xsl:variable name="def" select="key('def', @name)" />
<!-- Save the x:use node in a variable -->
<xsl:variable name="node" select="." />
<!-- Start a new element. the name is in the attribute x:element of the x:define -->
<xsl:element name="{$def/@x:element}">
<!-- Process all attributes in the x: namespace -->
<xsl:for-each select="$def/@x:*">
<xsl:choose>
<xsl:when test="name() = 'x:extends'">
<xsl:variable name="extName" select="." />
<xsl:variable name="ext" select="key('def', $extName)" />
<xsl:for-each select="$ext/@*">
<xsl:if test="namespace-uri() != 'http://my.company.com/tomcat-template'">
<xsl:copy />
</xsl:if>
</xsl:for-each>
</xsl:when>
</xsl:choose>
</xsl:for-each>
<!-- Copy all attributes from the x:define (except those in the x: namespace) -->
<xsl:for-each select="$def/@*">
<xsl:if test="namespace-uri() != 'http://my.company.com/tomcat-template'">
<xsl:copy />
</xsl:if>
</xsl:for-each>
<!-- If there is an x:override-Element, copy those attributes. This
will overwrite existing attributes with the same name. -->
<xsl:for-each select="$node/x:override/@*">
<xsl:variable name="name" select="name()" />
<xsl:variable name="value" select="." />
<xsl:variable name="orig" select="$def/attribute::*[name() = $name]" />
<xsl:choose>
<!-- ${orig} allows to acces the attributes from the x:define -->
<xsl:when test="contains($value, '${orig}')">
<xsl:attribute name="{$name}"><xsl:value-of select="substring-before($value, '${orig}')"
/><xsl:value-of select="$orig"
/><xsl:value-of select="substring-after($value, '${orig}')"
/></xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:copy />
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<!-- Copy all child nodes, too -->
<xsl:apply-templates select="$def/node()"/>
</xsl:element>
</xsl:template>
<!-- x:if, to filter parts of the document -->
<xsl:template match="x:if">
<!-- t will be non-empty if any of the conditions matches -->
<xsl:variable name="t">
<!-- Check for each paramater whether it is used in the x:if. If so,
check the value. If the value is the same as the stylesheet
paramater, the condition is met. Missing conditions count
as met, too.
<xsl:if test="not(@server) or @server = $server">1</xsl:if>
<xsl:if test="not(@env) or @env = $env">1</xsl:if>
<xsl:if test="not(@instance) or @instance = $instance">1</xsl:if>
</xsl:variable>
<xsl:if test="normalize-space($t) = '111'">
<xsl:comment> <xsl:value-of select="$server" />, Env <xsl:value-of select="$env" />, Instance <xsl:value-of select="$instance" /> </xsl:comment>
<xsl:apply-templates select="node()|@*"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
答案 5 :(得分:0)
如果您能够将远程JNDI目录绑定到Tomcat的“全局”JNDI目录,则可以使用此机制:Using a JNDI datasource created by another application with Tomcat
问候。