2009-07-14 24 views
8

Mi chiedo in che modo le persone gestiscono la manutenzione delle risorse JNDI su più istanze del proprio server Tomcat Application. Prendiamo, ad esempio, la mia risorsa JNDI del database. È dichiarato nel mio file /conf/context.xml e nei riferimenti dal mio file web.xml dell'app.Manutenzione di JNDI su più istanze di Tomcat

Quella risorsa JNDI deve essere definita in modo indipendente nella casella di sviluppo, nella casella di gestione temporanea e nella casella di produzione. Cosa succede se voglio impostare una nuova istanza di una casella di sviluppo/scenografia/produzione? Ciò significa che devo ricreare il nome della risorsa all'interno del contesto.xml per ogni nuova istanza visualizzata? Dal punto di vista dell'installazione, questo è un luogo in cui potrebbe verificarsi un errore umano che potrebbe causare l'avvio di un server di applicazioni che punta al DB errato.

Lo trovo complesso e confuso mentre aumento sia il numero di sviluppatori sul mio progetto sia il numero di server di produzione che potrei utilizzare.

Riesci semplicemente a far parte della configurazione o creare uno script di installazione che si occupa di questo per voi ogni volta che si reinstalla Tomcat e si imposta una scatola? O c'è qualche altro livello di riferimento indiretto che potrebbe renderlo più facile?

risposta

1

Si stanno distribuendo più applicazioni Web che devono utilizzare risorse condivise?

In caso contrario, non c'è assolutamente alcun motivo per dichiarare le risorse in /conf/context.xml. Dovrebbero invece essere dichiarati in un file context.xml dell'applicazione privata, privato, che verrà distribuito come /META-INF/context.xml all'interno di WAR. Quel file, insieme al tuo web.xml, dovrebbe essere controllato nel tuo sistema di controllo del codice sorgente ed essere distribuito come parte della tua build, senza alcun intervento manuale di sorta.

Se si distribuiscono più app Web con risorse condivise, è possibile scrivere una factory di risorse personalizzate che esponga la stessa risorsa a più app Web (vedere Tomcat documentation, nella parte inferiore della pagina) e utilizzare l'approccio sopra o - per ambiente di sviluppo almeno - puoi cambiare automaticamente (o anche sostituire come predefinito non fa nulla) /conf/context.xml come parte della tua build. Per l'implementazione della produzione che non è consigliabile, ovviamente.

+1

Questi voti negativi sono davvero fastidiosi. Se stai votando qualcuno, abbi un po 'di coraggio e lascia un commento perché. Questo vale il doppio per quando pensi che ho detto qualcosa di sbagliato nella mia risposta - in che altro modo qualcuno potrebbe imparare? – ChssPly76

+4

Non ho votato, ma sono fortemente in disaccordo con te. Risorse come origini dati JDBC e sessione di posta non dovrebbero mai essere definite nella WAR stessa. I parametri di connessione del database sono proprietà dell'ambiente, non dell'applicazione. Uno dovrebbe essere in grado di distribuire WAR in un ambiente di test prima di andare in produzione. –

+0

Sono d'accordo con Maurice ma penso che ChssPly76 valga un voto alto perché sta dando una risposta alla domanda. IIRC Bea Weblogic ha un "JNDI proxy" che permette di creare un riferimento JNDI che punta ad altre risorse JNDI – ATorras

0

Il punto di JNDI è disporre di risorse specifiche per l'ambiente definite in modo indipendente. Gli ambienti di sviluppo, di staging e di produzione non dovrebbero condividere lo stesso database (o in ogni caso, JNDI è stato progettato per consentire database separati per ciascun ambiente).

Se invece si sta tentando di bilanciare più server Tomcat e si desidera che tutte le istanze condividano la stessa configurazione, penso che si possa sempre suddividere il proprio context.xml e avere i bit comuni in un file condiviso. Ecco lo Tomcat documentation talking about context.xml.

Come gestirli dipende da voi. Può essere semplice, come avere un file context.xml "modello" che si avvia ogni volta che si crea una nuova istanza Tomcat (avere questi in un sistema di controllo del codice sorgente, specialmente uno distribuito, può essere utile). O potresti scrivere una sceneggiatura per farlo.

Se vuoi di più, credo che ci siano prodotti là fuori che mettono una bella interfaccia utente nell'intero processo. Quello che credo sia questo è SpringSource tc Server.

+0

Non condividerò lo stesso DB tra tutti i vari server. Come dovresti condividere un context.xml comune tra le varie caselle del server? Inoltre, se davvero sembra che io debba mantenere vari context.xml per ogni scatola quali buoni processi/pratiche esistono per automatizzare queste attività e ridurre l'errore umano? – Ish

+0

Non penso che sarete davvero in grado di condividere file tra diverse finestre senza ricorrere a file system condivisi (ad es. NFS con collegamenti simbolici). Ho anche aggiornato la risposta per fare riferimento al documento Tomcat che parla della configurazione context.xml. –

