【问题标题】:Tomcat: How to programmatically define a Datasource Resource in GlobalNamingResources and link it to appsTomcat:如何以编程方式在 GlobalNamingResources 中定义数据源资源并将其链接到应用程序
【发布时间】:2023-09-17 00:59:01
【问题描述】:

我正在使用 Tomcat9 部署多个共享相同数据源连接池的应用程序。我通过以下方式达到了目的:

  1. 将数据源添加为 conf/server.xml 中 GlobalNamingResources 下的 Resource 元素,
  2. 使用 conf/context.xml 中的 ResourceLink 元素链接全局 JNDI 资源,使其可用于所有 Web 应用程序,并且
  3. 使用 JNDI 从 Web 应用程序访问数据源。

它按预期工作。

但是,我要求避免编辑任何 xml(server.xml 或 conf/context.xml 或特定于应用程序的 context.xml)并尝试以编程方式实现第 1 步和第 2 步。如果是这样,好处是,只需要修改一个需要添加到 /lib 中的 jar 以支持新的数据源,而对任何 xml 绝对没有修改(或者可能在 server.xml 中具有最低配置)。

有没有办法实现将资源定义为 GlobalNamingResources 并通过 ResourceLink 将其链接到 Web 应用程序的要求,全部通过 Java?

【问题讨论】:

  • 当然可以。但这将要求您构建一个“嵌入式”应用程序,在其中编写 Tomcat 启动脚本,配置通常在 conf/server.xml 中配置的所有内容。 IMO 真的不值得。这是一个非常奇怪的要求:“避免编辑任何 XML”。您可以将其 (conf/server.xml) 放入版本控制中,并且您不必维护自己的代码来配置数据源。为什么要让自己变得更难?

标签: tomcat datasource tomcat9


【解决方案1】:

通过tomcat javadocs后,我已经达到了我的要求。下面给出详细信息:

1.将数据源添加为 GlobalNamingResource

为此,创建一个服务器生命周期监听器,创建一个数据源并将其添加到 JNDI GlobalNamingContext。

public class GlobalDatasourceCreator implements LifecycleListener {
    @Override
    public void lifecycleEvent(LifecycleEvent event) {
        if (event.getSource() instanceof Server) {
            Context namingContext = ((Server) event.getSource()).getGlobalNamingContext();
            if (Lifecycle.START_EVENT.equals(event.getType())) {
                bindDatasources(namingContext);
            } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
                unbindDatasources(namingContext);
            }
        }
    } 

    private void bindDatasources(Context namingContext) {
        if (createSubContext(namingContext)) {
            try {
                DataSource ds = getDatasource(); //TODO: Implement it 
                namingContext.rebind("jdbc/myds_global", ds);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private boolean createSubContext(Context namingContext) {
        try {
            namingContext.createSubcontext("jdbc");
        } catch (NameAlreadyBoundException e) {
        } catch (NamingException e) {
            return false;
        }
        return true;
    }

    private void unbindDatasources(Context namingContext) {
        try {
            namingContext.unbind("jdbc/myds_global");
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }

然后将这个类添加到conf/server.xml作为监听器

<Listener className="com.test.GlobalDatasourceCreator" />

2。通过 ResourceLink 向所有 Web 应用程序公开数据源

创建一个上下文 LifecycleListener。在 START 事件上,创建一个 ResourceLink 并将其附加到上下文。

注意:由于这是上下文级别的侦听器,因此将为所有应用程序创建 ResourceLink。我的要求是将它公开给所有应用程序,因为它是一个受控环境。如果仅需要为选定的应用程序创建 ResourceLink,则可以应用基于上下文名称的过滤。

public class AppDatasourceLinkCreator implements LifecycleListener {

    @Override
    public void lifecycleEvent(LifecycleEvent event) {
        if (event.getSource() instanceof Context) {
            Context ctx = (Context) event.getSource();
            if (Lifecycle.START_EVENT.equals(event.getType())) {
                addResourceLink(ctx);
            } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
                removeResourceLink(ctx);
            }
        } 
    }

    private void removeResourceLink(Context ctx) {
        ctx.getNamingResources().removeResourceLink("jdbc/myds");
    }

    private void addResourceLink(Context ctx) {
        ContextResourceLink resourceLink = new ContextResourceLink();
        resourceLink.setGlobal("jdbc/myds_global");
        resourceLink.setName("jdbc/myds");
        resourceLink.setType("javax.sql.DataSource");
        ctx.getNamingResources().addResourceLink(resourceLink);
    }
}

然后将这个类添加到conf/context.xml作为监听器

    <Listener className="com.test.AppDatasourceLinkCreator" />

创建一个包含这两个类的 jar 并将其放在 /lib 文件夹中。

优势:无需进一步修改 xml 即可添加任意数量的数据源。只需修改java代码添加新的数据源,更新lib文件夹中的jar并重新启动服务器,完全符合我的项目要求。这也解决了在 xml 中以纯文本形式公开数据源凭据的问题(尽管不是 100% 风险证明)。

【讨论】: