【问题标题】:Externalize Tomcat configuration外部化 Tomcat 配置
【发布时间】: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 文件已经是一个外部文件。为什么你认为你需要另一个?

标签: tomcat


【解决方案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

SomeDataSourceImpljavax.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")访问)

    见讨论:

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

    【讨论】:

      猜你喜欢
      • 2012-04-01
      • 1970-01-01
      • 2017-11-24
      • 2019-08-01
      • 2021-02-22
      • 2018-10-03
      • 2014-07-23
      • 2012-01-25
      • 2019-08-17
      相关资源
      最近更新 更多