Укажите фабрику DataSource вместо Tomcat по умолчанию - PullRequest
0 голосов
/ 15 октября 2019

tl; dr

Как мне указать Tomcat 9 использовать Postgres-специфическую фабрику объектов для производства DataSource объектав ответ на JNDI запрос?

Подробности

Я могу легко получить DataSource объект от Apache Tomcat 9определив файл XML, названный так же, как мой контекст. Например, для веб-приложения с именем 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>

Я помещаю этот файл в свою «базовую» папку Tomcat, в папку conf, в папки, созданные с помощьюимя двигателя Catalina и имя хоста localhost. Tomcat передает настройки в фабрику ресурсов, чтобы вернуть экземпляр DataSource. Я могу получить доступ к этому экземпляру через JNDI:

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

Я понимаю, что postgres в этой строке поиска может быть чем-то более специфичным для конкретного приложения. Но давайте пойдем с postgres для той же демонстрации.

Я хочу org.postgresql.ds.PGSimpleDataSource, а не org.apache.tomcat.dbcp.dbcp2.BasicDataSource

Эта установка использует собственную фабрику ресурсов Tomcat для объектов JDBC DataSource. Базовый класс возвращенного класса DataSource равен org.apache.tomcat.dbcp.dbcp2.BasicDataSource. К сожалению, я не хочу DataSource этого класса. Мне нужен DataSource класса, предоставляемого драйвером JDBC от Глобальной группы разработки PostgreSQL : org.postgresql.ds.PGSimpleDataSource.

Прочитав страницы документации Tomcat, How-To ресурсов JNDI и How-To источника данных JNDI , Iпришел к выводу, что Tomcat позволяет использовать альтернативную фабрику для этих DataSource объектов вместо стандартной реализации фабрики, связанной с Tomcat. Звучит так, как мне нужно.

PGObjectFactory

Я обнаружил, что драйвер Postgres JDBC уже поставляется в комплекте с такими реализациями:

  • PGObjectFactoryДля простых соединений JDBC.
  • PGXADataSourceFactoryДля реализации XA с поддержкой DataSource, для распределенных транзакций.

Кстати, в драйвер для приложений OSGi встроена аналогичная фабрика, PGDataSourceFactory. Я предполагаю, что это бесполезно для меня с Tomcat.

Итак, класс PGObjectFactory реализует интерфейс javax.naming.spi.ObjectFactory, требуемый JNDI.

SPI

Я предполагаю, что spi в этом имени пакета означает загрузку фабрик объектов через Интерфейс поставщика услуг Java (SPI) .

screenshot of location of SPI mapping file, and its contents

Поэтому я предполагаю, что нужен файл сопоставления SPI, как обсуждалось в Oracle Tutorial и в Vaadinдокументация . добавили папку META-INF в мою папку Vaadin resources и создали папку services, которая была дополнительно вложена в нее. Итак, в /resources/META-INF/services я создал файл с именем javax.naming.spi.ObjectFactory, содержащий одну строку текста, имя моей желаемой фабрики объектов: org.postgresql.ds.common.PGObjectFactory. Я даже проверил внутри драйвера Postgres JDBC, чтобы физически проверить существование и полное имя этого класса.

Вопрос

➥ Мой вопрос: как мне сказать Tomcat:использовать PGObjectFactory вместо фабрики объектов по умолчанию для создания моих DataSource объектов для создания соединений с моей базой данных Postgres?

factory атрибут <Resource> элемент

Iнадеялся, что это будет так же просто, как добавление атрибута factory (factory="org.postgresql.ds.common.PGObjectFactory") к моему элементу <Resource>, показанному выше. Я получил эту идею со страницы Tomcat, Контейнер контекста . Эта страница довольно запутанная, поскольку она фокусируется на глобальном ресурсе, но мне не нужно или не нужно определять это DataSource глобально. Мне нужно это DataSource только для одного моего веб-приложения.

Добавление этого атрибута 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"
                username="myuser"
                password="mypasswd"
                factory="org.postgresql.ds.common.PGObjectFactory"
                />
</Context>

... завершается с ошибкой, когда мой DataSource объект является нулевым.

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

null

Удаление этого атрибута factory="org.postgresql.ds.common.PGObjectFactory" разрешает исключение. Но потом я снова получаю Tomcat BasicDataSource, а не Postgres PGSimpleDataSource. Таким образом, мой вопрос здесь.

Я знаюТеперь мой Context XML успешно загружается, потому что я могу получить доступ к значению этой записи Environment.

2-й эксперимент

Я попробовал это сверху, несколько дней спустя.

Я создал новый проект Vaadin 14.0.9 "Простой Java-сервлет" под названием "datasource-object-factory".

Вот весь мой код веб-приложения Vaadin. Нижняя часть - это поиск JNDI.

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, вместо этого вручную перемещая файл WAR моего веб-приложения в папку webapps.

Я скачал новую версию Tomcat, версия 9.0.27. Я перетащил JAR-файл Postgres в папку /lib. Я использовал приложение BatChmod, чтобы установить разрешения для папки Tomcat.

В папке conf я создал папки 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 моего веб-приложения в webapps в Tomcat. Наконец, я запускаю Tomcat /bin/startup.sh и наблюдаю, как файл WAR взрывается в папке.

С атрибутом factory="org.postgresql.ds.common.PGObjectFactory" в моем элементе Resource получается DataSource null.

Как и в моем первом эксперименте, я могу получить доступ к значению <Environment>, поэтому я знаю, что мой XML-файл контекста-имени найден и успешно обрабатывается через JNDI.

Вотрегистрируется на Google Диске:

1 Ответ

1 голос
/ 19 октября 2019

Ваша конфигурация ресурса нуждается в модификации. Как упомянуто в Документация Tomcat ,

Вы можете объявить характеристики ресурса, который будет возвращен для поиска JNDI и элементов в дескрипторе развертывания веб-приложения. Вы ДОЛЖНЫ также определить необходимые параметры ресурса как атрибуты элемента Resource, чтобы сконфигурировать фабрику объектов , которая будет использоваться (если она еще не известна Tomcat), и свойства, используемые для настройкифабрика объектов.

Причина, по которой вы получаете нулевое значение, заключается в том, что фабрика объектов не может определить тип объекта, который необходимо создать, см. 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'.

Однако это все равно не даст вам действительный источник данных, поскольку в том же исходном коде см. Следующие разделы:

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 для суперкласса всех источников данных, см. BaseDataSource, раздел:

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', поэтому эти свойства также должны быть указаны в определении ресурса.

Итого, ваше объявление ресурса, вероятно, должно выглядеть следующим образом:

<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).

ПРИМЕЧАНИЕ. Вы также можете установить свойства 'userName' и 'password', определенные в BaseDataSource.

Собрав все это вместе, мы можем пересмотреть ваш оригинальный пример, чтобы он выглядел следующим образом. Мы используем некоторые из свойств конфигурации DataSource, определенных драйвером JDBC Postgres .

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

                username="myuser"
                password="mypasswd"

                ssl="false"
                />

</Context>
...