指定`DataSource`工厂而不是Tomcat的默认工厂 [英] Specify a `DataSource` factory instead of Tomcat's default

查看:121
本文介绍了指定`DataSource`工厂而不是Tomcat的默认工厂的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何告诉Tomcat 9使用特定于Postgres DataSource 对象,以响应 JNDI 查询?

How do I tell Tomcat 9 to use a Postgres-specific object factory for producing DataSource object in response to JNDI query?

我可以轻松获得 Apache Tomcat 9中获取nofollow noreferrer> DataSource 对象文件名与我的上下文相同.例如,对于名为clepsydra的网络应用程序,我创建了以下文件:

I can easily get a DataSource object from Apache Tomcat 9 by defining an XML file named the same as my context. For example, for a web-app named clepsydra, I create this file:

<?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"
                user="myuser"
                password="mypasswd"
                />
</Context>

我将该文件放置在我的Tomcat基本"文件夹中,在conf文件夹中,在用引擎名称Catalina和主机名localhost创建的文件夹中. Tomcat将设置馈入资源工厂以返回DataSource的实例.我可以通过JNDI访问该实例:

I place that file in my Tomcat "base" folder, in conf folder, in folders I created with engine name Catalina and host name localhost. Tomcat feeds settings into a resource factory to return an instance of DataSource. I can access that instance via JNDI:

Context ctxInitial = new InitialContext();
DataSource dataSource = 
        ( DataSource ) ctxInitial.lookup( "java:comp/env/jdbc/postgres" )
;

我确实意识到,该查找字符串中的postgres可能是特定于特定应用程序的内容.但是,让我们继续使用postgres进行相同的演示.

I do realize that postgres in that lookup string could be something more specific to a particular app. But let's go with postgres for the same of demonstration.

此设置使用 Tomcat自己的资源工厂JDBC数据源对象.返回的DataSource类的基础类是org.apache.tomcat.dbcp.dbcp2.BasicDataSource.不幸的是,我不希望该类的DataSource.我需要 PostgreSQL全球开发小组 提供的类的DataSource /a>: org.postgresql.ds.PGSimpleDataSource .

This setup is using Tomcat’s own resource factory for JDBC DataSource objects. The underlying class of the returned DataSource class is org.apache.tomcat.dbcp.dbcp2.BasicDataSource. Unfortunately, I do not want a DataSource of that class. I want a DataSource of the class provided by the JDBC driver from The PostgreSQL Global Development Group: org.postgresql.ds.PGSimpleDataSource.

通过阅读Tomcat文档页面, JNDI参考资料 JNDI数据源方法 ,我意识到Tomcat允许我们为这些DataSource对象使用替代工厂,以代替与Tomcat捆绑在一起的默认工厂实现.听起来像我的需要.

By reading the Tomcat documentation pages, JNDI Resources How-To and JNDI Datasource How-To, I came to realize that Tomcat allows us to use an alternate factory for these DataSource objects in place of the default factory implementation bundled with Tomcat. Sounds like what I need.

我发现Postgres JDBC驱动程序已经与以下实现捆绑在一起:

I discovered that the Postgres JDBC driver already comes bundled with such implementations:

  • PGObjectFactory
    For simple JDBC connections.
  • PGXADataSourceFactory
    For XA-enabled DataSource implementation, for distributed transactions.

顺便说一句,在OSGi应用程序的驱动程序中内置了一个类似的工厂, PGDataSourceFactory .我认为这对Tomcat毫无用处.

By the way, there is a similar factory built into the driver for OSGi apps, PGDataSourceFactory. I assume that is of no use to me with Tomcat.

因此,PGObjectFactory类实现了接口 javax.naming.spi.ObjectFactory .

So, the PGObjectFactory class implements the interface javax.naming.spi.ObjectFactory required by JNDI.

我猜测该包名称中的spi意味着对象工厂通过 Java服务提供商接口(SPI) .

I am guessing that the spi in that package name means the object factories load via the Java Service Provider Interface (SPI).

因此,我认为需要一个SPI映射文件,如 Oracle教程 Vaadin文档中.在我的Vaadin resources文件夹中添加了一个META-INF文件夹,并创建了一个进一步嵌套在其中的services文件夹.因此,在/resources/META-INF/services中,我创建了一个名为javax.naming.spi.ObjectFactory的文件,其中包含一行文本,即我想要的对象工厂的名称:org.postgresql.ds.common.PGObjectFactory.我什至在Postgres JDBC驱动程序内部检查,以物理方式验证此类的存在和全限定名称.

