外部化 Tomcat 配置

Posted

技术标签:

【中文标题】外部化 Tomcat 配置【英文标题】:Externalize Tomcat configuration 【发布时间】:2012-07-13 05:01:41 【问题描述】:

我在 context.xml 中有一个 DataSource 配置。是否可以不在该文件中硬编码数据库参数?例如,使用外部属性文件,并从中加载参数?

类似这样的东西:

context.xml:

  <Resource
  name="jdbc/myDS" auth="Container"
  type="javax.sql.DataSource"
  driverClassName="oracle.jdbc.OracleDriver"
  url="$db.url"
  username="$db.user"
  password="$db.pwd"
  maxActive="2"
  maxIdle="2"
  maxWait="-1"/>

db.properties:

db.url=jdbc:oracle:thin:@server:1521:sid
db.user=test
db.pwd=test

【问题讨论】:

context.xml 文件已经是一个外部文件。为什么你认为你需要另一个? 【参考方案1】:

当然,这是可能的。您必须像这样将ServletContextListener 注册到您的web.xml

<!-- at the beginning of web.xml -->

<listener>
    <listener-class>com.mycompany.servlets.ApplicationListener</listener-class>
</listener>

com.mycompany.servlets.ApplicationListener的来源:

package com.mycompany.servlets;

public class ApplicationListener implements ServletContextListener 

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) 
        // this method is invoked once when web-application is deployed (started)

        // reading properties file
        FileInputStream fis = null;
        Properties properties = new Properties();
        try 
            fis = new FileInputStream("path/to/db.properties")    
            properties.load(fis);
         catch(IOException ex) 
            throw new RuntimeException(ex);
         finally 
            try 
                if(fis != null) 
                    fis.close();
                
             catch(IOException e) 
                throw new RuntimeException(e);
            
        

        // creating data source instance
        SomeDataSourceImpl dataSource = new SomeDataSourceImpl();
        dataSource.setJdbcUrl(properties.getProperty("db.url"));
        dataSource.setUser(properties.getProperty("db.user"));
        dataSource.setPassword(properties.getProperty("db.pwd"));

        // storing reference to dataSource in ServletContext attributes map
        // there is only one instance of ServletContext per web-application, which can be accessed from almost anywhere in web application(servlets, filters, listeners etc)
        final ServletContext servletContext = servletContextEvent.getServletContext();
        servletContext.setAttribute("some-data-source-alias", dataSource);
    

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) 
        // this method is invoked once when web-application is undeployed (stopped) - here one can (should) implement resource cleanup etc
    


然后,在 Web 应用程序代码的某处访问 dataSource

ServletContext servletContext = ...; // as mentioned above, it should be accessible from almost anywhere
DataSource dataSource = (DataSource) servletContext.getAttribute("some-data-source-alias");
// use dataSource

SomeDataSourceImpl 是javax.sql.DataSource 的一些具体实现。请告知您是否不使用特定的DataSources(例如ComboPooledDataSource 用于连接池)并且不知道如何获取它 - 我将发布如何绕过它。

some-data-source-alias - 在ServletContext 属性映射中只是DataSource 实例的String 别名(键)。好的做法是给别名加上包名,如com.mycompany.mywebapp.dataSource

希望这会有所帮助...

【讨论】:

感谢您的回答。我相信它完美无缺。我想知道是否存在一些开箱即用的解决方案。 您的意思是使用JNDI 和Tomcat 的DataSource 的解决方案吗? 基本上是的。我想知道 Tomcat 是否接受 context.xml 中的某种变量,因此管理员可以将这些变量保存在外部属性文件中。 根据docs.oracle.com/javase/jndi/tutorial/beyond/env/source.html:可以在jndi.properties 文件中指定JNDI 资源,但它应该驻留在应用程序类路径或JAVA_HOME/lib/jndi.properties 中,这是非常有限的。看起来 Tomcat 在context.xml 中只使用了JNDI 功能,所以很可能不可能有像url="$db.url" 这样的东西从某个外部文件引用值。我见过 context.xml 在打包到 .war 文件之前被 Ant 预处理的情况。 谢谢。所以似乎没有开箱即用的解决方案。【参考方案2】:

如果这是 Tomcat 7,您可以编写自己的 org.apache.tomcat.util.IntrospectionUtils.PropertySource 实现读取变量,如 context.xml 中的“$...”。您需要将系统属性 org.apache.tomcat.util.digester.PROPERTY_SOURCE 设置为指向您的 PropertySource 实现。

【讨论】:

【参考方案3】:

如here 所述,您可以通过以下方式执行此操作。

1.下载tomcat库获取接口定义,例如定义maven依赖:

    <dependency>
        <groupId>org.apache.tomcat</groupId>
        <artifactId>tomcat-coyote</artifactId>
        <version>7.0.47</version>
    </dependency>

