类加载器的双亲委派模型_java mock 模拟接口

类加载器的双亲委派模型_java mock 模拟接口JVM 类加载器 JVM 主要有以下几种类加载器 引导类加载器 主要加载 JVM 运行核心类库 位于 JRE 的 lib 目录下 如 rt jar 中的类 扩展类加载器 主要加载 JVM 中扩展类 位于 JRE 的 ext 目录下 应用程序类加载器 主要负责加载 ClassPath 路径下的类 也就是业务类 自定义加载器 负责加载用户自定义路径下的类 类加载器关系 源码解析

JVM类加载器

JVM主要有以下几种类加载器:

引导类加载器
主要加载JVM运行核心类库,位于JRE的lib目录下,如rt.jar中的类。

扩展类加载器
主要加载JVM中扩展类,位于JRE的ext目录下。

应用程序类加载器
主要负责加载ClassPath路径下的类,也就是业务类。

自定义加载器
负责加载用户自定义路径下的类。

类加载器关系

源码解析

ExtClassLoader和AppClassLoader的创建流程

先看下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就是默认的类加载器:即AppClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {

throw new InternalError("Could not create application class loader", var9);
}
//设置默认classLoader
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
}
ExtClassLoader

看下ExtClassLoader的获取方法getExtClassloader():
可以看到ExtClassLoader是Launcher的一个内部类,继承的是URLClassLoader。

public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { 

//获取要加载的类文件
final File[] var0 = getExtDirs();

try {

return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction() {

public Launcher.ExtClassLoader run() throws IOException {

int var1 = var0.length;

for(int var2 = 0; var2 < var1; ++var2) {

MetaIndex.registerDirectory(var0[var2]);
}
//new一个ExtClassLoader
return new Launcher.ExtClassLoader(var0);
}
});
} catch (PrivilegedActionException var2) {

throw (IOException)var2.getException();
}
}

查看getExtDirs()方法:可以看到要加载的类文件都是位于ext文件夹下的。

private static File[] getExtDirs() { 

String var0 = System.getProperty("java.ext.dirs");
File[] var1;
if (var0 != null) {

StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
int var3 = var2.countTokens();
var1 = new File[var3];

for(int var4 = 0; var4 < var3; ++var4) {

var1[var4] = new File(var2.nextToken());
}
} else {

var1 = new File[0];
}

return var1;
}

继续看ExtClassLoader的构造方法:

  public ExtClassLoader(File[] var1) throws IOException { 

super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}

调用了父类的构造方法:
可以看到ExtClassLoader的parent赋值为null,因为引导类加载器是C++语言写的,没有实际java对象。

public URLClassLoader(URL[] urls, ClassLoader parent,
URLStreamHandlerFactory factory) {

super(parent);
// this is to make the stack depth consistent with 1.1
SecurityManager security = System.getSecurityManager();
if (security != null) {

security.checkCreateClassLoader();
}
acc = AccessController.getContext();
ucp = new URLClassPath(urls, factory, acc);
}

这样一个ExtClassLoader就创建好了。

AppClassLoader

AppClassLoader同样也是继承了URLClassLoader类
看下getAppClassLoader方法:

