0xCAFEBABE

talk is cheap show me the code

0%

jvm系列之类加载器命名空间

万物基于jvm

众所周知 我们写的一个个的类,最终会编译为class字节码 然后被类加载器加载到内存中

而类加载的最终产物是就是一个个的Class对象, 如果自定义类加载器就需要继承ClassLoader抽象类何实现findClass方法, 如果使用不当, 就会造成一些奇怪的错误下面举个栗

首先来个自定义类加载器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ClassLoaderA extends ClassLoader {
public ClassLoaderA(){
// 因为存在双亲委派机制在加载一个类时会首先尝试通过父类加载器加载
// 将父类加载器设为null 强制使用该类加载器
super(null);
}
@Override
protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
try(
InputStream inputStream = getSystemResourceAsStream(name.replaceAll("\\.", "/")+".class");
BufferedInputStream bufferedInputStream = new BufferedInputStream(Objects.requireNonNull(inputStream));
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
){
byte[] buff = new byte[1024];
int len;
while ((len = bufferedInputStream.read(buff))!=-1){
outputStream.write(buff, 0, len);
}
return defineClass(name, outputStream.toByteArray(),0, outputStream.size());

}catch (Exception e){
throw new RuntimeException(e);
}
}
}

接着用这个类加载器加载一个类

1
Class<?> aClass = loader.loadClass("com.example.bean.User");

然后通过class实例化一个对象

1
User user = (User)aClass.newInstance();

接着在main方法中测试

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
ClassLoader loader = new ClassLoaderA();
try {
Class<?> aClass = loader.loadClass("com.example.bean.User");
User user = (User) aClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}

最后神奇的事发生了

1
java.lang.ClassCastException: com.example.bean.User cannot be cast to com.example.bean.User

User不能转为User? 这个异常有点匪夷所思

原因就在于类加载器有一个所谓的命名空间的存在, 每个类加载器都有自己的命名空间, 每个Class都归属于一个命名空间,子命名空间可以访问父命名空间中的类, 不同的命名空间之间是相互不可见的, 上面的栗子中,main方法所在的类在运行时首先被系统类加载器加载, 属于系统类加载器的命名空间, 而User类被另一个独立的类加载器加载, 属于另外一个命名空间, 两者之间是相互隔离的, User user = (User) aClass.newInstance(); 这句虽然表面上看起来转换并没错, 但分属于了两个不同的命名空间, 所以出现了ClassCastException这个神奇的异常,解决方法也很简单, 把super(null) 这行删掉使得系统类加载器成为其parent就行了