Поддержание JNDI в нескольких экземплярах Tomcat - PullRequest
8 голосов
/ 14 июля 2009

Мне интересно, как люди управляют обслуживанием ресурсов JNDI в нескольких экземплярах своего сервера приложений Tomcat. Возьмем, к примеру, мой ресурс базы данных JNDI. Он объявлен в моем файле /conf/context.xml и содержит ссылки из файла моих приложений web.xml.

Этот ресурс JNDI должен быть независимо определен в моем блоке разработки, промежуточном блоке и производственном блоке. Что, если я хочу настроить новый экземпляр окна разработчика / постановки / производства? Это означает, что я должен воссоздавать имя ресурса в context.xml для каждого нового экземпляра, который я поднимаю? С точки зрения настройки это место, где может быть какая-то человеческая ошибка, которая может привести к тому, что сервер приложений начнет указывать на неправильную БД.

Я нахожу это громоздким и запутанным, поскольку я увеличиваю как количество разработчиков в моем проекте, так и, в конечном итоге, количество производственных серверов, которые я мог бы использовать.

Вы просто включаете его в свои настройки или создаете сценарий установки, который справляется с этим каждый раз, когда вы переустанавливаете tomcat и устанавливаете коробку? Или есть какой-то другой уровень косвенности, который мог бы облегчить это?

Ответы [ 6 ]

3 голосов
/ 21 июля 2009

Я предполагаю, что для данного ресурса вы используете одно и то же имя JNDI в каждой среде. В противном случае вам придется отредактировать код, чтобы он указывал на имя нового ресурса (JNDI).

Настройка среды в первый раз может быть почти невозможна для тестирования заранее. Невозможно проверить, что какая-то строка, например строка соединения с производственной базой данных, не стала толстой, пока вам не понадобится ее использовать. Это природа конфигурации среды. С учетом вышесказанного, если вы хотите уменьшить вероятность появления ошибок, сначала необходимо убедиться, что каждому ресурсу присваивается имя, которое используется независимо от среды, в которой он размещен. Создайте имя ресурса dbConnectionString в файле свойств, которое указывает на jndi: / jdbc / myproject / resources / 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 и Prod без перекомпоновки. Любые свойства, которые не были объявлены в файле свойств, мы обрабатывали аналогичным образом, используя одно и то же имя JNDI в каждой среде, но используя значения, характерные для этой среды.

1 голос
/ 22 июля 2009

Если вы используете среду 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 = [разработка | тест | производство]. Затем поместите три файла конфигурации в каталог 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>
    
1 голос
/ 17 июля 2009

Развертываете ли вы несколько веб-приложений, которые должны использовать общие ресурсы?

Если нет, нет абсолютно никаких причин объявлять ваши ресурсы в /conf/context.xml. Вместо этого они должны быть объявлены в отдельном, закрытом для вашего файла context.xml веб-приложения, которое будет развернуто как /META-INF/context.xml внутри вашей WAR. Этот файл вместе с вашим web.xml должен быть зарегистрирован в вашей системе контроля версий и развернут как часть вашей сборки - никакого ручного вмешательства вообще.

Если вы развертываете несколько веб-приложений с общими ресурсами, вы можете либо создать собственную фабрику ресурсов, которая будет предоставлять один и тот же ресурс нескольким веб-приложениям (см. Документация Tomcat , внизу страницы) и использовать Приведенный выше подход или - по крайней мере для среды разработки - вы можете автоматически изменить (или даже заменить, если по умолчанию ничего не происходит) /conf/context.xml как часть вашей сборки. Разумеется, для развертывания производства это не рекомендуется.

0 голосов
/ 30 сентября 2010

Если вы можете связать удаленный каталог JNDI с «глобальным» каталогом JNDI Tomcat, вы можете использовать этот механизм: Используя источник данных JNDI, созданный другим приложением с Tomcat

Привет.

0 голосов
/ 22 июля 2009

Мое решение состояло в том, чтобы поместить все определения в файл 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>
0 голосов
/ 17 июля 2009

Смысл JNDI состоит в том, чтобы ресурсы, определенные для среды, определялись независимо. Ваши среды разработки, промежуточной и производственной среды не должны совместно использовать одну и ту же базу данных (или, во всяком случае, JNDI был разработан для обеспечения отдельных баз данных для каждой среды).

Если, с другой стороны, вы пытаетесь сбалансировать нагрузку на несколько серверов Tomcat и хотите, чтобы все экземпляры имели одинаковую конфигурацию, я думаю, вы всегда можете разделить свой context.xml и иметь общие биты в общий файл. Вот документация Tomcat, в которой говорится о context.xml .

Как вы управляете этим, зависит от вас. Это может быть просто, например, иметь «шаблонные» файлы context.xml, которые вы запускаете каждый раз, когда создаете новый экземпляр Tomcat (полезно иметь их в системе контроля версий, особенно в распределенной). Или вы можете написать скрипт для этого.

Если вы хотите большего, я считаю, что есть продукты, которые обеспечивают приятный интерфейс во всем процессе. Я считаю, что это SpringSource tc Server .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...