如何动态替换Eclipse插件的类加载器?

时间:2008-12-18 15:59:23

标签: java hibernate jdbc eclipse-plugin osgi

我正在开发一个适合客户端 - 服务器模型的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()实例化驱动程序,然后使用{{1}打开连接而不是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()

1 个答案:

答案 0 :(得分:4)

OSGi中的

Class.forName()是一个很大的痛苦。这实际上不是任何人的错,只是两个都使用类加载器,它们不像另一个客户期望的那样工作(即OSGi类加载器不能像hibernate期望的那样工作)。

我认为你可以选择其中一种方式,但我现在能想到的是:

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