前言
URLClassLoader作为最常用、使用最广泛的类加载器,在java世界中扮演着举足轻重的角色。作为码农有必要对它的功能和用途有一定的了解,才能在java的世界里自在遨游。类加载器作为java中核心的要素,遇到一些疑难杂症时从它入手总能找到解决方法。相信小伙伴们经常会遇到jar包冲突的问题,这种情况下一般解决办法就是去掉其它版本的jar包最终只保留一个版本。那么能不能多个版本共存呢,答案是肯定的。问题还是出在类加载器身上,由于双亲委托机制的存在,对于包含相同类名的jar包,class类实例只会选择优先被加载的jar包,后者则会被无视。如果要实现多版本jar包共存的目的,只能让这些jar包分别被不同的类加载器加载。
一、系统类加载器 AppClassLoader和URLClassLoader
一般的,系统类加载器是AppClassLoader,当然也可以通过jvm选项Djava.system.class.loader。AppClassLoader类继承于URLClassLoader类,URLClassLoader加载的路径包含 文件目录、jar包、网络路径,大多数情况下我们使用的是前两者。
一般通过在URLClassLoader的类构造器中指定URL数组来初始化路径的加载,当然也可以使用反射的方式来动态加载路径。反射方式也有两种,其一是通过URLClassLoader的成员变量ucp的addURL方法加载,其二是直接通过URLClassLoader类本身的addURL进行加载。 AppClassLoader还可以通过 appendToClassPathForInstrumentation(String) 方法进行加载
当然本质还是调用了URLClassLoader的addURL来加载。
二、如何加载同名类
1、创建URLClassLoader类的子类,并重写loadClass方法,并且维护一个集合类的变量。
当要加载的类名已经在集合变量中存在,就跳过从父加载器加载的步骤。
因为类加载器都继承自ClassLoader,下面是它的loadClass方法的实现
在父加载器中加载失败时会转到findClass方法,所以我们可以在重载后的loadClass方法中去掉父加载器加载的过程。
MyURLClassLoader类完整代码如下
package com.suntown.loader;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandlerFactory;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public abstract class MyURLClassLoader extends URLClassLoader {
public MyURLClassLoader(URL[] urls, ClassLoader classLoader) {
super(urls, classLoader);
filter(urls);
}
public MyURLClassLoader(URL[] urls) {
super(urls);
filter(urls);
}
public MyURLClassLoader(URL[] urls, ClassLoader classLoader, URLStreamHandlerFactory urlStreamHandlerFactory) {
super(urls, classLoader, urlStreamHandlerFactory);
filter(urls);
}
protected void addURL(URL url) {
super.addURL(url);
filter(url);
}
private Set<String> classNameSet = new HashSet<String>();
public abstract boolean isOverrideURL(URL url);
private void filter(URL url){
if(isOverrideURL(url)){
try{
File f = new File(url.getPath());
if(f.exists()){
JarFile jarFile = new JarFile(f.getAbsolutePath());
Enumeration<JarEntry> et = jarFile.entries();
while (et.hasMoreElements()) {
JarEntry ele = et.nextElement();
if (ele.getName().endsWith(".class")) {
String name = ele.getName().substring(0, ele.getName().length() - 6).replace("/", ".");
classNameSet.add(name);
}
}
}
}catch(IOException e){
e.printStackTrace();
}
}
}
private void filter(URL[] urls){
for(URL url : urls){
filter(url);
}
}
public Class loadClass(String name) throws ClassNotFoundException {
if(classNameSet.contains(name)){
return super.findClass(name);
}else{
return super.loadClass(name);
}
}
}
三、运行验证
分别使用AppClassLoader、URLClassLoader和MyURLClassLoader类加载同一个jar包中的一个类
并分别打印该类所属的ClassLoader。
测试代码如下
package loader;
import sun.misc.URLClassPath;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class LoaderTest{
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, MalformedURLException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
URLClassLoader userloader = new URLClassLoader(new URL[]{});
URLClassLoader myloader = new MyURLClassLoader(new URL[]{}) {
@Override
public boolean isOverrideURL(URL url) {
return true;
}
};
Method mdAddURL = URLClassLoader.class.getDeclaredMethod("addURL",java.net.URL.class);
mdAddURL.setAccessible(true);
URL url = new File("f:\\git\\test\\ojdbc6.jar").toURI().toURL();
mdAddURL.invoke(sysloader,new Object[]{url});
mdAddURL.invoke(userloader,new Object[]{url});
mdAddURL.invoke(myloader,new Object[]{url});
Class c1 = sysloader.loadClass("oracle.sql.CHAR");
System.out.println(c1.getClassLoader());
Class c2 = userloader.loadClass("oracle.sql.CHAR");
System.out.println(c2.getClassLoader());
Class c3 = myloader.loadClass("oracle.sql.CHAR");
System.out.println(c3.getClassLoader());
}
}
运行结果如下