指定`DataSource`工厂而不是Tomcat的默认值

问题描述 投票:0回答:1

tl; dr

我如何告诉Tomcat 9使用Postgres-specific object factory来生成DataSource对象以响应DataSource查询?

详细信息

通过定义与我的上下文相同名称的XML文件,我可以轻松地从JNDI 9中获得DataSource对象。例如,对于名为DataSource的Web应用程序,我创建此文件:

Apache Tomcat

我将该文件放置在我的Tomcat“基本”文件夹中,在clepsydra文件夹中,在用引擎名称<?xml version="1.0" encoding="UTF-8"?> <Context> <!-- Domain: DEV, TEST, ACPT, ED, PROD --> <Environment name = "work.basil.example.deployment-mode" description = "Signals whether to run this web-app with development, testing, or production settings." value = "DEV" type = "java.lang.String" override = "false" /> <Resource name="jdbc/postgres" auth="Container" type="javax.sql.DataSource" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://127.0.0.1:5432/mydb" username="myuser" password="mypasswd" /> </Context> 和主机名conf创建的文件夹中。 Tomcat将设置馈入资源工厂以返回Catalina的实例。我可以通过JNDI访问该实例:

localhost

我想要DataSource,而不是Context ctxInitial = new InitialContext(); DataSource dataSource = ( DataSource ) ctxInitial.lookup( "java:comp/env/jdbc/postgres" ) ;

此设置使用org.postgresql.ds.PGSimpleDataSource。返回的org.apache.tomcat.dbcp.dbcp2.BasicDataSource类的基础类是Tomcat’s own resource factory for JDBC DataSource objects。不幸的是,我不想要该类的DataSource。我想要org.apache.tomcat.dbcp.dbcp2.BasicDataSourceDataSource提供的类的DataSource

