类加载器的概述
作用:
类加载器用于实现类的加载动作
解释:对于任意一个类,都必须由加载他的类加载器和这个类本身一起共同确立其在java虚拟机中的唯一性,每一个类加载器,都有一个独立的类名称空间
这计划可以表达的更通俗一些:比较两个类是否"相等",只有在这两个类是由
同一个类加载器加载
的前提下,才有意义,否则,即使这两个类来源于同一个Class文件,被同一个java虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等
如果想通过程序来演示上面的情况,则需要用到java提供的类加载器静态类
java.lang.ClassLoader
ClassLoader代码实现
```java
package com.ssy.jvm.classloader;import java.io.InputStream;
/**
- @Author: DearSil
- @Date: 2023/3/31 11:51
- @Version: 1.0
@Description: Hello Description!
/
public class MyClassLoader extends ClassLoader{
/*- 重写classLoader的加载类的方法
* - @param name
- @return
@throws ClassNotFoundException
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {/**
- name传过来的值是全类名,需要手动去分割并拼接出来class文件名称
*/
String className = name.substring(name.lastIndexOf(".") + 1) + ".class";
- name传过来的值是全类名,需要手动去分割并拼接出来class文件名称
- 重写classLoader的加载类的方法
InputStream is = getClass().getResourceAsStream(name);
if (null == is) {
return super.loadClass(className);
}
try {
//创建一个byte[]用来存储读取的数据
byte[] bytes = new byte[is.available()];
//通过流将数据读取到bytes数组中
is.read(bytes);
//调用父类方法进行读取数据到JVM
return defineClass(className, bytes, 0, is.available());
} catch (Exception e) {
e.printStackTrace();
}
return super.loadClass(name);
}
}
- 调用不同的类加载器加载相同的class文件,得到的class对象是不一样的
![image-20230331141712246.png](https://ucc.alicdn.com/pic/developer-ecology/kjxgjqvvyygtk_e0dc92d2a295484687bf2cc2cf35a348.png)
### 类加载器分类
- 类加载器分为以下几种
| 名称 | 加载的那些类 | 说明 |
| ----------------------------------------- | --------------------- | -------------------------- |
| BootStrap ClassLoader[启动类加载器] | JAVA_HOME/jre/lib | 无法直接访问 |
| Extension ClassLoader[拓展类加载器] | JAVA_HOME/jre/lib/ext | 上级为BootStrap,显示为null |
| Application ClassLoader[应用程序类加载器] | classpath | 上级为Extension |
| 自定义类加载器 | 自定义 | 上级为Application |
- 启动类加载器
- BootStrap ClassLoader 主要负责加载机器上安装的java目录下的核心类文件,也就是JDK安装目录下jre目录下的lib目录,里面存放了一下java运行所需要的核心的类库,当JVM启动的时候,会首先依托于启动类加载器加载我们lib目录下的核心类库。当JVM启动的时候,会首先依托启动类加载器加载我们lib目录下的核心类库
- 拓展类加载器
- Extension ClassLoader主要是加载lib目录下的ext中的文件,这里面的类用来支撑我们系统的运行
- 应用程序类加载器
- Application ClassLoader该类加载器主要是加载classpath环境变量所指定的路径中的类,可以理解为加载我们自己写的java代码
- 自定义类加载器
- 除了以上的三种以外,可以自定义类加载器,根据具体的需求来加载对应的类
### 双亲委派机制
- 所谓的双亲委派,就是指调用类加载器的loadClass方法时,查找类的规则
- 亲子层级结构图
![image-20230403141959209.png](https://ucc.alicdn.com/pic/developer-ecology/kjxgjqvvyygtk_1802764a672c4afbb8aa9b71cc02e3ae.png)
- 基于这个亲子层级结构,就有一个双亲委派机制:先找"父类"去加载,不行的话再由儿子来加载,这样的话可以避免多层级的类加载器重复加载某些类
- **注意**:这里的"父亲"翻译为上级似乎更为合理,因为它们并没有继承关系
- 在所有的ClassLoader类中,都有一个属性:
```java
private final ClassLoader parent;
对于自定义的类加载器,通过DeBUG可以发现,他的parent是
AppClassLoader
,对于AppClassLoader可以发现,他的上级是ExtClassLoader,而再观察ExtClassLoader来说,他的parent属性值为null;虽然Ext类加载器的parent是Null,但是当我们的代码真正执行的时候依然会调用BootStrapClassLoader,因为ClassLoader并不是由java代码实现的了,而是C++代码,所以这里的Ext类加载器的parent为null
类加载器核心源码
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
类加载器加载流程小结
Tomcat类加载器设计
Tomcat容器类加载器设计
Tomcat启动加载流程图
注意
: CommonClassLoader 类加载器只会加载Tomcat根目录下的lib包下的Jar包文件可以在Tomcat根目录下的 属性中进行更改/conf/catalina.properties - common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
SharedClassLoader类加载器默认为空,但是同样可以通过以下路径进行配置,用来指定
所有WebApp可以访问的应用共享类,但是Tomcat不可访问
的应用专用共享类。/conf/catalina.properties - shared.loader=
CatalinaClassLoader类加载器默认也为空,但是同样可以通过以下路径进行配置,用来指定
Tomcat内部课件的类,Web应用不可见
/conf/catalina.properties - server.loader=
WebAppClassLoader类加载器用来加载WEB/INF下面的包
Tomcat类加载器的层级关系
- WebAppClassLoader 的上级是 SharedClassLoader, 所以如果想要达到多个WebApp可以共同访问某一份依赖,可以将依赖的位置配置到Shared.loader配置中,这样可以保证所有的WebApp应用都可以访问同一份的依赖
思考
- 通过SharedClassLoader加载的Spring相关依赖如何获取WebApp相关的类?并实现Bean的管理控制?
- 答:通过上下文类加载器
线程上下文类加载器(Context ClassLoader)
线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassloader()和setContextClassLoader(ClassLoader);分别用来获取和设置上下文类加载器
如果没有通过setContextLoader(ClassLoader)进行设置的话,线程将继承父类的上下文类加载器
如果创建线程时还未设置,他将会从父类线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认为应用程序类加载器(在Tomcat中为webappClassLoader)
因此Spring根本不会去管自己被放在哪里,他统统使用线程上下文加载器来加载类,而线程上下文加载器默认使用了WebAppClassLoder,也就是说那个WebApp应用调用了Spring,Spring就去取该应用自己的WebAppClassLoader来加载Bean
ContextLoaderLinstener监听器的作用就是启动Web容器时,自动装配ApplicationContext的配置信息
小结
有了线程上下文类加载器,程序就可以做一些"舞弊"的事情了,父类加载器去请求子类加载器完成类加载的行为,这种行为实际上是打破了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型的一般性原则,但也是无可奈何的事情,java中涉及SPI的加载基本上都是采用这种方式来完成,例如JNDI,JDBC,JCE,JAXB,和JBI等
java提供了很多服务提供者接口,(Service provider Interface SPI),允许第三方为这些接口提供实现
这些SPI的接口由java核心库来提供,而这些SPI的实现代码则是作为java应用所依赖的jar包被包含进类路径(classPath)里,SPI接口中的代码经常需要加载具体的实现类,那么问题来了,SPI的接口是java核心库的一部分,是由引导类加载器来加载的,SPI的实现类是由系统类加载器来加载的,引导类加载器是无法找到SPI的实现类的,因为按照双亲委派模型,BootStrapClassLoader无法委派AppClassLoader来加载类
比如:在java中定义了接口java.sql.Driver,并没有具体的实现类,具体的实现类都是由各个数据库厂商来提供的