@weixin
2014-12-15T23:38:07.000000Z
字数 10301
阅读 1328
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 :
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 :
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class MyClassLoader extends ClassLoader {
private String name;
private String path = "";
private final String fileType = ".class";
public MyClassLoader(String name) {
super();
this.name = name;
}
public MyClassLoader(ClassLoader parent, String name) {
super(parent);
this.name = name;
}
@Override
public String toString() {
return "MyClassLoader [name=" + name + "]";
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getFileType() {
return fileType;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = this.loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
private byte[] loadClassData(String name) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try {
this.name = this.name.replace(".", "/");
is = new FileInputStream(new File(path + name + fileType));
System.out.println("file : " + path + name + fileType);
baos = new ByteArrayOutputStream();
int ch = 0;
while (-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
is.close();
baos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return data;
}
public static void showClassLoader(ClassLoader loader) throws Exception {
Class clazz = loader.loadClass("Sample");
Object object = clazz.newInstance();
System.out.println("object hash : " + object.toString());
// Sample sample = (Sample) object;
// System.out.println(sample.v1);
}
public static void main(String[] args) throws Exception {
MyClassLoader loader1 = new MyClassLoader("loader1");
loader1.setPath("/Users/xwei/Documents/workspace/ClassLoaderTest1/load1/");
MyClassLoader loader2 = new MyClassLoader(loader1, "loader2");
loader2.setPath("/Users/xwei/Documents/workspace/ClassLoaderTest1/load2/");
MyClassLoader loader3 = new MyClassLoader(null, "loader3");
loader3.setPath("/Users/xwei/Documents/workspace/ClassLoaderTest1/load3/");
// showClassLoader(loader1);
// showClassLoader(loader2);
showClassLoader(loader3);
showClassLoader(loader3);
//
// Class<Sample> sample = (Class<Sample>) loader1.findClass("Sample");
// Object ob1 = sample.newInstance();
// System.out.println("object hash : " + ob1.toString() +
// ob1.getClass().getClassLoader());
// Sample sa = (Sample) ob1;
// System.out.println(sa.v1);
//
// Class<Sample> sample2 = (Class<Sample>) loader1.findClass("Sample");
// Object ob2 = sample2.newInstance();
// System.out.println("object hash : " + ob2.toString() +
// ob2.getClass().getClassLoader());
}
}
Sample.java :
public class Sample {
public int v1 = 1;
public Sample()
{
System.out.println("Sample is load by :" + this.getClass().getClassLoader());
}
}
be aware that part of code :
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] data = this.loadClassData(name);
return this.defineClass(name, data, 0, data.length);
}
if you go to 'ClassLoader.java', you should be able to find the declare of this method :
/**
* Finds the class with the specified <a href="#name">binary name</a>.
* This method should be overridden by class loader implementations that
* follow the delegation model for loading classes, and will be invoked by
* the {@link #loadClass <tt>loadClass</tt>} method after checking the
* parent class loader for the requested class. The default implementation
* throws a <tt>ClassNotFoundException</tt>. </p>
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*
* @since 1.2
*/
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
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 :
// Create SnappyNativeLoader class from a byte code
Class< ? > classLoader = Class.forName("java.lang.ClassLoader");
Method defineClass = classLoader.getDeclaredMethod("defineClass", new Class[] { String.class, byte[].class,
int.class, int.class, ProtectionDomain.class });
ProtectionDomain pd = System.class.getProtectionDomain();
// ClassLoader.defineClass is a protected method, so we have to make it accessible
defineClass.setAccessible(true);
try {
// Create a new class using a ClassLoader#defineClass
defineClass.invoke(rootClassLoader, nativeLoaderClassName, byteCode, 0, byteCode.length, pd);
// And also define dependent classes in the root class loader
for (int i = 0; i < classesToPreload.length; ++i) {
byte[] b = preloadClassByteCode.get(i);
defineClass.invoke(rootClassLoader, classesToPreload[i], b, 0, b.length, pd);
}
}
finally {
// Reset the accessibility to defineClass method
defineClass.setAccessible(false);
}
Let's begin to run the test:
showClassLoader(loader1);
Sample is load by :sun.misc.Launcher$AppClassLoader@7d487b8b
object hash : 697579067
notice the findClass
doesn't got triggered at all.
bin
folder.
file : /Users/xwei/Documents/workspace/ClassLoaderTest1/load1/Sample.class
Sample is load by :MyClassLoader [name=loader1]
object hash : 1037797730
loadClass
,
Class<Sample> sample = (Class<Sample>) loader1.findClass("Sample");
Object ob1 = sample.newInstance();
System.out.println("object hash : " + ob1.toString()
+ ob1.getClass().getClassLoader());
I got :
file : /Users/xwei/Documents/workspace/ClassLoaderTest1/load1/Sample.class
Sample is load by :MyClassLoader [name=loader1]
object hash : 404267176MyClassLoader [name=loader1]
there are several things worth to have a look:
why if there is a 'Sample.class' stay at classpath, then it would use AppClassLoader to load it, instead of using 'Loader1' to loader the class in 'loader1' folder? That's related to java classloader's delegate policy. have a look at the pic below :
java.*
, its search path is jdk/jre/lib/*.jar
.NoClassFoundException
. 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.
showClassLoader(loader1);
showClassLoader(loader2);
we got :
Sample is load by :sun.misc.Launcher$AppClassLoader@21a722ef
object hash : 1098150096
Sample is load by :sun.misc.Launcher$AppClassLoader@21a722ef
object hash : 1453944506
file : /Users/xwei/Documents/workspace/ClassLoaderTest1/load1/Sample.class
Sample is load by :MyClassLoader [name=loader1]
object hash : 1037797730
Sample is load by :MyClassLoader [name=loader1]
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.
file : /Users/xwei/Documents/workspace/ClassLoaderTest1/load3/Sample.class
Sample is load by :MyClassLoader [name=loader3]
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.
showClassLoader(loader1);
showClassLoader(loader1);
the result :
file : /Users/xwei/Documents/workspace/ClassLoaderTest1/load1/Sample.class
Sample is load by :MyClassLoader [name=loader1]
object hash : 1343958201
Sample is load by :MyClassLoader [name=loader1]
object hash : 672485061
why the 'file...' just got print once, why not twice since we called it twice? here is the source code :
/**
* Loads the class with the specified <a href="#name">binary name</a>. The
* default implementation of this method searches for classes in the
* following order:
*
* <p><ol>
*
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li>
*
* <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
* on the parent class loader. If the parent is <tt>null</tt> the class
* loader built-in to the virtual machine is used, instead. </p></li>
*
* <li><p> Invoke the {@link #findClass(String)} method to find the
* class. </p></li>
*
* </ol>
*
* <p> If the class was found using the above steps, and the
* <tt>resolve</tt> flag is true, this method will then invoke the {@link
* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
*
* <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
* #findClass(String)}, rather than this method. </p>
*
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
* during the entire class loading process.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @param resolve
* If <tt>true</tt> then resolve the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
all the answer lie at the code.