为什么要编写自己的类加载器?
当class文件不在ClassPath路径下,默认系统类加载器无法找到该class文件,在这种情况下我们需要实现一个自定义的ClassLoader来加载特定路径下的class文件生成class对象。
当一个class文件是通过网络传输并且可能会进行相应的加密操作时,需要先对class文件进行相应的解密后再加载到JVM内存中,这种情况下也需要编写自定义的ClassLoader并实现相应的逻辑。当一个class文件是通过网络传输并且可能会进行相应的加密操作时,需要先对class文件进行相应的解密后再加载到JVM内存中,这种情况下也需要编写自定义的ClassLoader并实现相应的逻辑
当需要实现热部署功能时(一个class文件通过不同的类加载器产生不同class对象从而实现热部署功能),需要实现自定义ClassLoader的逻辑。当需要实现热部署功能时(一个class文件通过不同的类加载器产生不同class对象从而实现热部署功能),需要实现自定义ClassLoader的逻辑。
当继承ClassLoader时,只要重写findClass方法即可,还可以继承URLClassLoader,代码更简洁,这里不再赘述,下面写一个从指定文件中加载class文件的FileClassLoaderpublic class DemoObj {
public String toString() {
return 'I am DemoObj';
}
}
javac生成相应的class文件,放到指定目录,然后由FileClassLoader去加载public class FileClassLoader extends ClassLoader {
// class文件的目录
private String rootDir;
public FileClassLoader(String rootDir) {
this.rootDir = rootDir;
}
@Override
protected Class?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = rootDir + File.separatorChar + className.replace('.', File.separatorChar) + '.class';
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
String rootDir='E:\\Code\\study-java\\src\\main\\java';
FileClassLoader loader = new FileClassLoader(rootDir);
try {
// 传入class文件的全限定名
Class?> clazz = loader.loadClass('com.st.classloader.DemoObj');
// com.st.classloader.FileClassLoader@7ea987ac
System.out.println(clazz.getClassLoader());
// I am DemoObj
System.out.println(clazz.newInstance().toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
可以对class文件进行加密和解密,实现应用的热部署,防止类重名等。这里只对Tomcat中的ClassLoader进行分析
在解释防止类重名作用前先抛出一个问题,Class对象的唯一标识能否只由全限定名确定?答案是不能,因为你无法保证多个项目间不出现相同的全限定名的类。比如和JDK原生类重名或者你的Web项目和Tomcat类重名(全限定名重名无法沟通,无法约束)。JVM判断2个类是否相同的条件是:(1)全限定名相同(2)由同一个类加载器加载
写个例子验证一下,先自定义一个FileClassLoader1,直接defineClass,不委托父类加载器进行加载
public class FileClassLoader1 extends ClassLoader {
private String rootDir;
public FileClassLoader1(String rootDir) {
this.rootDir = rootDir;
}
public Class?> myLoadClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = rootDir + File.separatorChar + className.replace('.', File.separatorChar) + '.class';
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
验证即使同一个class文件被不同classloader加载,也会被认为是不同的类public class ClassLoaderDemo2 {
public static void main(String[] args) throws Exception {
String rootDir='E:\\Code\\study-java\\src\\main\\java';
FileClassLoader1 loader = new FileClassLoader1(rootDir);
Class?> clazz = loader.myLoadClass('com.st.classloader.DemoObj');
// com.st.classloader.FileClassLoader1@12a3a380
System.out.println(clazz.getClassLoader());
// sun.misc.Launcher$AppClassLoader@58644d46
System.out.println(DemoObj.class.getClassLoader());
Object object = clazz.newInstance();
// I am DemoObj
System.out.println(object);
// 从这里可以看到,虽然class文件一样,但是由不同的classloader加载,则为不同的类
// false
System.out.println(object instanceof DemoObj);
}
}
Tomcat中就定义了很多ClassLoader来防止重名。在Tomcat中提供了一个Common ClassLoader,它主要负责加载Tomcat使用的类和Jar包以及应用通用的一些类和Jar包,例如CATALINA_HOME/lib目录下的所有类和Jar包。Tomcat会为每个部署的应用创建一个唯一的类加载器,也就是WebApp ClassLoader,它负责加载该应用的WEB-INF/lib目录下的Jar文件以及WEB-INF/classes目录下的Class文件。由于没有应用都有自己的WebApp ClassLoader,这样就可以使不同的Web应用之间相互隔离,彼此之间看不到对方使用的类文件。即使不同项目下的类全限定名有可能相等,也能正常工作。而对应用进行热部署时,会抛弃原有的WebApp ClassLoader,并为应用创建新的WebApp ClassLoader。