2015-12-22 41 views
2

我有一個工作代碼,動態加載不同類名的類實現。類文件被加載到內存數據庫的Apache Derby Db的),以及類加載器檢索來自BLOB的.class文件類加載使用同一類的不同版本:java.lang.LinkageError:嘗試重複名稱的定義

我想要做什麼,在插入的.class文件與版本列和IS_ENABLED標誌二進制BLOB,那麼類加載器將加載類上運行時不同的版本。將有數據庫條目相同的編譯類版本的數量和將只有一個類IS_ENABLED標誌設置爲

因爲我嘗試使用自定義類加載器加載相同的類名,所以我得到以下異常;

Exception in thread "main" java.lang.LinkageError: loader (instance of com/levent/classloader/DerbyServerClassLoader): attempted duplicate class definition for name: "com/levent/greeter/Greeter" 
    at java.lang.ClassLoader.defineClass1(Native Method) 
    at java.lang.ClassLoader.defineClass(Unknown Source) 
    at java.lang.ClassLoader.defineClass(Unknown Source) 
    at com.levent.classloader.DerbyServerClassLoader.findClass(DerbyServerClassLoader.java:38) 
    at com.levent.example.ClientClassLoaderDBVersionDemo.main(ClientClassLoaderDBVersionDemo.java:43) 

