Как динамически заменить загрузчик классов для подключаемого модуля Eclipse? - PullRequest
2 голосов
/ 18 декабря 2008

Я разрабатываю плагин Eclipse, который подходит для модели клиент-сервер. Это коммерческий проект, поэтому мы не можем распространять драйверы JDBC для различных баз данных, которые мы поддерживаем с помощью плагина.

Итак, я разработал страницу настроек, чтобы позволить пользователю находить jar-файлы и иметь простой механизм обнаружения, который перебирает классы в jar-файлах, загружая каждый из них, чтобы убедиться, что он реализует интерфейс java.sql.Driver. Все это прекрасно работает.

Но суть в том, что я использую Hibernate. А Hibernate использует Class.forName () для создания экземпляра драйвера JDBC.

Если я пытаюсь использовать следующее, я получаю ClassNotFoundException.

public Object execute(final IRepositoryCallback callback)
{
    final DatabaseDriverClassLoader loader = new DatabaseDriverClassLoader(
        Activator.getDefault().getDatabaseDriverRegistry());
    final ClassLoader oldLoader = Thread.currentThread()
        .getContextClassLoader();
    try
    {
        Thread.currentThread().setContextClassLoader(loader);
        try
        {
            final SessionFactory sessionFactory = this.configuration
                .buildSessionFactory();
            if (sessionFactory != null)
            {
                final Session session = sessionFactory
                    .openSession();
                if (session != null)
                {
                    // CHECKSTYLE:OFF
                    try
                    // CHECKSTYLE:ON
                    {
                        return callback.doExecute(session);
                    }
                    finally
                    {
                        session.close();
                    }
                }
            }
            connection.close();
        }
        finally
        {
        }
    }
    // CHECKSTYLE:OFF
    catch (Exception e)
    // CHECKSTYLE:ON
    {
        RepositoryTemplate.LOG.error(e.getMessage(), e);
    }
    finally
    {
        Thread.currentThread().setContextClassLoader(oldLoader);
    }
    return null;
}

И если я сам пытаюсь создать драйвер следующим образом, я получаю исключение SecurityException.

public Object execute(final IRepositoryCallback callback)
{
    final DatabaseDriverClassLoader loader = new DatabaseDriverClassLoader(
        Activator.getDefault().getDatabaseDriverRegistry());
    final ClassLoader oldLoader = Thread.currentThread()
        .getContextClassLoader();
    try
    {
        Thread.currentThread().setContextClassLoader(loader);
        final Class driverClass = loader.loadClass(this.connectionDriverClassName);
        final Driver driver = (Driver)driverClass.newInstance();
        DriverManager.registerDriver(driver);
        try
        {
            final Connection connection = DriverManager.getConnection(
                this.connectionUrl, this.connectionUsername,
                this.connectionPassword);
            final SessionFactory sessionFactory = this.configuration
                .buildSessionFactory();
            if (sessionFactory != null)
            {
                final Session session = sessionFactory
                    .openSession(connection);
                if (session != null)
                {
                    // CHECKSTYLE:OFF
                    try
                    // CHECKSTYLE:ON
                    {
                        return callback.doExecute(session);
                    }
                    finally
                    {
                        session.close();
                    }
                }
            }
            connection.close();
        }
        finally
        {
            DriverManager.deregisterDriver(driver);
        }
    }
    // CHECKSTYLE:OFF
    catch (Exception e)
    // CHECKSTYLE:ON
    {
        RepositoryTemplate.LOG.error(e.getMessage(), e);
    }
    finally
    {
        Thread.currentThread().setContextClassLoader(oldLoader);
    }
    return null;
}

РЕДАКТИРОВАТЬ: я не уверен, что это лучший вариант, но я выбрал подход к реализации собственного ConnectionProvider, который позволил мне создать экземпляр драйвера, используя Class.forName(), и затем я открываю соединение, используя Driver.connect() вместо DriverManager.getConnection(). Это довольно простой, но мне не нужен пул соединений в моем конкретном случае использования.

Метод configure() был следующим:

public void configure(final Properties props)
{
    this.url = props.getProperty(Environment.URL);
    this.connectionProperties = ConnectionProviderFactory
        .getConnectionProperties(props);

    final DatabaseDriverClassLoader classLoader = new DatabaseDriverClassLoader(
        Activator.getDefault().getDatabaseDriverRegistry());

    final String driverClassName = props.getProperty(Environment.DRIVER);
    try
    {
        final Class driverClass = Class.forName(driverClassName, true,
            classLoader);
        this.driver = (Driver)driverClass.newInstance();
    }
    catch (ClassNotFoundException e)
    {
        throw new HibernateException(e);
    }
    catch (IllegalAccessException e)
    {
        throw new HibernateException(e);
    }
    catch (InstantiationException e)
    {
        throw new HibernateException(e);
    }
}

И метод getConnection() выглядит следующим образом:

public Connection getConnection()
    throws SQLException
{
    return this.driver.connect(this.url, this.connectionProperties);
}

1 Ответ

4 голосов
/ 18 декабря 2008

Class.forName() в OSGi это большая боль. На самом деле это не чья-то вина, просто оба используют загрузчики классов, которые не работают так, как ожидает клиент другого (т.е. загрузчик классов OSGi не работает так, как ожидает hibernate).

Я думаю, вы можете пойти одним из нескольких способов, но те, о которых я могу думать прямо сейчас:

  • чистый способ, который заключается в упаковке драйверов JDBC в виде пакетов OSGi. Внести класс как услугу. Вы можете сделать это с помощью декларативных сервисов (возможно, лучше) или написать активатор, который вам понадобится для управления запуском. Когда вы будете готовы получить драйвер, получите сервис JDBCDriver и найдите интересующий вас класс.
  • менее чистый способ, но будет работать с меньшими усилиями, чем первый - используйте DynamicImport-Package, чтобы добавить экспортированные пакеты из ваших драйверов. Таким образом, клиентский код все еще может видеть класс, который он будет использовать, но он не должен знать об этом до времени выполнения. Возможно, вам придется поэкспериментировать с шаблоном упаковки, чтобы охватить все случаи (поэтому он менее чистый).
  • менее OSGi способ; который должен добавить ваши драйверы в classpath eclipse и добавить родительский загрузчик классов приложения. Вы можете добавить это: osgi.parentClassloader=app в ваш config.ini. Это может не соответствовать вашему развертыванию, особенно если у вас нет контроля над файлом config.ini.
  • не-OSGi, вместо использования загрузчика класса контекста, используйте URLClassLoader. Это будет работать только в том случае, если у вас есть каталог, полный файлов jar драйверов, или пользователь может прямо или косвенно указать местоположение файла jar драйвера.
...