类加载子系统
核心代码:
• private static Launcher launcher = new Launcher();
• extcl = ExtClassLoader.getExtClassLoader();
• loader = AppClassLoader.getAppClassLoader(extcl);
• Thread.currentThread().setContextClassLoader(loader);
1.启动类加载器
启动类加载器是由C++编写,不像其它加载类一样有实体类,jvm是将c++处理类加载的一段逻辑定义为启动类加载器
查看启动类的加载路径
/**
* 启动类加载器
* bootstrap classloader path
* -Xbootclasspath 指定路径
*/
public void BootstrapClassLoader() {
URLClassPath bootstrapClassPath = Launcher.getBootstrapClassPath();
for (URL url : bootstrapClassPath.getURLs()) {
System.out.println(url);
}
}
openjdk1.8 源码
2.拓展类加载器
/**
* 拓展类加载器
* ext classloader path
* java.ext.dirs 指定路径
*/
public void ExtClassLoader() {
ClassLoader parent = ClassLoader.getSystemClassLoader().getParent();
URLClassLoader urlClassLoader = (URLClassLoader) parent;
Arrays.stream(urlClassLoader.getURLs()).forEach((u)-> System.out.println(u));
}
3.应用程序加载器
/**
* 应用类加载器
* app classloader path
* java.class.path 指定路径
*/
public void AppClassLoader() {
URLClassLoader systemClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Arrays.stream(systemClassLoader.getURLs()).forEach(s-> System.out.println(s));
}
4.自定义类加载器
/**
* 实现自定义加载器的步骤:
* 1.实现ClassLoader接口
* 2.重写超类的findClass方法
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is !=null) {
byte[] b = new byte[0];
try {
b = new byte[is.available()];
} catch (IOException e) {
e.printStackTrace();
}
try {
is.read(b);
} catch (IOException e) {
e.printStackTrace();
}
return defineClass(name, b, 0, b.length);
}
return null ;
}
5.线程上下文类加载器![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d44023672db8e85ff19826bcc4d81415.png#pic_center)
1.线程上下文类加载器:是一种特殊的类加载器,为了解决双亲委派的缺陷而产生的,SPI机制运用到
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
2.获取:Thread.currentThread().getContextClassLoader();
设置:Thread.currentThread().setContextClassLoader(new KasonClassLoader());
6.类加载器存储空间
ps:不同类加载器在方法区会分配不同的内存区域
7.双亲委派
如果一个类加载器收到了加载某个类的请求,则该类加载器并不会去加载该类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是如此,因此所有的类加载请求最终都会传送到顶端的启动类加载器;只有当父类加载器在其搜索范围内无法找到所需的类,并将该结果反馈给子类加载器,子类加载器会尝试去自己加载。
ps:类加载器存在逻辑上的父子关系,自定义->app->拓展类->启动类
类加载器加载类,首先自己不会去加载,会委托父类去加载,直到找到,启动类加载器找不到,在由自定义加载器去加载,如果没有直接抛异常
8.双亲委派的局限性(打破双亲委派)
优点:安全性高
缺陷:无法做到向下委派
在某些情况,父类的加载器需要委托子类去加载类信息,这个时候双亲委派无法满足
经典的例子:各种数据库的驱动类,如:mysql,oracle
DriverManager(也由jdk提供)要加载各个实现了Driver接口的实现类,然后进行管理,但是DriverManager由启动类加载器加载,只能记载JAVA_HOME的lib下文件,而其实现是由服务商提供的,由系统类加载器加载,这个时候就需要启动类加载器来委托子类来加载Driver实现
这个时候需要打破双亲委派,
SPI机制:运用到线程上下文类加载器,ServiceLoader
实现SPI机制的步骤
1.定义一个接口
2.实现接口类
3.在META-INF/services/下,添加接口类的全限定类名文件,内容是实现类的全限定类名,多个用换行
4.ServiceLoader.load 加载
简单代码实现:https://gitee.com/kasonlj/kason-spring-v1.0/tree/master/src/com/kason/spi
自定义类加载器也可以打破双亲委派
https://gitee.com/kasonlj/kason-spring-v1.0/tree/master/src/com/kason/classloader