有兩種不同的.class文件Greeter.class.v1Greeter.class.v2)(插入在代碼的開頭),用於同一個接口(招待員。 java

在測試代碼開始時,從lib/classes /文件夾中檢索類文件,並將其作爲blob二進制數據插入到內存數據庫中,之後,數據庫依次檢索.class文件並加載。在加載具有相同名稱的類時,發生異常。

我該如何解決這個問題?有沒有辦法卸載一個類,否則,無論如何,重新加載一個同名的類?

項目樹

enter image description here

DerbyServerClassLoader.java - 類裝載器

package com.levent.classloader; 

import java.sql.Blob; 
import java.sql.Connection; 
import java.sql.DriverManager; 
import java.sql.PreparedStatement; 
import java.sql.ResultSet; 
import java.sql.SQLException; 

public class DerbyServerClassLoader extends ClassLoader { 

    private ClassLoader parent; 
    private String connectionString; 

    public DerbyServerClassLoader(String connectionString) { 
     this(ClassLoader.getSystemClassLoader(), connectionString); 
    } 

    public DerbyServerClassLoader(ClassLoader parent, String connectionString) { 
     super(parent); 
     this.parent = parent; 
     this.connectionString = connectionString; 
    } 

    @Override 
    public Class<?> findClass(String name) throws ClassNotFoundException { 
     Class cls = null; 

     try { 
      cls = parent.loadClass(name);   // Delegate to the parent Class Loader 
     } catch (ClassNotFoundException clnfE) { // If parent fails, try to locate and load the class 
      byte[] bytes = new byte[0]; 
      try { 
       bytes = loadClassFromDatabase(name); 
      } catch (SQLException sqlE) { 
       throw new ClassNotFoundException("Unable to load class", sqlE); 
      } 
      return defineClass(name, bytes, 0, bytes.length); 
     } 

     return cls; 
    } 

    private byte[] loadClassFromDatabase(String name) throws SQLException { 
     PreparedStatement pstmt = null; 
     Connection connection = null; 

     try { 
      connection = DriverManager.getConnection(connectionString); 

      String sql = "SELECT CLASS FROM CLASSES WHERE CLASS_NAME = ? AND IS_ENABLED = ?"; 
      pstmt = connection.prepareStatement(sql); 
      pstmt.setString(1, name); 
      pstmt.setBoolean(2, true); 
      ResultSet rs = pstmt.executeQuery(); 

      if (rs.next()) { 
       Blob blob = rs.getBlob(1); 
       byte[] data = blob.getBytes(1, (int) blob.length()); 
       return data; 
      } 
     } catch (SQLException e) { 
      System.out.println("Unexpected exception: " + e.toString()); 
     } catch (Exception e) { 
      System.out.println("Unexpected exception: " + e.toString()); 
     } finally { 
      if (pstmt != null) { 
       pstmt.close(); 
      } 

      if(connection != null) { 
       connection.close(); 
      } 
     } 

     return null; 
    } 

} 

Greet.java - 迎賓接口

package com.levent.greeter; 

public interface Greet { 

    public String getGreetMessage(); 

} 

DbSingleton.java - 實用工具類連接DerbyDb

package com.levent.derbyutility; 

import java.sql.Connection; 
import java.sql.DriverManager; 
import java.sql.SQLException; 

public class DbSingleton { 

    private static DbSingleton instance = null; 

    private Connection conn = null; 

    private DbSingleton() { 
     try{ 
      DriverManager.registerDriver(new org.apache.derby.jdbc.EmbeddedDriver()); 
     } catch (SQLException e) { 
      e.printStackTrace(); 
     } 
    } 

    public static DbSingleton getInstance() { 
     if(instance == null) { 
      synchronized(DbSingleton.class) { 
       if(instance == null) { 
        instance = new DbSingleton(); 
       } 
      } 
     } 

     return instance; 
    } 

    public Connection getConnection() throws SQLException { 
     if(conn == null || conn.isClosed()) { 
      synchronized (DbSingleton.class) { 
       if(conn == null || conn.isClosed()) { 
        try{ 
         //String dbUrl = "jdbc:derby://localhost:1527/myDB;create=true;user=me;password=mine"; 
         String dbUrl = "jdbc:derby://localhost:1527/memory:myDB;create=true"; 

         conn = DriverManager.getConnection(dbUrl); 
        } catch (SQLException e) { 
         e.printStackTrace(); 
        } 
       } 
      } 
     } 

     return conn; 
    } 

} 

ClientClassLoaderDBVersionDemo.java - 測試代碼

package com.levent.example; 

import java.io.ByteArrayInputStream; 
import java.io.DataInputStream; 
import java.io.File; 
import java.io.FileInputStream; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.InputStream; 
import java.nio.file.Path; 
import java.nio.file.Paths; 
import java.sql.Connection; 
import java.sql.PreparedStatement; 
import java.sql.SQLException; 
import java.sql.Statement; 

import com.levent.classloader.DerbyServerClassLoader; 
import com.levent.derbyutility.DbSingleton; 
import com.levent.greeter.Greet; 

public class ClientClassLoaderDBVersionDemo { 

    // apache derby in-memory db 
    private static final String connectionString = "jdbc:derby://localhost:1527/memory:myDB;create=true"; 
    private static final String classFileName1 = "Greeter.class.v1"; 
    private static final String classFileName2 = "Greeter.class.v2"; 
    private static final String className = "com.levent.greeter.Greeter"; 

    public static void main(String[] args) { 
     prepareClass(); 

     try { 
      Greet greet = null; 

      DerbyServerClassLoader cl = new DerbyServerClassLoader(connectionString); 

      updateVersion(className, "v1"); 
      Class clazz1 = cl.findClass(className); 
      greet = (Greet) clazz1.newInstance(); 
      System.out.println("Version 1 Greet.getGreetMessage() : " + greet.getGreetMessage()); 

      updateVersion(className, "v2"); 
      Class clazz2 = cl.findClass(className); 
      greet = (Greet) clazz2.newInstance(); 
      System.out.println("Version 2 Greet.getGreetMessage() : " + greet.getGreetMessage());   
     } catch (ClassNotFoundException e) { 
      e.printStackTrace(); 
     } catch (InstantiationException e) { 
      e.printStackTrace(); 
     } catch (IllegalAccessException e) { 
      e.printStackTrace(); 
     } 
    } 

    private static void prepareClass() { 
     DbSingleton instance = DbSingleton.getInstance(); 

     Connection conn = null; 

     try { 
      conn = instance.getConnection(); 
     } catch (SQLException e1) { 
      e1.printStackTrace(); 
     } 

     Statement sta; 

     if (conn != null) { 
      try { 
       sta = conn.createStatement(); 
       int count = sta 
         .executeUpdate("CREATE TABLE CLASSES (CLASS_NAME VARCHAR(50), CLASS BLOB, IS_ENABLED BOOLEAN, VERSION VARCHAR(10))"); 
       System.out.println("CLASSES Table created"); 
       sta.close(); 

       sta = conn.createStatement(); 

       PreparedStatement psta = conn.prepareStatement("INSERT INTO CLASSES (CLASS_NAME, CLASS, IS_ENABLED, VERSION) values (?, ?, ?, ?)"); 
       byte[] bytes = null; 
       InputStream blobObject = null; 

       psta.setString(1, className); 
       bytes = readJarFileAsByteArray(classFileName1); 
       blobObject = new ByteArrayInputStream(bytes); 
       psta.setBlob(2, blobObject, bytes.length); 
       psta.setBoolean(3, false); 
       psta.setString(4, "v1"); 
       count = psta.executeUpdate(); 

       psta.setString(1, className); 
       bytes = readJarFileAsByteArray(classFileName2); 
       blobObject = new ByteArrayInputStream(bytes); 
       psta.setBlob(2, blobObject, bytes.length); 
       psta.setBoolean(3, false); 
       psta.setString(4, "v2"); 
       count += psta.executeUpdate(); 

       System.out.println(count + " record(s) created."); 
       sta.close(); 
      } catch (SQLException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 

    private static byte[] readJarFileAsByteArray(String classFileName) { 
     Path currentRelativePath = Paths.get(""); 
     String s = currentRelativePath.toAbsolutePath().toString(); 

     File file = new File(s + "/lib/classes/" + classFileName); 
     byte[] fileData = new byte[(int) file.length()]; 
     DataInputStream dis; 
     try { 
      dis = new DataInputStream(new FileInputStream(file)); 
      dis.readFully(fileData); 
      dis.close(); 
     } catch (FileNotFoundException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

     return fileData; 
    } 

    private static void updateVersion(String className, String version) { 
     DbSingleton instance = DbSingleton.getInstance(); 

     Connection conn = null; 

     try { 
      conn = instance.getConnection(); 
     } catch (SQLException e1) { 
      e1.printStackTrace(); 
     } 

     Statement sta; 

     if (conn != null) { 
      try { 
       int count = 0; 
       sta = conn.createStatement(); 
       PreparedStatement psta = conn.prepareStatement("UPDATE CLASSES SET IS_ENABLED = ? WHERE CLASS_NAME = ?"); 
       psta.setBoolean(1, false); 
       psta.setString(2, className); 
       count = psta.executeUpdate(); 
       System.out.println(count + " record(s) updated."); 

       psta = conn.prepareStatement("UPDATE CLASSES SET IS_ENABLED = ? WHERE CLASS_NAME = ? AND VERSION = ?"); 

       psta.setBoolean(1, true); 
       psta.setString(2, className); 
       psta.setString(3, version); 

       count = psta.executeUpdate(); 

       System.out.println(count + " record(s) updated."); 
       sta.close(); 
      } catch (SQLException e) { 
       e.printStackTrace(); 
      } 
     } 
    } 

} 

輸出

CLASSES Table created 
2 record(s) created. 
2 record(s) updated. 
1 record(s) updated. 
Version 1 Greet.getGreetMessage() : Hail to the King Baby! 
2 record(s) updated. 
1 record(s) updated. 
Exception in thread "main" java.lang.LinkageError: loader (instance of com/levent/classloader/DerbyServerClassLoader): attempted duplicate class definition for name: "com/levent/greeter/Greeter" 
    at java.lang.ClassLoader.defineClass1(Native Method) 
    at java.lang.ClassLoader.defineClass(Unknown Source) 
    at java.lang.ClassLoader.defineClass(Unknown Source) 
    at com.levent.classloader.DerbyServerClassLoader.findClass(DerbyServerClassLoader.java:38) 
    at com.levent.example.ClientClassLoaderDBVersionDemo.main(ClientClassLoaderDBVersionDemo.java:43) 
+1

你看http://docs.oracle.com/javase/6/docs/api/java/net/URLClassLoader html的#的URLClassLoader%28java.net.URL []%29 –

回答

1

我認爲問題會從您正在使用的父ClassLoader變成。您不會重載loadClass方法,因此您正在委託父類,具體地在ClassLoader.getSystemClassLoader()中。

正如javadoc中說,在https://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#getSystemClassLoader()

該方法首先在運行時的啓動過程的早期調用, 此時它會創建系統類加載器並將其設置爲調用線程的 上下文類加載器。

你想加載改變的類,但你正在委託給Thread ClassLoader的操作,有點混亂。

你可以做這樣的事情,用你自己的類ClassLoader

package a; 

import java.io.IOException; 
import java.net.URL; 
import java.net.URLClassLoader; 

public class ReloadTest { 

    public static void main(String[] args) throws ClassNotFoundException, IOException { 

     final Class<?> clazz = ReloadTest.class; 

     System.out.println("Class: " + clazz.hashCode()); 

     final URL[] urls = new URL[1]; 

     urls[0] = clazz.getProtectionDomain().getCodeSource().getLocation(); 
     final ClassLoader delegateParent = clazz.getClassLoader().getParent(); 

     try (final URLClassLoader cl = new URLClassLoader(urls, delegateParent)) { 

      final Class<?> reloadedClazz = cl.loadClass(clazz.getName()); 
      System.out.println("Class reloaded: " + reloadedClazz.hashCode()); 
      System.out.println("Are the same: " + (clazz != reloadedClazz)); 
     } 
    } 
} 

希望幫助!

PD:此鏈接是關係到同樣的問題,可以幫助它太Reload used classes at runtime Java

2

我怎樣才能解決這個問題?有沒有辦法卸載一個類,否則,無論如何,重新加載一個同名的類?

有沒有辦法迫使一類去卸載。除非舊類被卸載,否則無法將新版本的類加載到同一個類加載器中。(這是因爲一個類的真實身份是包含該類的全限定名稱和類加載器身份的元組)。

解決方案是在新的類加載器中加載該類的新版本。

目前尚不清楚這是否適合您,但不幸的是它是唯一可用的選項。 JVM以不能顛覆它的方式執行「重複類定義」檢查。該檢查具有安全性和JVM穩定性影響。


1 - 不是由任何可到達的對象引用的類將最終由GC(模JVM命令行選項,版本等)卸載。但是,消除對類的所有引用可能會非常棘手。此外,反覆強制GC運行現在對整體性能不利。

相關問題