在spring中,有一个获取默认的类加载器的方法
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
获取的是线程上下文加载器,其实这个加载器默认的情况就是系统加载器,跟这个ClassLoader.getSystemClassLoader()一样的,那为什么要这么写呢?
之前在tomcat中部署项目,可以部署很多个war包,其中war中的类很多类路径+类名都是一样,根据双亲委派,一个com.fen.dou.TestCl类只能被加载一次,然而会造成版本冲突,tomcat是怎么解决的呢?
在java程序中是由Launcher来进行加载启动程序的
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
if (var2 != null) {
SecurityManager var3 = null;
if (!"".equals(var2) && !"default".equals(var2)) {
try {
var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
} catch (IllegalAccessException var5) {
} catch (InstantiationException var6) {
} catch (ClassNotFoundException var7) {
} catch (ClassCastException var8) {
}
} else {
var3 = new SecurityManager();
}
if (var3 == null) {
throw new InternalError("Could not create SecurityManager: " + var2);
}
System.setSecurityManager(var3);
}
}
里面初始化了一个系统加载器 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
这个加载器,可以通过ClassLoader.getSystemClassLoader()获取,整个java程序就只会有一个系统加载器,符合双亲委派,所以默认整个java程序不会出现重复加载相同类的情况
我们可以自定义两个ClassLoader,打破双亲委派,重写findClass,分别加载不同的spring项目
package com.fen.dou;
import lombok.SneakyThrows;
import org.springframework.core.io.UrlResource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.util.StringUtils;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
public class MyClassLoaderTest implements Runnable{
private static ClassLoader commoncl = null;
private static ClassLoader customcl = null;
public MyClassLoaderTest() {
}
public static ClassLoader getCommonClassLoader() throws IOException {
if (commoncl == null) {
synchronized(MyClassLoaderTest.class) {
if (commoncl == null) {
commoncl = new MyClassLoader("F:\\code\\nacosServiceProvider\\provider3303\\target\\classes");
}
}
}
return commoncl;
}
public static ClassLoader getCustomClassLoader() throws IOException {
if (customcl == null) {
synchronized(MyClassLoaderTest.class) {
if (customcl == null) {
customcl = new MyClassLoader("F:\\code\\nacosServiceProvider\\proviver3302\\target\\classes");
}
}
}
return customcl;
}
@SneakyThrows
@Override
public void run() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(getCommonClassLoader());
Class clazz = Thread.currentThread().getContextClassLoader().loadClass("com.fen.dou.TestCl");
Object obj = clazz.newInstance();
Method method= clazz.getDeclaredMethod("say", null);
method.invoke(obj, null);
Thread.currentThread().setContextClassLoader(getCustomClassLoader());
Class clazz1 = Thread.currentThread().getContextClassLoader().loadClass("com.fen.dou.TestCl");
Object obj1 = clazz1.newInstance();
Method method1= clazz1.getDeclaredMethod("say", null);
method1.invoke(obj1, null);
// print(Thread.currentThread().getContextClassLoader(),"application.yml");
}catch (Exception e){
e.printStackTrace();
}finally {
Thread.currentThread().setContextClassLoader(classLoader);
// print(classLoader,"META-INF/spring.factories");
}
}
void print(ClassLoader classLoader,String path) throws IOException {
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(path) :
ClassLoader.getSystemResources(path));
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
for (Map.Entry<?, ?> entry : properties.entrySet()) {
String factoryClassName = ((String) entry.getKey()).trim();
for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
// System.out.println("---factoryClassName--"+factoryClassName);
System.out.println("---"+classLoader.getClass().getName()+"--"+factoryName.trim());
}
}
}
}
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终 的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
MyClassLoaderTest classLoader = new MyClassLoaderTest();
new Thread(classLoader).start();
}
}
现在还没说明spring要用线程上下文加载器
main线程 刚开始使用的是 AppClassLoader,我现在要使用自定义打破双亲委派的 CommonClassLoader 加载器 去加载 spring 项目,是不是需要切换 加载器,由AppClassLoader切换成 CommonClassLoader,那怎么切换呢?
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(getCommonClassLoader());
Class clazz = Thread.currentThread().getContextClassLoader().loadClass("com.fen.dou.TestCl");
Object obj = clazz.newInstance();
Method method= clazz.getDeclaredMethod("say", null);
method.invoke(obj, null);
}catch (Exception e){
e.printStackTrace();
}finally {
Thread.currentThread().setContextClassLoader(classLoader);
}
则tomcat可以用 AppClassLoader加载自身的类,用Thread.currentThread().setContextClassLoader(getCommonClassLoader());方式切换加载器,加载spring项目的类,那spring项目通过 Thread.currentThread().getContextClassLoader();来进行接收这个加载器