[关闭]
@weixin 2014-12-15T23:38:07.000000Z 字数 10301 阅读 1328

test with classloader

mock


Among the jmockit test cases, there is one for classloader, CustomeClassLoadingTest. I'm not able to fully understand the test code and its purpose, So I did some test in my local.

Here is the screenshot for testing project :
project structure

It is a plain java project, have 2 classes. the bin folder, by default is in project's classpath. Also, I create another 3 folders - load1 to load3.

Here is the code :

  1. import java.io.ByteArrayOutputStream;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.InputStream;
  5. public class MyClassLoader extends ClassLoader {
  6. private String name;
  7. private String path = "";
  8. private final String fileType = ".class";
  9. public MyClassLoader(String name) {
  10. super();
  11. this.name = name;
  12. }
  13. public MyClassLoader(ClassLoader parent, String name) {
  14. super(parent);
  15. this.name = name;
  16. }
  17. @Override
  18. public String toString() {
  19. return "MyClassLoader [name=" + name + "]";
  20. }
  21. public String getName() {
  22. return name;
  23. }
  24. public void setName(String name) {
  25. this.name = name;
  26. }
  27. public String getPath() {
  28. return path;
  29. }
  30. public void setPath(String path) {
  31. this.path = path;
  32. }
  33. public String getFileType() {
  34. return fileType;
  35. }
  36. protected Class<?> findClass(String name) throws ClassNotFoundException {
  37. byte[] data = this.loadClassData(name);
  38. return this.defineClass(name, data, 0, data.length);
  39. }
  40. private byte[] loadClassData(String name) {
  41. InputStream is = null;
  42. byte[] data = null;
  43. ByteArrayOutputStream baos = null;
  44. try {
  45. this.name = this.name.replace(".", "/");
  46. is = new FileInputStream(new File(path + name + fileType));
  47. System.out.println("file : " + path + name + fileType);
  48. baos = new ByteArrayOutputStream();
  49. int ch = 0;
  50. while (-1 != (ch = is.read())) {
  51. baos.write(ch);
  52. }
  53. data = baos.toByteArray();
  54. } catch (Exception e) {
  55. e.printStackTrace();
  56. } finally {
  57. try {
  58. is.close();
  59. baos.close();
  60. } catch (Exception e) {
  61. e.printStackTrace();
  62. }
  63. }
  64. return data;
  65. }
  66. public static void showClassLoader(ClassLoader loader) throws Exception {
  67. Class clazz = loader.loadClass("Sample");
  68. Object object = clazz.newInstance();
  69. System.out.println("object hash : " + object.toString());
  70. // Sample sample = (Sample) object;
  71. // System.out.println(sample.v1);
  72. }
  73. public static void main(String[] args) throws Exception {
  74. MyClassLoader loader1 = new MyClassLoader("loader1");
  75. loader1.setPath("/Users/xwei/Documents/workspace/ClassLoaderTest1/load1/");
  76. MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
  77. loader2.setPath("/Users/xwei/Documents/workspace/ClassLoaderTest1/load2/");
  78. MyClassLoader loader3 = new MyClassLoader(null, "loader3");
  79. loader3.setPath("/Users/xwei/Documents/workspace/ClassLoaderTest1/load3/");
  80. // showClassLoader(loader1);
  81. // showClassLoader(loader2);
  82. showClassLoader(loader3);
  83. showClassLoader(loader3);
  84. //
  85. // Class<Sample> sample = (Class<Sample>) loader1.findClass("Sample");
  86. // Object ob1 = sample.newInstance();
  87. // System.out.println("object hash : " + ob1.toString() +
  88. // ob1.getClass().getClassLoader());
  89. // Sample sa = (Sample) ob1;
  90. // System.out.println(sa.v1);
  91. //
  92. // Class<Sample> sample2 = (Class<Sample>) loader1.findClass("Sample");
  93. // Object ob2 = sample2.newInstance();
  94. // System.out.println("object hash : " + ob2.toString() +
  95. // ob2.getClass().getClassLoader());
  96. }
  97. }

