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

时间:2022-02-19 11:11:34

I am developing an Eclipse plug-in that fits a client-server model. Its a commercial project so we cannot re-distribute the JDBC drivers for the various databases we support with the plug-in.

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

So I developed a preference page to allow the user locate the jars and have a simple discovery mechanism that iterates through the classes in the jar files, loading each one to verify that it implements the java.sql.Driver interface. This all works great.

所以我开发了一个首选项页面,允许用户找到jar并有一个简单的发现机制,它遍历jar文件中的类,加载每个文件以验证它是否实现了java.sql.Driver接口。一切都很好。

But the catch is that I am using Hibernate. And Hibernate uses Class.forName() to instantiate the JDBC driver.

但问题是我正在使用Hibernate。 Hibernate使用Class.forName()来实例化JDBC驱动程序。

If I try to use the following I get ClassNotFoundException.

如果我尝试使用以下内容,我会得到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;
}

And if I try creating the driver myself as follows I get a SecurityException.

如果我尝试自己创建驱动程序如下,我得到一个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;
}

EDIT: I am not sure it is the best option but I took the approach of implementing my own ConnectionProvider which allowed me instantiate the driver using Class.forName() and I then open the connection using Driver.connect() instead of DriverManager.getConnection(). Its pretty basic but I don't need connection pooling in my specific use case.

编辑:我不确定它是最好的选择但是我采用了实现我自己的ConnectionProvider的方法,它允许我使用Class.forName()实例化驱动程序然后我使用Driver.connect()而不是DriverManager.getConnection打开连接()。它非常基本但我不需要在我的特定用例中使用连接池。

The configure() method was as follows:

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

And the getConnection() method is as follows:

getConnection()方法如下:

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

1 个解决方案

#1


Class.forName() in OSGi is a major pain. This is not really anyone's fault, just that both use class loaders which do not work the way the other's client is expecting (i.e. OSGi class loader doesn't work in the way that hibernate is expecting).

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

I think you can go one of a few ways, but the ones I can think of right now are:

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

  • the clean way, which is to package the JDBC drivers as OSGi bundles. Contribute the class as a service. You can do this with declarative services (probably better) or write an activator which you'll need to manage starting. When you're ready to get the driver, get the JDBCDriver service, and look for the class that you're interested in.
  • 干净的方法,即将JDBC驱动程序打包为OSGi包。将该类作为服务进行贡献。您可以使用声明性服务(可能更好)来执行此操作,或者编写一个您需要管理启动的激活器。当您准备好获取驱动程序时,获取JDBCDriver服务,并查找您感兴趣的类。

  • the less clean way, but will work for less effort than the first - use DynamicImport-Package to add the exported packages from your bundled drivers. This way, the client code can still see the class that it'll use, but it doesn't have to know about it until runtime. You may have to experiment with the package pattern, however, to cover all cases (that's why it's less clean).
  • 不太干净的方式,但比第一次使用更少的工作 - 使用DynamicImport-Package从捆绑的驱动程序添加导出的包。这样,客户端代码仍然可以看到它将使用的类,但它不需要知道它直到运行时。但是,您可能需要尝试使用包装模式来涵盖所有情况(这就是为什么它不太干净)。

  • the less OSGi way; which is to add your drivers to the eclipse classpath, and add the application parent classloader. You can add this: osgi.parentClassloader=app to your config.ini. This may not fit in with your deployment, especially if you haven't got control of the config.ini file.
  • OSGi方式越少;这是将您的驱动程序添加到eclipse类路径,并添加应用程序父类加载器。你可以将这个:osgi.parentClassloader = app添加到你的config.ini中。这可能不适合您的部署,尤其是在您无法控制config.ini文件的情况下。

  • the non-OSGi way, instead of using the context class loader, use the URLClassLoader. This will only work if you have a directory full of driver jars, or the user can directly or indirectly specify the location of the driver jar.
  • 非OSGi方式,而不是使用上下文类加载器,使用URLClassLoader。这仅在您拥有一个充满驱动程序jar的目录时才有效,或者用户可以直接或间接指定驱动程序jar的位置。

#1


Class.forName() in OSGi is a major pain. This is not really anyone's fault, just that both use class loaders which do not work the way the other's client is expecting (i.e. OSGi class loader doesn't work in the way that hibernate is expecting).

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

I think you can go one of a few ways, but the ones I can think of right now are:

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

  • the clean way, which is to package the JDBC drivers as OSGi bundles. Contribute the class as a service. You can do this with declarative services (probably better) or write an activator which you'll need to manage starting. When you're ready to get the driver, get the JDBCDriver service, and look for the class that you're interested in.
  • 干净的方法,即将JDBC驱动程序打包为OSGi包。将该类作为服务进行贡献。您可以使用声明性服务(可能更好)来执行此操作,或者编写一个您需要管理启动的激活器。当您准备好获取驱动程序时,获取JDBCDriver服务,并查找您感兴趣的类。

  • the less clean way, but will work for less effort than the first - use DynamicImport-Package to add the exported packages from your bundled drivers. This way, the client code can still see the class that it'll use, but it doesn't have to know about it until runtime. You may have to experiment with the package pattern, however, to cover all cases (that's why it's less clean).
  • 不太干净的方式,但比第一次使用更少的工作 - 使用DynamicImport-Package从捆绑的驱动程序添加导出的包。这样,客户端代码仍然可以看到它将使用的类,但它不需要知道它直到运行时。但是,您可能需要尝试使用包装模式来涵盖所有情况(这就是为什么它不太干净)。

  • the less OSGi way; which is to add your drivers to the eclipse classpath, and add the application parent classloader. You can add this: osgi.parentClassloader=app to your config.ini. This may not fit in with your deployment, especially if you haven't got control of the config.ini file.
  • OSGi方式越少;这是将您的驱动程序添加到eclipse类路径,并添加应用程序父类加载器。你可以将这个:osgi.parentClassloader = app添加到你的config.ini中。这可能不适合您的部署,尤其是在您无法控制config.ini文件的情况下。

  • the non-OSGi way, instead of using the context class loader, use the URLClassLoader. This will only work if you have a directory full of driver jars, or the user can directly or indirectly specify the location of the driver jar.
  • 非OSGi方式,而不是使用上下文类加载器,使用URLClassLoader。这仅在您拥有一个充满驱动程序jar的目录时才有效,或者用户可以直接或间接指定驱动程序jar的位置。