指定`DataSource`工厂而不是Tomcat的默认工厂 [英] Specify a `DataSource` factory instead of Tomcat's default
问题描述
如何告诉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
用于简单的JDBC连接. -
PGXADataSourceFactory
对于启用XA 的DataSource
实现,用于分布式事务.
PGObjectFactory
For simple JDBC connections.PGXADataSourceFactory
For XA-enabledDataSource
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"
属性,生成的DataSource
是null
.
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屋!