So I presume that need a SPI mapping file, as discussed in the Oracle Tutorial and in the Vaadin documentation. added a META-INF folder to my Vaadin resources folder, and created a services folder further nested there. So in /resources/META-INF/services I created a file named javax.naming.spi.ObjectFactory containing a single line of text, the name of my desired object factory: org.postgresql.ds.common.PGObjectFactory. I even checked inside the Postgres JDBC driver to verify physically the existence and the fully-qualified name of this class.

➥我的问题是:我如何告诉Tomcat使用PGObjectFactory而不是其默认对象工厂来产生我的DataSource对象,以产生与Postgres数据库的连接?

➥ My question is: How do I tell Tomcat to use PGObjectFactory rather than its default object factory for producing my DataSource objects for producing connections to my Postgres database?

我曾希望这会像在上面看到的<Resource>元素中添加factory属性(factory="org.postgresql.ds.common.PGObjectFactory")一样简单.我从Tomcat页面 The Context中获得了这个想法容器 .该页面专注于全局资源,因此非常混乱,但是我不需要或不想全局定义此DataSource.我仅对一个Web应用程序需要此DataSource.

I had hoped it would be as simple as adding a factory attribute (factory="org.postgresql.ds.common.PGObjectFactory") to my <Resource> element seen above. I got this idea from the Tomcat page, The Context Container. That page is quite confusing as it focuses on global resource, but I do not need or want to define this DataSource globally. I need this DataSource only for my one web app.

添加factory属性:

<?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"
                user="myuser"
                password="mypasswd"
                factory="org.postgresql.ds.common.PGObjectFactory"
                />
</Context>

…由于我的DataSource对象为空而失败.

…fails with my DataSource object being null.

ctxInitial = new InitialContext();
DataSource dataSource = ( DataSource ) ctxInitial.lookup( "java:comp/env/jdbc/postgres" );
System.out.println( "dataSource = " + dataSource );

删除该factory="org.postgresql.ds.common.PGObjectFactory"属性可解决该异常.但是然后我又回到了Tomcat BasicDataSource而不是Postgres PGSimpleDataSource的位置.这就是我的问题.

Removing that factory="org.postgresql.ds.common.PGObjectFactory" attribute resolves the exception. But then I am back to getting a Tomcat BasicDataSource rather than a Postgres PGSimpleDataSource. Thus my Question here.

我知道我的Context XML已成功加载,因为我可以访问该Environment条目的值.

I know my Context XML is being loaded successfully because I can access that Environment entry’s value.

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

I tried this from the top, days later.

我创建了一个新的名为"Plain Java Servlet"的Vaadin 14.0.9项目,名为"datasource-object-factory".

I created a new "Plain Java Servlet" flavor Vaadin 14.0.9 project named "datasource-object-factory".

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

Here is my entire Vaadin web app code. The bottom half is the JNDI lookup.

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();
        }
    }
}

为简单起见,我没有指定Tomcat基本"文件夹,而是使用默认文件夹.我不是从IntelliJ运行,而是将我的Web应用程序的WAR文件手动移动到webapps文件夹.

To keep things simple, I did not designate a Tomcat "base" folder, instead going with defaults. I did not run from IntelliJ, instead moving my web app’s WAR file manually to the webapps folder.

我下载了Tomcat的新版本9.0.27.我将Postgres JDBC jar拖到/lib文件夹中.我使用BatChmod应用程序设置了Tomcat文件夹的权限.

I downloaded a new version of Tomcat, version 9.0.27. I dragged in the Postgres JDBC jar to the /lib folder. I used the BatChmod app to set the permissions of the Tomcat folder.

conf文件夹中,我创建了Catalina& localhost文件夹.在其中,我创建了一个名为datasource-object-factory.xml的文件,其内容与上面看到的相同.

To the conf folder, I created the Catalina & localhost folders. In there I created a file named datasource-object-factory.xml with the same contents as seen above.

<?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"
                user="myuser"
                password="mypasswd"
                />
</Context>

我将Web应用程序的datasource-object-factory.war文件复制到Tomcat中的webapps.最后,我运行Tomcat的/bin/startup.sh并观察WAR文件爆炸到一个文件夹中.

I copied my web app’s datasource-object-factory.war file to webapps in Tomcat. Lastly, I run Tomcat's /bin/startup.sh and watch the WAR file explode into a folder.

