您的位置:首页 > 其它

使用自定义 classloader 的正确姿势

2016-07-04 15:21 579 查看
详细的原理就不多说了,网上一大把, 但是, 看了很多很多, 即使看了jdk 源码, 说了罗里吧嗦, 还是不很明白:

到底如何正确自定义ClassLoader, 需要注意什么

ExtClassLoader 是什么鬼

自定义ClassLoader具体是如何加载 类的。。

直接上代码:

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Date;

import com.lk.AbcBean;

public class ClassLoaderLK extends ClassLoader {
/**
* @param args
*/
public static void main(String[] args) {
//        this.class.getSystemClassLoader();

String ext = "java.ext.dirs";
System.out.println("java.ext.dirs :\n" + System.getProperty(ext));
String cp = "java.class.path";
System.out.println("java.class.path :\n" + System.getProperty(cp));

ClassLoader currentClassloader = ClassLoaderLK.class.getClassLoader();

String pp = "d:\\testcl\\";
ClassLoaderLK cl = new ClassLoaderLK(currentClassloader, pp);

System.out.println();
System.out.println("currentClassloader is " + currentClassloader);
System.out.println();
String name = "com.lk.AbcBean.class";
name = "com.lk.AbcBean";
try {
Class<?> loadClass = cl.loadClass(name);

Object object = loadClass.newInstance();

//            AbcBean ss = (AbcBean) object; // 无法转换的 (1)
//            ss.greeting();  (1)

System.out.println();
System.out.println(" invoke some method !");
System.out.println();

Method method = loadClass.getMethod("greeting");
method.invoke(object);

} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

private ClassLoader parent = null; // parent classloader
private String path;

public ClassLoaderLK(ClassLoader parent, String path) {
super(parent);
this.parent = parent; // 这样做其实是无用的
this.path = path;
}

public ClassLoaderLK(String path) {
this.path = path;
}

@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
//        return super.loadClass(name);
Class<?> cls = findLoadedClass(name);
if (cls == null) {
//            cls = getSystemClassLoader().loadClass(name); (2)// SystemClassLoader 会从classpath下加载
//            if (cls == null) {(2)
// 默认情况下, 当前cl的parent是 SystemClassLoader,
// 而当前cl的parent的parent 才是ExtClassLoader
ClassLoader parent2 = getParent().getParent();
//                System.out.println("Classloader is : " + parent2);

try {
System.out.println("try to use ExtClassLoader to load class : " + name);
cls = parent2.loadClass(name);
} catch (ClassNotFoundException e) {
System.out.println("ExtClassLoader.loadClass :" + name + " Failed");
}
//            }(2)

if (cls == null) {
System.out.println("try to ClassLoaderLK load class : " + name);
cls = findClass(name);

if (cls == null) {
System.out.println("ClassLoaderLK.loadClass :" + name + " Failed");
} else {
System.out.println("ClassLoaderLK.loadClass :" + name + " Successful");
}

} else {
System.out.println("ExtClassLoader.loadClass :" + name + " Successful");
}
}
return cls;
}

@Override
@SuppressWarnings("rawtypes")
protected Class<?> findClass(String name) throws ClassNotFoundException {
//        return super.findClass(name);
System.out.println( "try findClass " + name);
InputStream is = null;
Class class1 = null;
try {
String classPath = name.replace(".", "\\") + ".class";
//            String[] fqnArr = name.split("\\."); // split("."); 是不行的, 必须split("\\.")
//            if (fqnArr == null || fqnArr.length == 0) {
//                System.out.println("ClassLoaderLK.findClass()");
//                fqnArr = name.split("\\.");
//            } else {
//                System.out.println( name  +  fqnArr.length);
//            }

String classFile = path + classPath;
byte[] data = getClassFileBytes(classFile );

class1 = defineClass(name, data, 0, data.length);
if (class1 == null) {
System.out.println("ClassLoaderLK.findClass() ERR ");
throw new ClassFormatError();
}

} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return class1;
}

private byte[] getClassFileBytes(String classFile) throws Exception {
FileInputStream fis = new FileInputStream(classFile );
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileC.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outC.write(buffer);
buffer.clear();
}
fis.close();
return baos.toByteArray();
}

}


