双亲委派机制指的是加载类时,会首先从其父加载器中去加载类,如果加载不到,再从当前类加载器中去加载类。这样的目的就是为了防止我们自定义的一些类覆盖掉启动时的一些核心类。
不过,有些场景下,我们想要从自定义的类加载器中加载类时(比如自定义加载器和父加载器加载的是磁盘上两个不同目录下的类文件时,如果此时二者目录下都放置着同名的类文件,就会发生覆盖),这个机制就会给我们带来很大的问题。
解决办法:自定义类加载器,开放findClass()方法,直接调用findClass()方法进行加载。
理由:ClassLoader类里面的loadClass()方法实际上是先通过父加载器加载,再通过子加载器加载。
此时,如果我们直接调用子加载器的findClass()方法,就可以绕过ClassLoader类loadClass()方法的双亲委派机制代码逻辑。
自定义加载类:
public class CustomerWebAppClassLoader extends URLClassLoader {
public CustomerWebAppClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public CustomerWebAppClassLoader(URL[] urls) {
super(urls);
}
public CustomerWebAppClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
super(urls, parent, factory);
}
/**
* 将findClass()方法开放(findClass()方法本身是protected类型的,不能在外界直接访问),直接调用以获取class,绕过双亲委派机制。
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
public Class<?> findClass(final String name) throws ClassNotFoundException {
return super.findClass(name);
}
}
调用加载:
Class<?> aClass = classLoader.findClass(className);
附录:以下,贴出从某个根路径下加载所有的类文件的代码。
以下代码参考于https://www.cnblogs.com/umgsai/p/11918520.html,在其基础上就绕过双亲委派机制做了一点微调。希望能对大家有所帮助。
public static Set<Class<?>> loadClasses(String rootClassPath, CustomerWebAppClassLoader classLoader) throws Exception {
Set<Class<?>> classSet = new HashSet();
// 设置class文件所在根路径
File clazzPath = new File(rootClassPath);
// 记录加载.class文件的数量
int clazzCount = 0;
if (clazzPath.exists() && clazzPath.isDirectory()) {
// 获取路径长度
int clazzPathLen = clazzPath.getAbsolutePath().length() + 1;
Stack<File> stack = new Stack<>();
stack.push(clazzPath);
// 遍历类路径
while (!stack.isEmpty()) {
File path = stack.pop();
File[] classFiles = path.listFiles(new FileFilter() {
@Override
public boolean accept(File pathname) {
//只加载class文件
return pathname.isDirectory() || pathname.getName().endsWith(".class");
}
});
if (classFiles == null) {
break;
}
for (File subFile : classFiles) {
if (subFile.isDirectory()) {
stack.push(subFile);
} else {
if (clazzCount++ == 0) {
Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
boolean accessible = method.isAccessible();
try {
if (!accessible) {
method.setAccessible(true);
}
// 设置类加载器(从外面传入,每一个context应用各自独立一个classLoader)
//URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
// 将当前类路径加入到类加载器中
method.invoke(classLoader, clazzPath.toURI().toURL());
} catch (Exception e) {
e.printStackTrace();
} finally {
method.setAccessible(accessible);
}
}
// 文件名称
String className = subFile.getAbsolutePath();
className = className.substring(clazzPathLen, className.length() - 6);
//将/替换成. 得到全路径类名
className = className.replace(File.separatorChar, '.');
// 加载Class类,此处直接调用findClass()方法,
Class<?> aClass = classLoader.findClass(className);
classSet.add(aClass);
}
}
}
}
return classSet;
}
调用时代码:
CustomerWebAppClassLoader customerWebAppClassLoader = new CustomerWebAppClassLoader(new URL[]{});
Set<Class<?>> classes = loadClasses(mergedRootPath,customerWebAppClassLoader);