public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { 

final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() {

public Launcher.AppClassLoader run() {

URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
}

可以看到,getAppClassLoader主要加载工程classPath下的类文件。
继续看getAppClassLoader构造方法:

AppClassLoader(URL[] var1, ClassLoader var2) { 

super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}

从一开始的Launcher构造方法中可以看到参数var2就是先初始化的extClassLoader。
同样调用了父类URLClassLoader的构造,将extClassLoader设置为parent,所以appClassLoader的parent是extClassLoader。

由此三个主要类加载器之间的关系弄清楚了,各自要加载的范围也弄清楚。我们再看看自定义类加载器的实现。

自定义类加载器

自定义类加载器要继承ClassLoader方法,只需要重写findClass方法就行了:

package classload;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
/** * @author zhw * @description * @date 2021-07-15 14:36 */
public class MyClassLoader extends ClassLoader{


@Override
protected Class findClass(String name) throws ClassNotFoundException {

File file = new File("C:/Users/hiwei/Desktop/hiwei/test/Person.class");
try{

byte[] bytes = getClassBytes(file);
//defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
Class c = defineClass(name, bytes, 0, bytes.length);
return c;
} catch (Exception e) {

e.printStackTrace();
}
return super.findClass(name);
}

private byte[] getClassBytes(File file) throws Exception
{

FileInputStream inputStream = new FileInputStream(file);//原始输入流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1 ) {

baos.write(buffer, 0, len);
}
baos.flush();
return baos.toByteArray();
}
}

关于自定义类加载器的parent是谁,可以查看:

    protected ClassLoader() { 

this(checkCreateClassLoader(), getSystemClassLoader());
}

继续看getSystemClassLoader():

public static ClassLoader getSystemClassLoader() { 

initSystemClassLoader();
if (scl == null) {

return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {

checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}

private static synchronized void initSystemClassLoader() {

if (!sclSet) {

if (scl != null)
throw new IllegalStateException("recursive invocation");
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {

Throwable oops = null;
scl = l.getClassLoader();

}
sclSet = true;
}
}

public ClassLoader getClassLoader() {

return this.loader;
}

返回的是this.loader。上面已经知道loader就是AppClassLoader。所以自定义类加载器的默认parent就是AppClassLoader。

双亲委派

在类加载流程中,首先调用的是Launcher.loader.loadClass()方法。

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就是默认的类加载器:即AppClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {

throw new InternalError("Could not create application class loader", var9);
}
//设置默认classLoader
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
}

loader就是AppClassLoader。所以继续看AppClassLoader.loadClass方法:

public Class loadClass(String var1, boolean var2) throws ClassNotFoundException { 

int var3 = var1.lastIndexOf(46);
if (var3 != -1) {

SecurityManager var4 = System.getSecurityManager();
if (var4 != null) {

var4.checkPackageAccess(var1.substring(0, var3));
}
}

if (this.ucp.knownToNotExist(var1)) {

Class var5 = this.findLoadedClass(var1);
if (var5 != null) {

if (var2) {

this.resolveClass(var5);
}

return var5;
} else {

throw new ClassNotFoundException(var1);
}
} else {

//调用父类的loadClass方法
return super.loadClass(var1, var2);
}
}

继续看super.loadClass(var1, var2):双亲委派机制的核心代码来了

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 {

//如果父加载器不为null,则交给父加载器加载。
if (parent != null) {

c = parent.loadClass(name, false);
} else {
//如果父加载器为null,则交给引导类加载器加载。
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;
}
}

看完上面的代码后,是不是觉得双亲委派机制的实现很简单?


双亲委派的作用:

沙箱安全,保证JVM核心代码不被用户自定义类覆盖。

保证了类加载的唯一性。

如何打破双亲委派?

看双亲委派机制的源码,可以看到主要实现实在loadClass方法中,那么,只需要重写loadClass(String name, boolean resolve)方法即可:

package classload;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
/** * @author zhw * @description * @date 2021-07-15 14:36 */
public class MyClassLoader extends ClassLoader{


@Override
protected Class findClass(String name) throws ClassNotFoundException {

File file = new File("C:/Users/hiwei/Desktop/hiwei/test/Person.class");
try{

byte[] bytes = getClassBytes(file);
//defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
Class c = defineClass(name, bytes, 0, bytes.length);
return c;
} catch (Exception e) {

e.printStackTrace();
}
return super.findClass(name);
}

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("hiwei.test.Person".equals(name)){

c = findClass(name);
}else{

c = getParent().loadClass(name);
}

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;
}
}

private byte[] getClassBytes(File file) throws Exception
{

FileInputStream inputStream = new FileInputStream(file);//原始输入流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1 ) {

baos.write(buffer, 0, len);
}
baos.flush();
return baos.toByteArray();
}
}

测试类:

package classload;
/** * @author zhw * @description * @date 2021-07-15 15:09 */
public class ClassLoadTest {

public static void main(String[] args) throws Exception {

MyClassLoader myClassLoader = new MyClassLoader();
Class clazz = Class.forName("hiwei.test.Person", true, myClassLoader);
Object o = clazz.newInstance();
System.out.println(o.toString());
System.out.println(clazz.getClassLoader());
}
}

测试:
目标文件夹和classPath都存在Person.class

测试一:
结果:使用自定义加载器加载。

测试二:不覆盖loadClass方法。
结果:使用AppClassLoader

破坏双亲委派的应用

tomcat破环双亲委派


在tomcat中不同的应用可能依赖同一个jar的不同版本,如果共用一个类加载器,会导致无法进行环境隔离。所以tomcat自定义类加载器,每个应用都有自己的类加载器,负责加载自己应用下的类,打破了双亲委派机制,不在让父加载器先加载。

源码分析

