我正在开发一个适合客户端-服务器模型的 Eclipse 插件。这是一个商业项目,因此我们无法为我们通过插件支持的各种数据库重新分发 JDBC 驱动程序。
因此,我开发了一个首选项页面,允许用户定位 jar,并具有一个简单的发现机制,该机制迭代 jar 文件中的类,加载每个类以验证它是否实现了 java.sql.Driver 接口(interface)。这一切都很好。
但问题是我正在使用 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);
}
最佳答案
OSGi 中的
Class.forName()
是一个很大的痛苦。这并不是任何人的错,只是两者都使用的类加载器不按照对方客户端期望的方式工作(即 OSGi 类加载器不按照 hibernate 期望的方式工作)。
我认为你可以采用几种方法之一,但我现在能想到的是:
- 干净的方法,即将 JDBC 驱动程序打包为 OSGi bundle 。将类(class)贡献为服务。您可以使用声明性服务(可能更好)来做到这一点,或者编写一个您需要管理启动的激活器。当您准备好获取驱动程序时,获取 JDBCDriver 服务,并查找您感兴趣的类。
- 不太干净的方法,但比第一种方法省力 - 使用
DynamicImport-Package
添加从捆绑驱动程序导出的包。这样,客户端代码仍然可以看到它将使用的类,但直到运行时才需要知道它。但是,您可能需要尝试一下包模式,以涵盖所有情况(这就是它不太干净的原因)。 - 较少的 OSGi 方式;即将您的驱动程序添加到 eclipse 类路径,并添加应用程序父类加载器。您可以将以下内容添加到您的 config.ini 中:
osgi.parentClassloader=app
。这可能不适合您的部署,特别是如果您无法控制config.ini
文件。 - 非 OSGi 方式,不使用上下文类加载器,而是使用
URLClassLoader
。仅当您有一个充满驱动程序 jar 的目录,或者用户可以直接或间接指定驱动程序 jar 的位置时,这才有效。
关于java - 如何动态替换 Eclipse 插件的类加载器?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/378297/