2.下一步是通过以下方式创建一个com.mycompany.MyPropertyDecoder:

import org.apache.tomcat.util.IntrospectionUtils;
public class MyPropertyDecoder implements IntrospectionUtils.PropertySource  
    @Override
    public String getProperty(String arg0) 
        //TODO read properties here
        return null;
    

3.将 MyPropertyDecoder.class 放入 tomcat7/lib 文件夹 4.定义org.apache.tomcat.util.digester。 tomcat7/conf/catalina.properties 的 PROPERTY_SOURCE 属性如下:

org.apache.tomcat.util.digester.PROPERTY_SOURCE=com.mycompany.MyPropertyDecoder

5.用属性变量更新你的 context.xml

<Resource name="jdbc/TestDB"
           auth="Container"
           type="javax.sql.DataSource"
           username="root"
           password="$db.password"
           driverClassName="com.mysql.jdbc.Driver"
           url="jdbc:mysql://localhost:3306/mysql?autoReconnect=true"
           ...  

6.将 application.properties 文件放在您的项目/容器中的某处 7.确保 MyPropertyDecoder 正确读取 application.properties 8.享受吧!

PS 另外,tc Server 也有类似的方法。

【讨论】:

太好了,这对我有用。您知道是否可以使用相同的方法来启用/禁用代码 sn-p,即使用属性文件的条目值(如标志)? Objnewbie,使用属性文件来启用/禁用应用程序的某些功能是可以的。但是,您不应该为此实现 Tomcat 的 IntrospectionUtils.PropertySource。因为这只是为了让 Tomcat 容器了解您的属性文件。在您的场景中,我建议使用属性文件和一些方便的方法来读取它,例如 Spring 框架中的 @Value 注释。 非常感谢。不幸的是,我们的团队不使用 Spring,而是使用 Struts 1.3.10。我发布了a question application.properties 文件可以放在与 Tomcat lib 文件夹不同的位置吗?从一些测试来看,在我看来这是不可能的(例如在 WEB-INF 项目文件夹中) 当然可以。通常,该位置应该存在于类路径中,这样就可以了。【参考方案4】:

context deploy descriptors 很容易,看起来像:

<Context docBase="$basedir/src/main/webapp"
         reloadable="true">
    <!-- http://tomcat.apache.org/tomcat-7.0-doc/config/context.html -->
    <Resources className="org.apache.naming.resources.VirtualDirContext"
               extraResourcePaths="/WEB-INF/classes=$basedir/target/classes,/WEB-INF/lib=$basedir/target/$project.build.finalName/WEB-INF/lib"/>
    <Loader className="org.apache.catalina.loader.VirtualWebappLoader"
            virtualClasspath="$basedir/target/classes;$basedir/target/$project.build.finalName/WEB-INF/lib"/>
    <JarScanner scanAllDirectories="true"/>

    <Parameter name="min" value="dev"/>
    <Environment name="app.devel.ldap" value="USER" type="java.lang.String" override="true"/>
    <Environment name="app.devel.permitAll" value="true" type="java.lang.String" override="true"/>
</Context>

有几个地方可以放置这个配置,我认为最好的选择是$CATALINA_BASE/conf/[enginename]/[hostname]/$APP.xml

在上面的 XML Context 中可以保存自定义 Loader org.apache.catalina.loader.VirtualWebappLoader(在现代 Tomcat 7 中可用,您可以将每个应用程序拥有单独的类路径添加到您的 .properties 文件),@987654334 @(通过FilterConfig.getServletContext().getInitParameter(name)访问)和Environment(通过new InitialContext().lookup("java:comp/env").lookup("name")访问)

见讨论:

Adding a directory to tomcat classpath Can I create a custom classpath on a per application basis in Tomcat How to read a properties file outside my webapp context in Tomcat Configure Tomcat to use properties file to load DB connection information Should you set up database connection properties in server.xml or context.xml

更新 Tomcat 8 change syntax for &lt;Resources&gt;&lt;Loader&gt; 元素,对应部分现在看起来像:

<Resources>
    <PostResources className="org.apache.catalina.webresources.DirResourceSet"
                   webAppMount="/WEB-INF/classes" base="$basedir/target/classes" />
    <PostResources className="org.apache.catalina.webresources.DirResourceSet"
                   webAppMount="/WEB-INF/lib" base="$basedir/target/$project.build.finalName/WEB-INF/lib" />
</Resources>

【讨论】:

以上是关于外部化 Tomcat 配置的主要内容,如果未能解决你的问题,请参考以下文章

将 grails 应用程序配置外部化到 tomcat conf 目录

tomcat.配置外部应用

tomcat配置外部静态资源映射路径

IDEA 使用外部Tomcat配置文件

SpringBoot项目部署到外部Tomcat的相关配置

如何配置 Tomcat 以提供来自 webapps 外部文件夹的图像? [复制]