Sample.java :

  1. public class Sample {
  2. public int v1 = 1;
  3. public Sample()
  4. {
  5. System.out.println("Sample is load by :" + this.getClass().getClassLoader());
  6. }
  7. }

be aware that part of code :

  1. protected Class<?> findClass(String name) throws ClassNotFoundException {
  2. byte[] data = this.loadClassData(name);
  3. return this.defineClass(name, data, 0, data.length);
  4. }

if you go to 'ClassLoader.java', you should be able to find the declare of this method :

  1. /**
  2. * Finds the class with the specified <a href="#name">binary name</a>.
  3. * This method should be overridden by class loader implementations that
  4. * follow the delegation model for loading classes, and will be invoked by
  5. * the {@link #loadClass <tt>loadClass</tt>} method after checking the
  6. * parent class loader for the requested class. The default implementation
  7. * throws a <tt>ClassNotFoundException</tt>. </p>
  8. *
  9. * @param name
  10. * The <a href="#name">binary name</a> of the class
  11. *
  12. * @return The resulting <tt>Class</tt> object
  13. *
  14. * @throws ClassNotFoundException
  15. * If the class could not be found
  16. *
  17. * @since 1.2
  18. */
  19. protected Class<?> findClass(String name) throws ClassNotFoundException {
  20. throw new ClassNotFoundException(name);
  21. }

For each customized classloader, you need to implement this method, specify the way how you want to load the class.

Also, there is a 'defineClass' method, I searched some sample code related to this method :

  1. // Create SnappyNativeLoader class from a byte code
  2. Class< ? > classLoader = Class.forName("java.lang.ClassLoader");
  3. Method defineClass = classLoader.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class,
  4. int.class, int.class, ProtectionDomain.class });
  5. ProtectionDomain pd = System.class.getProtectionDomain();
  6. // ClassLoader.defineClass is a protected method, so we have to make it accessible
  7. defineClass.setAccessible(true);
  8. try {
  9. // Create a new class using a ClassLoader#defineClass
  10. defineClass.invoke(rootClassLoader, nativeLoaderClassName, byteCode, 0, byteCode.length, pd);
  11. // And also define dependent classes in the root class loader
  12. for (int i = 0; i < classesToPreload.length; ++i) {
  13. byte[] b = preloadClassByteCode.get(i);
  14. defineClass.invoke(rootClassLoader, classesToPreload[i], b, 0, b.length, pd);
  15. }
  16. }
  17. finally {
  18. // Reset the accessibility to defineClass method
  19. defineClass.setAccessible(false);
  20. }

Let's begin to run the test:

  1. Sample is load by :sun.misc.Launcher$AppClassLoader@7d487b8b
  2. object hash : 697579067

notice the findClass doesn't got triggered at all.

  1. file : /Users/xwei/Documents/workspace/ClassLoaderTest1/load1/Sample.class
  2. Sample is load by :MyClassLoader [name=loader1]
  3. object hash : 1037797730
  1. Class<Sample> sample = (Class<Sample>) loader1.findClass("Sample");
  2. Object ob1 = sample.newInstance();
  3. System.out.println("object hash : " + ob1.toString()
  4. + ob1.getClass().getClassLoader());

I got :

  1. file : /Users/xwei/Documents/workspace/ClassLoaderTest1/load1/Sample.class
  2. Sample is load by :MyClassLoader [name=loader1]
  3. object hash : 404267176MyClassLoader [name=loader1]

there are several things worth to have a look:

so in our case, 'loader1' has parent 'AppLoader'. loader1 delegate its loading task to his parent - AppLoader, then AppLoader searched and found the 'Sample.class' in its classpath. so Apploader loads it. that's the reason we didn't see loader1 's findClass got called.

After the class got removed from the classpath, AppLoader can not find it. then loader1 begin to handle the task by himself.

if we call the findClass directly, then we bypass the delegate logic, we direclty handle the task to loader1.

  1. showClassLoader(loader1);
  2. showClassLoader(loader2);

