2008-12-18 39 views
2

我正在开发一个适合客户端 - 服务器模型的Eclipse插件。它是一个商业项目,所以我们不能为我们用插件支持的各种数据库重新分配JDBC驱动程序。如何动态替换Eclipse插件的类加载器?

因此,我开发了一个首选项页面,允许用户找到瓶子并具有一个简单的发现机制,它遍历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); 
} 

回答

4

Class.forName()在OSGi是一个重大的痛苦。这不是任何人的错,只是它们都使用类加载器,而这些加载器不能像其他客户端所期望的那样工作(即OSGi类加载器不能像休眠期望的那样工作)。

我觉得你可以去的几个方法之一,但我能想到的,现在的是:

  • 的清洁方式,这是打包的JDBC驱动程序为OSGi包。作为服务贡献课程。你可以用声明式服务(可能更好)来做到这一点,或者写一个你需要管理启动器的激活器。当您准备好获得驱动程序时,获取JDBCDriver服务,并寻找您感兴趣的课程。
  • 不太干净的方式,但会比第一次使用DynamicImport-Package更省力地添加从捆绑的驱动程序导出包。这样,客户端代码仍然可以看到它将使用的类,但直到运行时才需要知道它。然而,你可能不得不尝试包模式,以覆盖所有情况(这就是为什么它不那么干净)。
  • 较少的OSGi方式;即将您的驱动程序添加到eclipse classpath中,并添加应用程序父类加载器。你可以添加:osgi.parentClassloader=app到你的config.ini。这可能不适合您的部署,尤其是如果您没有控制config.ini文件。
  • 非OSGi的方式,而不是使用上下文类加载器,使用URLClassLoader。这只有在你有一个充满驱动程序jar的目录时才可用,或者用户可以直接或间接指定驱动程序jar的位置。
+0

我会为其他项目记住这个建议。不幸的是,这些方法相当于我用我的工具重新包装和分发供应商的驱动程序。我必须避免由于许可问题。 – 2008-12-18 22:22:07