2016-04-06 7 views
2

Ich habe Zweifel mit Klasse ‚s identifizieren, Klasse‘ s sollte The same classloader instance + The same class full path .Aber ich einige Testfälle machen sein identifizieren, funktioniert es nicht.Java-Klasse identifizieren Classloader

Ich habe ein selbstdefinierte Classloader:

import java.io.*; 

public class FileSystemClassLoader extends ClassLoader { 

    private String rootDir; 

    public FileSystemClassLoader(String rootDir) { 
     this.rootDir = rootDir; 
    } 

    protected Class<?> findClass(String name) throws ClassNotFoundException { 
     byte[] classData = getClassData(name); 
     if (classData == null) { 
      throw new ClassNotFoundException(); 
     } else { 
      return defineClass(name, classData, 0, classData.length); 
     } 
    } 

    private byte[] getClassData(String className) { 
     String path = classNameToPath(className); 
     try { 
      InputStream ins = new FileInputStream(path); 
      ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
      int bufferSize = 4096; 
      byte[] buffer = new byte[bufferSize]; 
      int bytesNumRead = 0; 
      while ((bytesNumRead = ins.read(buffer)) != -1) { 
       baos.write(buffer, 0, bytesNumRead); 
      } 
      return baos.toByteArray(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
     return null; 
    } 

    private String classNameToPath(String className) { 
     return rootDir + File.separatorChar 
       + className.replace('.', File.separatorChar) + ".class"; 
    } 
} 

Die Sample Klasse:

public class Sample { 

    private Sample instance; 

    public void setSample(Object instance) { 
     this.instance = (Sample) instance; 
    } 
} 

Und ein Testfall:

String classDataRootPath = "/Users/haolin/Github/jvm/target/classes"; 
FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath); 
FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); 
String className = "me.hao0.jvm.classloader.Sample"; 
try { 
    Class<?> class1 = fscl1.loadClass(className); 
    Object obj1 = class1.newInstance(); 
    Class<?> class2 = fscl2.loadClass(className); 
    Object obj2 = class2.newInstance(); 
    Method setSampleMethod = class1.getMethod("setSample", Object.class); 
    setSampleMethod.invoke(obj1, obj2); 
} catch (Exception e) { 
    e.printStackTrace(); 
} 

Es sollte ClassCastException auftreten, wenn setSampleMethod.invoke(obj1, obj2), weil obj1 und obj2 sind verschiedene Klasse (ihre Classloader anders ist), aber der Code funktioniert gut, wirft keine ClassCastException

Kann jemand das vorschlagen?

+0

Sie sind wiederum die gleiche Klasse, obwohl richtig? Ich sehe keinen Unterschied zwischen den beiden. – 3kings

+0

Ja, auch ** obj1 ** und ** obj2 ** sind die gleiche vollständige Pfadklasse 'Sample', aber die Klassenladeprogramme laden ihre Klasse' Sample' sind unterschiedlich, 'obj1' ist' fscl1', 'obj2' ist' fscl2', also sollte 'jvm' ihre Klasse anders sehen, tat es aber nicht. – haolin

+0

Was ist 'class1 == class2' in Ihrem Test? –

Antwort

1

Sie wären mit Ausnahme eines wichtigen Details korrekt. Klassenloader existieren in einer Hierarchie, und alle ClassLoader delegiert an seinen übergeordneten, bevor Sie versuchen, eine Klasse selbst zu laden. Wenn also ein gemeinsamer Vorgänger Ihrer Classloader die angeforderte Klasse findet, geben Ihre Klassenlader die gleiche Instanz dieser Klasse zurück. Betrachten Sie folgendes Beispiel:

public class Foo { 
    public void isFoo(Object obj) { 
     System.out.println("object is a Foo: " + (obj instanceof Foo)); 
     Foo foo = (Foo) obj; 
    } 
} 

und den Test:

import java.lang.reflect.Method; 
import java.net.URL; 
import java.net.URLClassLoader; 

public class Test { 
    public static void main(String[] args) throws Exception { 
     File path = new File("."); 
     URL[] urls = new URL[] { path.toURI().toURL() }; 
     URLClassLoader cl1 = new URLClassLoader(urls); 
     URLClassLoader cl2 = new URLClassLoader(urls); 

     Class c1 = cl1.loadClass("Foo"); 
     Class c2 = cl2.loadClass("Foo"); 
     System.out.println("same class instance: " + (c1 == c2)); 

     Object o1 = c1.newInstance(); 
     Object o2 = c2.newInstance(); 

     Method m = c1.getDeclaredMethod("isFoo", Object.class); 
     m.invoke(o1, o2); 
    } 
} 

Die Ausgabe lautet:

same class instance: true 
object is a Foo: true 

Dies geschieht, weil das aktuelle Verzeichnis Teil des Standard-Classpath ist, so dass die Eltern Classloader Findet und lädt Foo, und beide benutzerdefinierte Klassenladeprogramme geben die Instanz von ihrem übergeordneten Objekt zurück.

Nun machen diese Änderung an der Testklasse und neu kompilieren:

File path = new File("foo"); 

Erstellen Sie das Verzeichnis foo/ und Foo.class dort bewegen. Jetzt ist die Ausgabe:

same class instance: false 
object is a Foo: false 
Exception in thread "main" java.lang.reflect.InvocationTargetException 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
    at java.lang.reflect.Method.invoke(Method.java:498) 
    at Test.main(Test.java:21) 
Caused by: java.lang.ClassCastException: Foo cannot be cast to Foo 
    at Foo.isFoo(Foo.java:4) 
    ... 5 more 

Das ist, was Sie erwartet haben. Der Systemklassenlader kann Foo nicht mehr finden, sodass die benutzerdefinierten Klassenladeprogramme separate Instanzen davon laden. Die JVM sieht die beiden Instanzen Class als unterschiedliche Klassen, obwohl sie identisch sind und die Umwandlung fehlschlägt.

+0

Vielen Dank für die Antwort, es ist meine Nachlässigkeit, ich habe vergessen, die Class-Class-Datei zu löschen. – haolin