文章引用:
既然JVM已经提供了默认的类加载器,为什么还要定义自已的类加载器呢?
因为Java中提供的默认ClassLoader,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时,比如:我要加载网络上的一个class文件,通过动态加载到内存之后,要调用这个类中的方法实现我的业务逻辑。在这样的情况下,默认的ClassLoader就不能满足我们的需求了,所以需要定义自己的ClassLoader。
定义自已的类加载器分为两步:
1、继承java.lang.ClassLoader
2、重写父类的findClass方法
读者可能在这里有疑问,父类有那么多方法,为什么偏偏只重写findClass方法?
因为JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的算法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可。如没有特殊的要求,一般不建议重写loadClass搜索类的算法。下图是API中ClassLoader的loadClass方法:
示例:自定义一个NetworkClassLoader,用于加载网络上的class文件
public class NetWorkClassLoader extends ClassLoader {
private String rootUrl;
public NetWorkClassLoader(String rootUrl) {
this.rootUrl = rootUrl;
}
@SuppressWarnings("rawtypes")
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = null;
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
// 将class的字节码数组转换成Class类的实例
clazz = defineClass(name, classData, 0, classData.length);
return clazz;
}
/**
* 根据类的二进制名称,获得该class文件的字节码数组
*
* @param name
* @return
*/
private byte[] getClassData(String name) {
InputStream inputStream = null;
try {
String path = classNameToPath(name);
URL url = new URL(path);
byte[] buff = new byte[1024 * 4];
int len = -1;
inputStream = url.openStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while ((len = inputStream.read(buff)) != -1) {
byteArrayOutputStream.write(buff, 0, len);
}
return byteArrayOutputStream.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(inputStream);
}
return null;
}
private String classNameToPath(String name) {
return rootUrl + "/" + name.replace(".", "/") + ".class";
}
/*
通过URL读取网页内容
1通过URL对象的openStream()方法能够得到指定资源的输入流。
2通过输入流能够读取、訪问网络上的数据。
*/
}
测试类:
public static void main(String[] args) {
try {
String rootUrl = "http://localhost:8080/httpweb/classes";
NetWorkClassLoader networkClassLoader = new NetWorkClassLoader(rootUrl);
String classname = "org.classloader.simple.NetClassLoaderTest";
Class clazz = networkClassLoader.loadClass(classname);
System.out.println(clazz.getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
打印结果:
下图是我机器上web服务器的目录结构:
目前常用web服务器中都定义了自己的类加载器,用于加载web应用指定目录下的类库(jar或class),如:Weblogic、Jboss、tomcat等,下面我以Tomcat为例,展示该web容器都定义了哪些个类加载器:
1、新建一个web工程httpweb
2、新建一个ClassLoaderServletTest,用于打印web容器中的ClassLoader层次结构
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ClassLoaderServletTest extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
ClassLoader loader = this.getClass().getClassLoader();
while(loader != null) {
out.write(loader.getClass().getName()+"<br/>");
loader = loader.getParent();
}
out.write(String.valueOf(loader));
out.flush();
out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
this.doGet(request, response);
}
}
3、配置Servlet,并启动服务
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>ClassLoaderServletTest</servlet-name>
<servlet-class>ClassLoaderServletTest</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>ClassLoaderServletTest</servlet-name>
<url-pattern>/servlet/ClassLoaderServletTest</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
4、访问Servlet,获得显示结果