tomcat的Bootstrap.initClassLoaders()方法中会初始化tomcat核心类的类加载器:

	private void initClassLoaders() { 

try {

commonLoader = createClassLoader("common", null);
if( commonLoader == null ) {

// no config file, default to this loader - we might be in a 'single' env.
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader);
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {

handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}

这三个类加载器并未破坏双亲委派模型,这三个都是URLClassLoader的实例。
真正破坏双亲委派模型的是WebappClassLoader类加载器,WebappClassLoader继承了WebappClassLoaderBase,而WebappClassLoaderBase重写了loadClass方法:

@Override
//todo 此处破坏了双亲委派模型
public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {


synchronized (getClassLoadingLock(name)) {

if (log.isDebugEnabled())
log.debug("loadClass(" + name + ", " + resolve + ")");
Class clazz = null;

// Log access to stopped class loader
checkStateForClassLoading(name);

// (0) Check our previously loaded local class cache
clazz = findLoadedClass0(name);
if (clazz != null) {

if (log.isDebugEnabled())
log.debug(" Returning class from cache");
if (resolve)
resolveClass(clazz);
return clazz;
}
//省略,,,,
}

可以看到,重写的loadClass方法破坏了双亲委派模型。

JDBC破坏双亲委派

原生的JDBC中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。例如,MySQL的jar中的Driver类具体实现的。
以以下版本为例:


mysql
mysql-connector-java
8.0.25

Driver实现类:

public class Driver extends NonRegisteringDriver implements java.sql.Driver { 

static {

try {

java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {

throw new RuntimeException("Can't register driver!");
}
}

可以看到,使用了DriverManager类。在DriverManager类中有静态代码块:

	static { 

loadInitialDrivers();
println("JDBC DriverManager initialized");
}

继续看loadInitialDrivers()

private static void loadInitialDrivers() { 

String drivers;
try {

drivers = AccessController.doPrivileged(new PrivilegedAction() {

public String run() {

return System.getProperty("jdbc.drivers");
}
});
} catch (Exception ex) {

drivers = null;
}
AccessController.doPrivileged(new PrivilegedAction() {

public Void run() {


ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
Iterator driversIterator = loadedDrivers.iterator();
try{

while(driversIterator.hasNext()) {

driversIterator.next();
}
} catch(Throwable t) {

// Do nothing
}
return null;
}
});

println("DriverManager.initialize: jdbc.drivers = " + drivers);

if (drivers == null || drivers.equals("")) {

return;
}
String[] driversList = drivers.split(":");
println("number of Drivers:" + driversList.length);
for (String aDriver : driversList) {

try {

println("DriverManager.Initialize: loading " + aDriver);
Class.forName(aDriver, true,
ClassLoader.getSystemClassLoader());
} catch (Exception ex) {

println("DriverManager.Initialize: load failed: " + ex);
}
}
}

看下面方法:

 ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
public static  ServiceLoader load(Class service) { 

ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}

使用了当前线程的classLoader。

	private ServiceLoader(Class svc, ClassLoader cl) { 

service = Objects.requireNonNull(svc, "Service interface cannot be null");
loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
reload();
}

回到loadInitialDrivers()方法,继续往下看:

AccessController.doPrivileged(new PrivilegedAction() { 

public Void run() {


ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
//加载Driver.class
Iterator driversIterator = loadedDrivers.iterator();
try{

while(driversIterator.hasNext()) {

driversIterator.next();
}
} catch(Throwable t) {

// Do nothing
}
return null;
}
});

进入loadedDrivers.iterator():

public Iterator iterator() { 

return new Iterator() {


Iterator> knownProviders
= providers.entrySet().iterator();
public boolean hasNext() {

if (knownProviders.hasNext())
return true;
return lookupIterator.hasNext();
}
public S next() {

if (knownProviders.hasNext())
return knownProviders.next().getValue();
return lookupIterator.next();
}
public void remove() {

throw new UnsupportedOperationException();
}
};
}

可以看到返回了一个重写了hasNext()和next()方法的匿名Iterator类。

try{ 

while(driversIterator.hasNext()) {

driversIterator.next();
}
}

在这里调用的都是重写方法。
由调用关系,最终可以看到下面的方法:

		private boolean hasNextService() { 

if (nextName != null) {

return true;
}
if (configs == null) {

try {

String fullName = PREFIX + service.getName();
if (loader == null)
//找到Driver.calss
configs = ClassLoader.getSystemResources(fullName);
else
configs = loader.getResources(fullName);
} catch (IOException x) {

fail(service, "Error locating configuration files", x);
}
}
while ((pending == null) || !pending.hasNext()) {

if (!configs.hasMoreElements()) {

return false;
}
pending = parse(service, configs.nextElement());
}
nextName = pending.next();
return true;
}

private S nextService() {

if (!hasNextService())
throw new NoSuchElementException();
String cn = nextName;
nextName = null;
Class c = null;
try {

//加载Driver.calss
c = Class.forName(cn, false, loader);
} catch (ClassNotFoundException x) {

fail(service,
"Provider " + cn + " not found");
}
if (!service.isAssignableFrom(c)) {

fail(service,
"Provider " + cn + " not a subtype");
}
try {

S p = service.cast(c.newInstance());
providers.put(cn, p);
return p;
} catch (Throwable x) {

fail(service,
"Provider " + cn + " could not be instantiated",
x);
}
throw new Error(); // This cannot happen
}

可以看到,Driver.class是在hasNextService()中取到,nextService()中加载的:

c = Class.forName(cn, false, loader);

这里的类加载器loader就是上面的

ClassLoader cl = Thread.currentThread().getContextClassLoader();

现在真相大白了,在使用spi机制时,会使用当前线程的类加载器加载”META-INF/services/”下面的Driver.class。
在双亲委派模型下,类的加载是由下至上委托的,jdk无法加载其它文件夹下的类文件。但是在jdbc中,Driver要由供应商实现,所以需要进行加载,在spi使用方法中,使用线程上下文类加载器加载指定路径下的Driver.class文件,解决了这个问题。
JDBC破坏双亲委派的实现是使用父加载器加载指定路径下的class文件。

编程小号
上一篇 2025-03-16 18:01
下一篇 2025-01-26 10:33

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/hz/138132.html