we got :

  1. Sample is load by :sun.misc.Launcher$AppClassLoader@21a722ef
  2. object hash : 1098150096
  3. Sample is load by :sun.misc.Launcher$AppClassLoader@21a722ef
  4. object hash : 1453944506
  1. file : /Users/xwei/Documents/workspace/ClassLoaderTest1/load1/Sample.class
  2. Sample is load by :MyClassLoader [name=loader1]
  3. object hash : 1037797730
  4. Sample is load by :MyClassLoader [name=loader1]
  5. object hash : 2027651571

loader2 's parent is loader 1, so loader1 would load the class for load2.

from both test 4 and 5, we see that the delegate policy got confirmed.

  1. file : /Users/xwei/Documents/workspace/ClassLoaderTest1/load3/Sample.class
  2. Sample is load by :MyClassLoader [name=loader3]
  3. object hash : 249986066

loader3 doesn't have parent. so it has no parents to delegate. it has to do the dirty task by itself. A poor ophan.

  1. showClassLoader(loader1);
  2. showClassLoader(loader1);

the result :

  1. file : /Users/xwei/Documents/workspace/ClassLoaderTest1/load1/Sample.class
  2. Sample is load by :MyClassLoader [name=loader1]
  3. object hash : 1343958201
  4. Sample is load by :MyClassLoader [name=loader1]
  5. object hash : 672485061

why the 'file...' just got print once, why not twice since we called it twice? here is the source code :

  1. /**
  2. * Loads the class with the specified <a href="#name">binary name</a>. The
  3. * default implementation of this method searches for classes in the
  4. * following order:
  5. *
  6. * <p><ol>
  7. *
  8. * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
  9. * has already been loaded. </p></li>
  10. *
  11. * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
  12. * on the parent class loader. If the parent is <tt>null</tt> the class
  13. * loader built-in to the virtual machine is used, instead. </p></li>
  14. *
  15. * <li><p> Invoke the {@link #findClass(String)} method to find the
  16. * class. </p></li>
  17. *
  18. * </ol>
  19. *
  20. * <p> If the class was found using the above steps, and the
  21. * <tt>resolve</tt> flag is true, this method will then invoke the {@link
  22. * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
  23. *
  24. * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
  25. * #findClass(String)}, rather than this method. </p>
  26. *
  27. * <p> Unless overridden, this method synchronizes on the result of
  28. * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
  29. * during the entire class loading process.
  30. *
  31. * @param name
  32. * The <a href="#name">binary name</a> of the class
  33. *
  34. * @param resolve
  35. * If <tt>true</tt> then resolve the class
  36. *
  37. * @return The resulting <tt>Class</tt> object
  38. *
  39. * @throws ClassNotFoundException
  40. * If the class could not be found
  41. */
  42. protected Class<?> loadClass(String name, boolean resolve)
  43. throws ClassNotFoundException
  44. {
  45. synchronized (getClassLoadingLock(name)) {
  46. // First, check if the class has already been loaded
  47. Class c = findLoadedClass(name);
  48. if (c == null) {
  49. long t0 = System.nanoTime();
  50. try {
  51. if (parent != null) {
  52. c = parent.loadClass(name, false);
  53. } else {
  54. c = findBootstrapClassOrNull(name);
  55. }
  56. } catch (ClassNotFoundException e) {
  57. // ClassNotFoundException thrown if class not found
  58. // from the non-null parent class loader
  59. }
  60. if (c == null) {
  61. // If still not found, then invoke findClass in order
  62. // to find the class.
  63. long t1 = System.nanoTime();
  64. c = findClass(name);
  65. // this is the defining class loader; record the stats
  66. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  67. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  68. sun.misc.PerfCounter.getFindClasses().increment();
  69. }
  70. }
  71. if (resolve) {
  72. resolveClass(c);
  73. }
  74. return c;
  75. }
  76. }

all the answer lie at the code.

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注