8

我想知道人们如何管理跨其 Tomcat 应用程序服务器的多个实例维护 JNDI 资源。让我们以我的数据库 JNDI 资源为例。它在我的 /conf/context.xml 文件和我的应用程序 web.xml 文件中的引用中声明。

该 JNDI 资源必须在我的开发箱、暂存箱和生产箱上独立定义。如果我想设置开发人员/登台/生产框的新实例怎么办?这意味着我必须为我提出的每个新实例在 context.xml 中重新创建资源名称?从设置的角度来看,这是一个可能存在一些人为错误的地方,这些错误可能导致应用服务器开始指向错误的数据库。

我发现这既麻烦又令人困惑,因为我增加了项目中的开发人员数量以及最终可能使用的生产服务器的数量。

每次重新安装 tomcat 并设置盒子时,您是否只是将其作为设置的一部分,或者创建一个设置脚本来为您处理这个问题?或者是否有一些其他级别的间接可以使这更容易?

4

6 回答 6

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”,而在 Test 上,相同的变量将设置为“$ProjectDir/Properties/Test”。

通过这种方式,我们可以为 dev/test/prod 数据库预先配置数据库连接字符串,而不必在每次想要为不同的环境构建时都签出/进入属性文件。

这也意味着我们可以将完全相同的 .war/.ear 文件部署到 Test 和 Prod 而无需重新构建。任何未在属性文件中声明的属性,我们以类似的方式处理,在每个环境中使用相同的 JNDI 名称,但使用特定于该环境的值。

于 2009-07-21T14:21:49.417 回答
1

您是否正在部署多个必须使用共享资源的 Web 应用程序?

如果没有,那么绝对没有理由在 /conf/context.xml 中声明您的资源。相反,它们应该在一个单独的、私有的 Web 应用程序 context.xml 文件中声明,该文件将在 WAR 中部署为 /META-INF/context.xml。该文件以及您的 web.xml 应检入您的源代码控制系统并作为构建的一部分进行部署 - 无需任何手动干预。

如果您使用共享资源部署多个 Web 应用程序,您可以编写一个自定义资源工厂,将相同的资源公开给多个 Web 应用程序(请参阅页面底部的Tomcat 文档)并使用上述方法或 - 用于开发环境至少 - 您可以在构建过程中自动更改(甚至默认替换)/conf/context.xml。当然,对于不建议的生产部署。

于 2009-07-17T06:11:40.710 回答
1

如果您使用的是 spring 框架,则可以使用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

对于实际属性,您有两个选择:

  1. 将属性文件放在文件系统的外部,因此在每台机器上都会有所不同:

    <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>
    
  2. 将以下系统属性添加到您的服务器 -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>
    
于 2009-07-22T18:49:31.100 回答
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>
于 2009-07-22T09:19:42.280 回答
0

JNDI 的重点是独立定义特定于环境的资源。您的开发、登台和生产环境不应该共享同一个数据库(或者无论如何,JNDI 被设计为允许每个环境使用不同的数据库)。

另一方面,如果您尝试对多个 Tomcat 服务器进行负载平衡,并且希望所有实例共享相同的配置,我认为您总是可以拆分您的 context.xml 并在共享文件中拥有公共位。这是关于 context.xml 的 Tomcat 文档

如何管理这些取决于您。它可以很简单,例如每次创建新的 Tomcat 实例时都有一个“模板”context.xml 文件(将这些文件放在源代码控制系统中,尤其是分布式系统中会很有帮助)。或者您可以编写一个脚本来执行此操作。

如果你想要更多,我相信有一些产品可以在整个过程中提供一个漂亮的 UI。我相信这样做的一个是SpringSource tc Server

于 2009-07-17T06:38:32.590 回答
0

如果您能够将远程 JNDI 目录绑定到 Tomcat 的“全局”JNDI 目录,则可以使用以下机制:使用由另一个应用程序与 Tomcat 创建的 JNDI 数据源

问候。

于 2010-09-30T01:23:20.670 回答