很多时候人们会使用一些自定义的classloader ,而不是使用系统的class loader。大多数时候人们这样做的原因是,他们在编译时无法预知运行时会需要那些class。特别是在那些appserver中,比如tomcat,avalon-phonix,jboss中。或是程序提供一些plug-in的功能,用户可以在程序编译好之后再添加自己的功能,比如ant, jxta-shell等。定制一个classloader很简单,一般只需要理解很少的几个方法就可以完成。
一个最简单的自定义的classloader从classloader类继承而来。这里我们要做一个可以在运行时指定路径,加载这个路径下的class的classloader。
通常我们使用classloader.loadclass(string):class方法,通过给出一个类名,就会得到一个相应的class实例。因此只要小小的改动这个方法,就可以实现我们的愿望了。
源码:
protected synchronized class loadclass(string name, boolean resolve) throws classnotfoundexception { // first, check if the class has already been loaded class c = findloadedclass(name); if (c == null) { try { if (parent != null) { c = parent.loadclass(name, false); }else{ c = findbootstrapclass0(name); } }catch(classnotfoundexception e){ // if still not found, then call findclass in order // to find the class. c = findclass(name); } } if (resolve) { resolveclass(c); } return c;}
source from classloader.java
first,check javaapi doc:上面指出了缺省的loadclass方法所做的几个步骤。
1. 调用findloadedclass(string):class 检查一下这个class是否已经被加载过了,由于jvm 规范规定classloader可以cache它所加载的class,因此如果一个class已经被加载过的话,直接从cache中获取即可。
2. 调用它的parent 的loadclass()方法,如果parent为空,这使用jvm内部的class loader(即著名的bootstrap classloader)。
3. 如果上面两步都没有找到,调用findclass(string)方法来查找并加载这个class。
后面还有一句话,在java 1.2版本以后,鼓励用户通过继承findclass(string)方法实现自己的class loader而不是继承loadclass(string)方法。
既然如此,那么我们就先这么做:)
public class anotherclassloader extends classloader { private string basedir;private static final logger log = logger.getlogger(anotherclassloader.class); public anotherclassloader (classloader parent, string basedir) { super(parent); this.basedir = basedir; } protected class findclass(string name) throws classnotfoundexception { log.debug("findclass " + name); byte[] bytes = loadclassbytes(name); class theclass = defineclass(name, bytes, 0, bytes.length);//a if (theclass == null) throw new classformaterror(); return theclass; } private byte[] loadclassbytes(string classname) throws classnotfoundexception { try { string classfile = getclassfile(classname); 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(); } catch (ioexception fnfe) { throw new classnotfoundexception(classname); } } private string getclassfile(string name) { stringbuffer sb = new stringbuffer(basedir); name = name.replace('.', file.separatorchar) + ".class"; sb.append(file.separator + name); return sb.tostring(); }}
[i]ps:这里使用了一些jdk1.4的nio的代码:)[/i]
很简单的代码,关键的地方就在a处,我们使用了defineclass方法,目的在于把从class文件中得到的二进制数组转换为相应的class实例。defineclass是一个native的方法,它替我们识别class文件格式,分析读取相应的数据结构,并生成一个class实例。
还没完呢,我们只是找到了发布在某个目录下的class,还有资源呢。我们有时会用class.getresource():url来获取相应的资源文件。如果仅仅使用上面的classloader是找不到这个资源的,相应的返回值为null。
同样我们看一下原来的classloader内部的结构。
public java.net.url getresource(string name) { name = resolvename(name); classloader cl = getclassloader0();//这里 if (cl==null) { // a system class. return classloader.getsystemresource(name); } return cl.getresource(name);}
原来是使用加载这个class的那个classloader获取得资源。
public url getresource(string name) { url url; if (parent != null) { url = parent.getresource(name); } else { url = getbootstrapresource(name); } if (url == null) { url = findresource(name);//这里 } return url;}
这样看来只要继承findresource(string)方法就可以了。修改以下我们的代码:
//新增的一个findresource方法protected url findresource(string name) { log.debug("findresource " + name); try { url url = super.findresource(name); if (url != null) return url; url = new url("file:///" + convername(name)); //简化处理,所有资源从文件系统中获取 return url; } catch (malformedurlexception mue) { log.error("findresource", mue); return null; }}private string convername(string name) { stringbuffer sb = new stringbuffer(basedir); name = name.replace('.', file.separatorchar); sb.append(file.separator + name); return sb.tostring();}
好了,到这里一个简单的自定义的classloader就做好了,你可以添加其他的调料(比如安全检查,修改class文件等),以满足你自己的口味:)
闽公网安备 35060202000074号