有时候,根据自己项目的需求,需要重新实现属于自己的类加载器,以满足项目的灵活性和扩展性,下面我们就来实现自己的类加载器.
实现自己的类加载器必须首先继承一个父类加载器.
编写一个类加载器会涉及到以下几个方法:
1. findClass(String name) 根据类的路径查找类,必须重写的方法
2.defineClass(String name, byte[] b, int off, int len) 由父类实现,直接调用
3.loadClass(String name)首先调用父类的findClass方法找,找不到则调用自身重写的findClass方法找,也不需要我们实现.loadClass(String name)默认会调用loadClass(name,false)方法,表示只加载,不发生连接操作.JDK的ClassLoader类的loadClass(String name,boolean resolve)方法的实现如下:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
// 如果该类以前被调用,则直接调用
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 自己实现的findClass方法被调用
c = findClass(name);
}
}
if (resolve) { //进行类的连接,与其他类发生联系
resolveClass(c);
}
return c;
}
可以看出ClassLoader采用了模版模式,在父类加载器加载不到想要的类时,采用自己实现的方法.
自己写的类加载器及测试代码如下:
public class MyClassLoader extends ClassLoader {
private String path = "c:/bin/";
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String namePath = name.replace(".", "/");
String classPath = path + namePath + ".class";
InputStream is = null;
ByteArrayOutputStream os = null;
try {
is = new FileInputStream(new File(classPath));
os = new ByteArrayOutputStream();
int b = 0;
while ((b = is.read()) != -1) {
os.write(b);
}
byte[] bytes = os.toByteArray();
os.flush();
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(os!=null)
os.close();
if(is!=null)
is.close();
} catch (IOException e1) {
os=null;
is = null;
}
}
return null;
}
@SuppressWarnings("unchecked")
public static void main(String[] args) {
MyClassLoader myLoader = new MyClassLoader();
try {
Class myClass = myLoader.loadClass("com.ldh.loader.HelloWorld");
Object obj = myClass.newInstance();
Method method = myClass.getMethod("say", null);
method.invoke(obj, null);
} catch (Exception e) {
e.printStackTrace();
}
}
}
HelloWolrd类如下:
public class HelloWorld {
public void say(){
System.out.println("hello,world");
}
}
我把HelloWorld类放在c:/bin/目录下.
最后谈一下loadClass()和forName()的区别.
从上可以看出调用loadClass(name),相当于调用loadClass(name,false),表示只加载类,不连接初始化类,调用newInstance()才真正完成连接初始化操作.
Class.forName("xxxx")等同于Class.forName("xxxx",true,loader).true,表示载入实例的同时也载入静态初始化区块;false,表示只会加载该类别,但不会调用其静态初始化区块,只有等到整个程序第一次实例化某个类时,静态初始化区块才会被调用
在大多情况下loadClass()和forName()可以互用, 可以把ClassLoader.loadClass()看成是更底层的操作.在某些必须初始化类的场合,比如加载JDBC驱动,只能使用forName()方法了
从上可以看出,实现自己的类加载器相当简单,只要继承一个父类加载器,重写findClass方法就可以了.