2008-12-18 45 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