-
双亲委派模型:如果一个类加载器收到了类加载请求,他并不会自己先去加载,而是把这个请求委托给父类加载器,父类加载器还存在父类加载器则进一步向上委托,依次递归,请求最终到达顶层启动类加载器。如果父类加载器可以完成类加载任务,就成功返回,父类加载器无法完成加载任务,则子类加载器会尝试自己去加载。
-
双签委派模型优缺点:
2.1 优点
避免重复加载,确保类的全局唯一性
保护程序安全,防止核心api被随意篡改
2.2 缺点
由于加载范围限制,顶层的ClassLoader无法访问底层的ClassLoader所加载的类。 -
为什么要破坏双亲委派模型?
由于加载范围的限制,顶层的ClassLoader无法访问底层ClassLoader所加载的类,此时需要破坏双亲委派模型。 -
以JDBC为例
4.1 不使用java SPI时,以如下方式加载驱动实现类:
在Driver类中像DriverManager注册对应的驱动实现类。
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.
getConnection("jdbc:mysql://myhost/test?useUnicode=true&characterEncoding=utf-8&useSSL=false", "test", "test");
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
4.2 使用java SPI
JDBC4.0以后,开始支持使用SPI的方式来注册Driver,具体做法:在Mysql的jar包中的META-INF/services/java.sql.Driver文件中指明当前使用的Driver是哪个。
SPI:策略模式,根据配置决定运行时的接口实现类是哪个。当使用不同驱动时候,我们不需要手动通过Class.forName加载驱动类,只需要引入相应jar即可。
Connection conn = DriverManager.
getConnection("jdbc:mysql://myhost/test?useUnicode=true&characterEncoding=utf-8&useSSL=false", "test", "test");
4.2.1 驱动类何时加载?
- 从META-INF/services/java.sql.Driver文件中获取具体的实现类“com.mysql.jdbc.Driver”
- 通过Class.forName(“com.mysql.jdbc.Driver”)将这个类加载进来
- DriverManager在rt.jar包中,所以DriverManager是通过启动类加载器加载进来的。而Class.forName()加载的调用者是ClassLoader,所以如果用启动类加载器加载com.mysql.jdbc.Driver 肯定是加载不到的。
4.2.2 解决办法
破坏双亲委派模型。
让顶层类加载器加载底层类加载器。
public class DriverManager {
static {
loadInitialDrivers();
println("JDBC DriverManager initialized");
}
private static void loadInitialDrivers() {
// 省略部分代码
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
// 根据配置文件加载驱动实现类
ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
Iterator<Driver> driversIterator = loadedDrivers.iterator();
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
return null;
}
});
// 省略部分代码
}
}
ServiceLoader:通过执行Thread.currentThread().getContextClassLoader()获取了线程上下文加载器
// ServiceLoader#load
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
ExtClassLoader和AppClassLoader都是通过Launcher类来创建的,在Launcher类的构造函数中,可以看到线程上下文类加载器默认设置了AppClassLoader
public class 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);
}
// 设置线程上下文类加载器为AppClassLoader
Thread.currentThread().setContextClassLoader(this.loader);
// 省略部分代码
}
}
线程上下文类加载器让父类加载器能通过调用子类加载器来加载类,这打破了双亲委派模型的原则
网友评论