在我的Resource元素上具有factory="org.postgresql.ds.common.PGObjectFactory"属性,生成的DataSourcenull.

With the factory="org.postgresql.ds.common.PGObjectFactory" attribute on my Resource element, the resulting DataSource is null.

与第一个实验一样,我可以访问<Environment>的值,因此我知道可以找到我的上下文名称XML文件并通过JNDI成功处理.

As with my first experiment, I can access the value of the <Environment>, so I know my context-name XML file is being found and processes successfully via JNDI.

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

Here are the logs on a Google Drive:

  • catalina.out
  • catalina.2019-10-18.log

推荐答案

您的资源配置似乎需要修改.如 Tomcat文档

Your resource configuration seems to need modification. As mentioned in Tomcat Documentation,

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

You can declare the characteristics of the resource to be returned for JNDI lookups of and elements in the web application deployment descriptor. You MUST also define the needed resource parameters as attributes of the Resource element, to configure the object factory to be used (if not known to Tomcat already), and the properties used to configure that object factory.

获取空值的原因是对象工厂无法确定需要创建的对象类型,请参考

The reason you are getting null, is the object factory cannot determine the type of object it needs to create, refer PGObjectFactory code

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;
    }
}

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

The value 'javax.sql.DataSource' in the resource definition does not correspond to any of the class the object factory understands, use one of the classes the object factory understands, in your case 'org.postgresql.ds.PGSimpleDataSource'.

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

However, this will still not get you a valid data source, reason being, refer, in the same source code, the following sections:

private Object loadSimpleDataSource(Reference ref) {
    PGSimpleDataSource ds = new PGSimpleDataSource();
    return loadBaseDataSource(ds, ref);
}

protected Object loadBaseDataSource(BaseDataSource ds, Reference ref) {
    ds.setFromReference(ref);

    return ds;
}

loadBaseDataSource在所有数据源的超类上调用setFromReference,请参见:

The loadBaseDataSource calls setFromReference on the super class of all data sources, refer: BaseDataSource, section:

public void setFromReference ( Reference ref )
{
    databaseName = getReferenceProperty( ref , "databaseName" );
    String portNumberString = getReferenceProperty( ref , "portNumber" );
    if ( portNumberString != null )
    {
        String[] ps = portNumberString.split( "," );
        int[] ports = new int[ ps.length ];
        for ( int i = 0 ; i < ps.length ; i++ )
        {
            try
            {
                ports[ i ] = Integer.parseInt( ps[ i ] );
            }
            catch ( NumberFormatException e )
            {
                ports[ i ] = 0;
            }
        }
        setPortNumbers( ports );
    } else
    {
        setPortNumbers( null );
    }
    setServerNames( getReferenceProperty( ref , "serverName" ).split( "," ) );

    for ( PGProperty property : PGProperty.values() )
    {
        setProperty( property , getReferenceProperty( ref , property.getName() ) );
    }
}

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

The above requires three properties viz. 'databaseName', 'portNumber' and 'serverName', so these properties also need to be on the resource definition.

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

Sum total, your resource declaration probably should look as follows:

<Resource
            factory="org.postgresql.ds.common.PGObjectFactory"
            name="jdbc/postgres"
            auth="Application"
            type="org.postgresql.ds.PGSimpleDataSource"
            serverName="127.0.0.1"
            portNumber="5432"
            databaseName="mydb"
            />

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

You should then resolve the data source as you have already done and get the connection with getConnection(userName, pwd).

注意:您还可以设置在BaseDataSource中定义的'userName'和'password'属性.

NOTE: You could also set 'userName' and 'password' property, defined in BaseDataSource.

综上所述,我们可以将您的原始示例修改为如下所示.我们使用一些由Postgres JDBC驱动程序定义的 DataSource配置属性.

Putting that all together, we can revise your original example to look like the following. We use some of the DataSource configuration properties defined by the Postgres JDBC driver.

<?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"
                 />

    <!-- `DataSource` object for obtaining database connections to Postgres  -->
    <Resource
                factory="org.postgresql.ds.common.PGObjectFactory"
                type="org.postgresql.ds.PGSimpleDataSource"
                auth="Container"

                driverClassName="org.postgresql.Driver"
                name="jdbc/postgres"

                serverName="127.0.0.1"
                portNumber="5432"
                databaseName="myDb"

                user="myuser"
                password="mypasswd"

                ssl="false"
                />

</Context>

这篇关于指定`DataSource`工厂而不是Tomcat的默认工厂的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