随便的一个java 类, 简单起见,就写一个bean吧

package com.lk;

import java.util.Date;

public class AbcBean {

@Override
public String toString() {
return "AbcBean [name=" + name + ", age=" + age + "]";
}

String name;
int age;
Date birthDay;

public Date getBirthDay() {
return birthDay;
}
public void setBirthDay(Date birthDay) {
this.birthDay = birthDay;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}

public void greeting() {
System.out.println("AbcBean.greeting()");
}
}


直接执行,结果:

currentClassloader is sun.misc.Launcher$AppClassLoader@513cf0

try to use ExtClassLoader to load class : com.lk.AbcBean
ExtClassLoader.loadClass :com.lk.AbcBean Failed
try to ClassLoaderLK load class : com.lk.AbcBean
try findClass com.lk.AbcBean
java.io.FileNotFoundException: d:\testcl\com\lk\AbcBean.class (系统找不到指定的路径。)
at java.io.FileInputStream.open(Native Method)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:97)
at ClassLoaderLK.getClassFileBytes(ClassLoaderLK.java:172)
at ClassLoaderLK.findClass(ClassLoaderLK.java:145)
at ClassLoaderLK.loadClass(ClassLoaderLK.java:112)
at ClassLoaderLK.main(ClassLoaderLK.java:38)
Exception in thread "main" java.lang.NullPointerException
at ClassLoaderLK.main(ClassLoaderLK.java:40)
ClassLoaderLK.loadClass :com.lk.AbcBean Failed


将com.lk 目录全部复制到 d:\\testcl\\ 下,

currentClassloader is sun.misc.Launcher$AppClassLoader@513cf0

try to use ExtClassLoader to load class : com.lk.AbcBean
ExtClassLoader.loadClass :com.lk.AbcBean Failed
try to ClassLoaderLK load class : com.lk.AbcBean
try findClass com.lk.AbcBean
try to use ExtClassLoader to load class : java.lang.Object
ExtClassLoader.loadClass :java.lang.Object Successful
ClassLoaderLK.loadClass :com.lk.AbcBean Successful

invoke some method !

try to use ExtClassLoader to load class : java.lang.String
ExtClassLoader.loadClass :java.lang.String Successful
try to use ExtClassLoader to load class : java.lang.System
ExtClassLoader.loadClass :java.lang.System Successful
try to use ExtClassLoader to load class : java.io.PrintStream
ExtClassLoader.loadClass :java.io.PrintStream Successful
AbcBean.greeting()


将AbcBean打包成 jar 放置到 jdk 下的jre 的ext目录 ( 打包成 zip 也是可行的! 但是rar是不行的!!! why ? 估计zip和jar都是使用的java 的zip流, 而rar是后面产生的新格式,故没有被支持。另外, 仅仅拷贝class 过去也是不行的! )

执行结果:

currentClassloader is sun.misc.Launcher$AppClassLoader@513cf0

try to use ExtClassLoader to load class : com.lk.AbcBean
ExtClassLoader.loadClass :com.lk.AbcBean Successful

invoke some method !

AbcBean.greeting()


可见ExtClassLoader 是如何作用了的吧!!

总结,

1 从 invoke some method 前后的日志,可见 类加载 的大致过程。

2 代码中 (1), 的部分是注释了的, 因为 不同类加载器加载的类是 不能直接cast的。。 但把(1),(2) 同时解开注释, 又可以了, 这是因为他们都是使用的系统类加载器, 自定义的类加载器相当于没有生效。。( 这个当然不是我们需要的结果。)

3 loadClass, findClass 两个方法的复写是必须的。 上面代码中的loadClass 的写法其实有点问题, 参照classloader 源码, 应该还需要一步: parent加载不上了, 使用bootstrap 加载, 不过感觉一般应该是用不上的—— 谁需要去替换 jdk 的rt.jar 的类 ??

4 ExtClassLoader 是去加载 jdk 下 jre ext 目录的类似jar 的文件—— 后缀是不是jar 不要紧, 内容是jar就行了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: