将Tomcat配置外部化

18

我在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文件已经是一个外部文件了。你为什么认为你需要另一个文件呢? - user207421
4个回答

14

此处所述,您可以按以下方式进行操作。

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.在tomcat7/conf/catalina.properties中定义org.apache.tomcat.util.digester.PROPETY_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服务器


太好了,这对我有用。您知道是否可以使用相同的方法来启用/禁用代码片段,即通过使用属性文件的条目值作为标志? - Sefran
1
Objnewbie,使用属性文件来启用/禁用应用程序的某些功能是可以的。但是你不应该为此实现Tomcat的IntrospectionUtils.PropertySource。因为这只是为了让Tomcat容器理解你的属性文件。在你的情况下,我建议使用属性文件和一些方便的读取方式,例如Spring框架中的@Value注释。 - Anton Shchastnyi
非常感谢。不幸的是,我们的团队不使用Spring,而是使用Struts 1.3.10。我发布了一个问题。 - Sefran
应用程序属性文件可以放在与Tomcat lib文件夹不同的其他位置吗?从一些测试来看,我觉得这似乎是不可能的(例如在WEB-INF项目文件夹中)。 - Sefran
当然可以。通常,位置应该存在于类路径中,那样就可以工作了。 - Anton Shchastnyi
如果我将它放在WEB-INF/classesWEB-INF/lib中,并以不同的方式更改web.xml,我会得到一个“意外异常解析引用java.lang.NumberFormatException”(如果文件在tomcat lib中,则不会发生这种情况)。 - Sefran

5

有了上下文部署描述符,这就很容易了(详情请参考链接)。上下文部署描述符如下:

<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可以容纳自定义Loaderorg.apache.catalina.loader.VirtualWebappLoader(在现代Tomcat 7中可用,您可以将自己的单独类路径添加到每个应用程序.properties文件中),Parameter(通过FilterConfig.getServletContext().getInitParameter(name)访问)和Environment(通过new InitialContext().lookup("java:comp/env").lookup("name")访问)
请参见以下讨论: 更新 Tomcat 8更改了 <Resources><Loader>元素的语法,相应部分现在如下:
<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>

2
当然,这是可以实现的。您需要像这样在web.xml中注册ServletContextListener
<!-- 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
    }

}

然后,在访问dataSource的web应用程序代码中:

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 的一种具体实现。如果您不使用特定的 DataSource(如连接池 ComboPooledDataSource)并且不知道如何获取它,请告诉我 - 我将发布如何绕过此步骤。

some-data-source-alias - 只是您在 ServletContext 属性映射中为 DataSource 实例设置的别名(键)String。良好的做法是以包名开头给出别名,如 com.mycompany.mywebapp.dataSource

希望这有所帮助...


谢谢您的回答。我相信它完美地运作。不知道是否存在一些开箱即用的解决方案。 - never
您是指使用Tomcat的DataSourceJNDI的解决方案吗? - Yuriy Nakonechnyy
基本上是的。我想知道Tomcat是否接受在context.xml中使用某种变量,以便管理员可以将这些变量保存在外部属性文件中。 - never
2
根据http://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}"。我见过一种情况,即在打包成.war文件之前,context.xml会被Ant预处理。 - Yuriy Nakonechnyy
谢谢你。所以似乎没有现成的解决方案。 - never

0
如果您使用的是Tomcat 7,您可以编写自己的org.apache.tomcat.util.IntrospectionUtils.PropertySource实现来读取像"${...}"这样写入的变量在context.xml中。您需要设置系统属性org.apache.tomcat.util.digester.PROPERTY_SOURCE指向您的PropertySource实现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接