[通过阅读Tomcat文档页面JDBC driver from The PostgreSQL Global Development Grouporg.postgresql.ds.PGSimpleDataSource,我意识到Tomcat允许我们为这些org.postgresql.ds.PGSimpleDataSource对象使用替代工厂,以代替与Tomcat捆绑在一起的默认工厂实现。听起来像我需要的。

JNDI Resources How-To

[我发现Postgres JDBC驱动程序已经与此类实现捆绑在一起:

顺便说一下,OSGi应用程序的驱动程序PGObjectFactory中内置了一个类似的工厂。我认为这对Tomcat没有用。

因此,PGXADataSourceFactory类实现JNDI所需的接口PGXADataSourceFactory

SPI

我猜测该包名称中的XA表示对象工厂通过DataSource加载。

PGDataSourceFactory

因此,我认为需要一个SPI映射文件,如PGDataSourceFactoryPGObjectFactory中所述。在我的Vaadin javax.naming.spi.ObjectFactory文件夹中添加了javax.naming.spi.ObjectFactory文件夹,并创建了一个进一步嵌套在其中的spi文件夹。因此,在Java Service Provider Interface (SPI)中创建了一个名为screenshot of location of SPI mapping file, and its contents的文件,其中包含一行文本,即所需对象工厂的名称:Oracle Tutorial。我什至在Postgres JDBC驱动程序内部检查,以物理方式验证此类的存在和全限定名称。

问题

➥我的问题是:如何告诉Tomcat使用Vaadin documentation而不是其默认对象工厂来产生我的META-INF对象来产生与Postgres数据库的连接?

resources元素上的[services属性

我曾希望这会像在上面看到的/resources/META-INF/services元素中添加javax.naming.spi.ObjectFactory属性(org.postgresql.ds.common.PGObjectFactory)一样简单。我从Tomcat页面PGObjectFactory中得到了这个想法。该页面专注于全局资源,因此非常令人困惑,但是我不需要或不想全局定义此DataSource。我只有一个Web应用程序需要此factory

[添加<Resource>属性:

factory

…我的factory="org.postgresql.ds.common.PGObjectFactory"对象为null时失败。

<Resource>

null

删除The Context Container属性可解决该异常。但是然后我又回到了Tomcat DataSource而不是Postgres DataSource的位置。因此,我的问题在这里。

我知道factory XML已成功加载,因为我可以访问该<?xml version="1.0" encoding="UTF-8"?> <Context> <!-- Domain: DEV, TEST, ACPT, ED, PROD --> <Environment name = "work.basil.example.deployment-mode" description = "Signals whether to run this web-app with development, testing, or production settings." value = "DEV" type = "java.lang.String" override = "false" /> <Resource name="jdbc/postgres" auth="Container" type="javax.sql.DataSource" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://127.0.0.1:5432/mydb" username="myuser" password="mypasswd" factory="org.postgresql.ds.common.PGObjectFactory" /> </Context> 条目的值。

第二实验

几天后,我从顶部尝试了此操作。

我创建了一个名为“ datasource-object-factory”的新的“纯Java Servlet”类Vaadin 14.0.9项目。

这是我完整的Vaadin网络应用代码。下半部分是JNDI查找。

DataSource

为了简单起见,我没有指定Tomcat“基本”文件夹,而是使用默认文件夹。我不是从IntelliJ运行,而是将我的Web应用程序的WAR文件手动移动到ctxInitial = new InitialContext(); DataSource dataSource = ( DataSource ) ctxInitial.lookup( "java:comp/env/jdbc/postgres" ); System.out.println( "dataSource = " + dataSource ); 文件夹。

我下载了Tomcat的新版本9.0.27。我将Postgres JDBC jar拖到factory="org.postgresql.ds.common.PGObjectFactory"文件夹中。我使用BatChmod应用程序来设置Tomcat文件夹的权限。

BasicDataSource文件夹中,创建了PGSimpleDataSourceContext文件夹。在其中,我创建了一个名为Environment的文件,其内容与上面所示的相同。

package work.basil.example;

import com.vaadin.flow.component.ClickEvent;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.PWA;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

/**
 * The main view contains a button and a click listener.
 */
@Route ( "" )
@PWA ( name = "Project Base for Vaadin", shortName = "Project Base" )
public class MainView extends VerticalLayout
{

    public MainView ( )
    {
        Button button = new Button( "Click me" ,
                event -> Notification.show( "Clicked!" ) );


        Button lookupButton = new Button( "BASIL - Lookup DataSource" );
        lookupButton.addClickListener( ( ClickEvent < Button > buttonClickEvent ) -> {
            Notification.show( "BASIL - Starting lookup." );
            System.out.println( "BASIL - Starting lookup." );
            this.lookupDataSource();
            Notification.show( "BASIL - Completed lookup." );
            System.out.println( "BASIL - Completed lookup." );
        } );

        this.add( button );
        this.add( lookupButton );
    }

    private void lookupDataSource ( )
    {
        Context ctxInitial = null;
        try
        {
            ctxInitial = new InitialContext();

            // Environment entry.
            String deploymentMode = ( String ) ctxInitial.lookup( "java:comp/env/work.basil.example.deployment-mode" );
            Notification.show( "BASIL - deploymentMode: " + deploymentMode );
            System.out.println( "BASIL - deploymentMode = " + deploymentMode );

            // DataSource resource entry.
            DataSource dataSource = ( DataSource ) ctxInitial.lookup( "java:comp/env/jdbc/postgres" );
            Notification.show( "BASIL - dataSource: " + dataSource );
            System.out.println( "BASIL - dataSource = " + dataSource );
        }
        catch ( NamingException e )
        {
            Notification.show( "BASIL - NamingException: " + e );
            System.out.println( "BASIL - NamingException: " + e );
            e.printStackTrace();
        }
    }
}

我将Web应用程序的webapps文件复制到Tomcat中的/lib。最后,我运行Tomcat的conf,并观察WAR文件爆炸到一个文件夹中。

在我的Catalina元素上具有localhost属性后,所得的datasource-object-factory.xml<?xml version="1.0" encoding="UTF-8"?> <Context> <!-- Domain: DEV, TEST, ACPT, ED, PROD --> <Environment name = "work.basil.example.deployment-mode" description = "Signals whether to run this web-app with development, testing, or production settings." value = "DEV" type = "java.lang.String" override = "false" /> <Resource factory="org.postgresql.ds.common.PGObjectFactory" name="jdbc/postgres" auth="Container" type="javax.sql.DataSource" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://127.0.0.1:5432/mydb" username="myuser" password="mypasswd" /> </Context>

与我的第一个实验一样,我可以访问datasource-object-factory.war的值,所以我知道可以找到我的上下文名称XML文件并通过JNDI成功处理。

以下是Google云端硬盘上的日志:

  • webapps
  • /bin/startup.sh
java tomcat servlets jndi objectfactory
1个回答
1
投票

您的资源配置似乎需要修改。如factory="org.postgresql.ds.common.PGObjectFactory"中所述,

您可以在Web应用程序部署描述符中声明要为JNDI查找和元素返回的资源的特征。 [您还必须将所需的资源参数定义为Resource元素的属性,以配置对象工厂(如果尚未被Tomcat知道)和用于配置该对象工厂的属性。

得到空值的原因是,对象工厂无法确定需要创建的对象类型,请参阅Resource

DataSource

资源定义中的值'javax.sql.DataSource'与对象工厂可以理解的任何类都不对应,请使用对象工厂可以理解的一个类,在您的情况下为'org.postgresql.ds.PGSimpleDataSource' 。

但是,这仍然不能为您提供有效的数据源,原因是,请在同一源代码中引用以下部分:

null

<Environment>

catalina.out在所有数据源的超类上调用catalina.2019-10-18.log,请参考:Tomcat Documentation,部分:

PGObjectFactory code

以上要求三个属性,即。 'databaseName','portNumber'和'serverName',因此这些属性也必须位于资源定义中。

总计,您的资源声明可能应该如下所示:

public Object getObjectInstance ( Object obj , Name name , Context nameCtx ,
                                  Hashtable < ?, ? > environment ) throws Exception
{
    Reference ref = ( Reference ) obj;
    String className = ref.getClassName();
    // Old names are here for those who still use them
    if ( 
            className.equals( "org.postgresql.ds.PGSimpleDataSource" )
            || className.equals( "org.postgresql.jdbc2.optional.SimpleDataSource" )
            || className.equals( "org.postgresql.jdbc3.Jdbc3SimpleDataSource" ) 
    )
    {
        return loadSimpleDataSource( ref );
    } else if ( 
            className.equals( "org.postgresql.ds.PGConnectionPoolDataSource" )
            || className.equals( "org.postgresql.jdbc2.optional.ConnectionPool" )
            || className.equals( "org.postgresql.jdbc3.Jdbc3ConnectionPool" ) 
    )
    {
        return loadConnectionPool( ref );
    } else if ( 
            className.equals( "org.postgresql.ds.PGPoolingDataSource" )
            || className.equals( "org.postgresql.jdbc2.optional.PoolingDataSource" )
            || className.equals( "org.postgresql.jdbc3.Jdbc3PoolingDataSource" ) 
    )
    {
        return loadPoolingDataSource( ref );
    } else
    {
        return null;
    }
}

然后您应该像已经完成的那样解析数据源,并使用getConnection(userName,pwd)获得连接。

注意:您还可以设置private Object loadSimpleDataSource(Reference ref) { PGSimpleDataSource ds = new PGSimpleDataSource(); return loadBaseDataSource(ds, ref); } 中定义的'userName'和'password'属性。

总而言之,我们可以将您的原始示例修改为如下所示。我们使用一些protected Object loadBaseDataSource(BaseDataSource ds, Reference ref) { ds.setFromReference(ref); return ds; }

loadBaseDataSource
© www.soinside.com 2019 - 2024. All rights reserved.