3

Suppongo che per una determinata risorsa si utilizzi lo stesso nome JNDI in ogni ambiente. Altrimenti dovresti modificare il tuo codice per puntare al nome della nuova risorsa (JNDI).

Impostare l'ambiente per la prima volta può essere quasi impossibile da testare in anticipo.Non c'è modo di verificare che alcune stringhe, come la stringa di connessione del database di produzione, non siano diventate fat-fingered fino a quando non è effettivamente necessario utilizzarle. È la natura della configurazione dell'ambiente. Detto questo, se si vuole ridurre il rischio di errori, prima è necessario assicurarsi che a ciascuna risorsa venga assegnato un nome che viene utilizzato indipendentemente dall'ambiente in cui è ospitato. Creare un nome risorsa dbConnectionString in un file delle proprietà che punta a jndi:/jdbc/myproject/resources/dbConnectionString e assicurarsi che tutti gli ambienti dichiarino la stessa risorsa. Di seguito è riportato come abbiamo mantenuto il codice isolato da questi tipi di dipendenze ambientali. Detto questo, sarà sempre necessario verificare manualmente che la configurazione di un server specifico stia utilizzando i valori appropriati per le risorse definite.

NOTA: mai creare nomi di risorse come "dbProdConnectionString", "dbQAConnectionString", "dbDevConnectionString". Sconfiggerai lo scopo di cercare di eliminare l'intervento manuale perché hai aggiunto un passaggio indiretto che richiederà una modifica del codice (per indirizzare il codice al nome della risorsa corretta) e compilare (per impacchettare le modifiche nel tuo file .war) quando si muove tra gli ambienti.


Quello che abbiamo fatto è stato creare una struttura di cartelle per le proprietà che erano specifiche dell'ambiente. Sotto quella cartella abbiamo creato le cartelle per ogni specifico ambiente destinato alla distribuzione, incluso lo sviluppo locale. Si presentava così:

Project 
\ 
-Properties 
\ 
    -Local (your PC) 
    -Dev (shared dev server) 
    -Test (pre-production) 
    -Prod (Production) 

In ogni cartella abbiamo messo copie parallele dei file di proprietà/config e mettere le diverse configurazioni solo nel file nella cartella appropriata. Il segreto era controllare il classpath dell'ambiente di distribuzione. Abbiamo definito una voce classpath PROPERTIES su ogni server. Su Prod, sarebbe impostato su "$ ProjectDir/Properties/Prod" mentre su Test la stessa variabile verrebbe impostata su "$ ProjectDir/Properties/Test".

In questo modo è possibile avere stringhe di connessione al database per il database dev/test/prod preconfigurato e non dover effettuare il checkout/nel file delle proprietà ogni volta che si desidera creare un ambiente diverso.

Ciò significava anche che è possibile distribuire lo stesso file .war/.ear su Test e Prod senza ricostruzione. Qualsiasi proprietà che non è stata dichiarata nel file delle proprietà è stata gestita in modo simile utilizzando lo stesso nome JNDI in ciascun ambiente ma utilizzando valori specifici per quell'ambiente.

0

La mia soluzione era di inserire tutte le definizioni in un file server-template.xml e quindi utilizzare una trasformata XSLT intelligente per generare l'server.xml finale per ogni istanza. Sto usando un file di build ant per copiare tutti i file per un'installazione di Tomcat e lasciare che crei un modello server.xml. Tutto viene salvato in CVS, quindi posso ripristinare rapidamente l'installazione senza dovermi preoccupare che qualcosa potrebbe non funzionare. Ecco ciò che il modello si presenta come:

<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> 

Come potete vedere, io definisco le impostazioni predefinite e quindi specializzato le impostazioni. All'interno dell'ambiente, quindi sovrascrivo alcune cose (il sistema locale ottiene un pool più piccolo rispetto al test di produzione e integrazione).

sceneggiatura Il filtro è simile al seguente:

<!-- 

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> 
1

Se si utilizza il quadro di primavera è possibile risolvere questo problema molto facilmente utilizzando il PropertyPlaceholderConfigurer. Questa classe consente di spostare le definizioni in file di proprietà esterni.La configurazione origine dati è la seguente:

<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> 

Le proprietà stesse sono definite in un proprietà di file standard:

jdbc.driverClassName=com.mysql.jdbc.Driver 
jdbc.url=jdbc:mysql://host/database 
jdbc.username=user 
jdbc.password=secret 

Per le proprietà effettive avete due opzioni:

  1. Put il file delle proprietà esternamente sul file system, quindi sarà diverso su ogni macchina:

    <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. Aggiungere la seguente proprietà di sistema al server -Denv = [sviluppo | prova | produzione]. Quindi, inserisci tre file di configurazione nella directory WEB-INF/classes: jdbc-development.properties, test-development.properties e production-development.properties. La configurazione contesto sarà:

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 
        <property name="location">classpath:jdbc-${env}.properties</property> 
    </bean> 
    
Problemi correlati