【问题标题】:How do I dynamically replace the class loader for an Eclipse plug-in?如何动态替换 Eclipse 插件的类加载器?
【发布时间】:2010-09-27 13:22:03
【问题描述】:

我正在开发一个适合客户端-服务器模型的 Eclipse 插件。它是一个商业项目,因此我们无法为我们通过插件支持的各种数据库重新分发 JDBC 驱动程序。

所以我开发了一个首选项页面,允许用户定位 jars,并有一个简单的发现机制,它遍历 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() 而不是@987654327 打开连接@。它非常基本,但在我的特定用例中我不需要连接池。

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

【问题讨论】:

    标签: java hibernate jdbc eclipse-plugin osgi


    【解决方案1】:

    Class.forName() 在 OSGi 中是一个主要的痛苦。这并不是任何人的错,只是两者都使用了类加载器,而这些类加载器的工作方式与其他客户端的预期不同(即 OSGi 类加载器的工作方式与 hibernate 的预期不同)。

    我认为你可以选择几种方法之一,但我现在能想到的方法是:

    • 干净的方式,即将 JDBC 驱动程序打包为 OSGi 包。将课程作为服务提供。您可以使用声明式服务(可能更好)或编写一个您需要管理启动的激活器来做到这一点。当您准备好获取驱动程序时,获取 JDBCDriver 服务,然后查找您感兴趣的类。
    • 不太干净的方式,但比第一种方式更省力 - 使用DynamicImport-Package 添加从捆绑驱动程序中导出的包。这样,客户端代码仍然可以看到它将使用的类,但直到运行时才知道它。但是,您可能必须尝试使用​​包模式以涵盖所有情况(这就是它不太干净的原因)。
    • 较少的 OSGi 方式;也就是将你的驱动程序添加到 Eclipse 类路径中,并添加应用程序父类加载器。您可以将这个:osgi.parentClassloader=app 添加到您的 config.ini。这可能不适合您的部署,尤其是在您无法控制 config.ini 文件的情况下。
    • 非OSGi方式,不使用上下文类加载器,而是使用URLClassLoader。这仅在您有一个充满驱动程序 jar 的目录时才有效,或者用户可以直接或间接指定驱动程序 jar 的位置。

    【讨论】:

    • 我会在其他项目中记住这个建议。不幸的是,这些方法相当于我用我的工具重新打包和分发供应商的驱动程序。由于许可问题,我必须避免这种情况。
    猜你喜欢
    • 2017-08-03
    • 1970-01-01
    • 2017-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多