当前位置: 首页>后端>正文

tomcat是如何破坏双亲委派的

一)类加载

//这里是从think in jvm第三部分第七章里面引用的加了一些自己的话,回顾一下类加载,为理解双亲委派破坏做一下复习

在java当中要使用一个类,需要事先被classloader加载,触发类初始化clinit的动作有

1)new getstatic putstatic invokestatic,四条指令分别对应了new对象,读取、设置静态变量,调用静态方法

总结起来就是使用或创建一个类的时候,创建包括newInstance,这里我划分为A类型

2)反射,使用的方法来获取类对象,其实在forName这里就有了加载动作,后期使用时的初始化和上面的创建是一样,和1)可以划分为一类还是A类型

3)初始化一个类先初始化其父类,这里用继承的思想不难理解,a extends b,a就是一个b,理解起来不难,要看代码估计要去看jvm源码了,这里分为B类型

4)虚拟机启动调用主类的main方法,还是A类型

5)jdk1.7中使用MethodHandle,lookup.findStatic后调用方法会初始化(参考MethodHandle注释里面的示例),暂时理解未A类

总体看下来就是三种情况:创建,调用静态方法,子类初始化

类加载器有基本的层级关系

System.out.println(System.getProperty("sun.boot.class.path"));//boot,jre的包

java.ext.dirs//ext,扩展

java.class.path//AppClassLoader,当前应用的classes和jar包

系统执行了boot后才会执行ext再执行app,执行的按双亲委派搜索,父加载器加载过的就不会再加载了

可以看DriverManager类进行进一步了解了,由这里可以引出spi的学习,暂不拉开

二)tomcat

整理了一下关于tomcat的类加载

从6.x开始的tomcat版本,已经没有了common/share/server的包存放路径

为了将同一个tomcat webapp下的不同应用隔离开,所以需要不同的类加载器,每一个应用对应到的tomcat组件就是context

在StandardHost的事件监听器hostConfig中的deployDirectories(或者其他两种解析方式)方法开启了一个单线程的线程池,用于多个应用发布不同的context

**1.webappLoader **

针对每个应用有一个webappLoader加载器,其classloader父类继承前面的加载器,所以查看代码

org.apache.catalina.core.StandardContext.startInternal()

if (getLoader() == null) {

//点进去会看到,在初次启动时,返回host的加载器

WebappLoader webappLoader = new WebappLoader(getParentClassLoader());

webappLoader.setDelegate(getDelegate());

//这个地方将新建的webappLoader传入,在restart情况下会替换原有的loader,同时将原有的loader停掉

setLoader(webappLoader);

}

2.bindThread()方法

再往下看会看到这么一段有趣的代码,刚好可以理解一下破坏双亲体系的一种方式

ClassLoader oldCCL = bindThread();

...loader.start();//启动刚刚建好的webappLoader

unbindThread(oldCCL);

oldCCL = bindThread();

...others.start()

unbindThread(oldCCL);

这个方法调用了两次,看bindThread注释:Bind current thread, both for CL purposes and for JNDI ENC support

方法将旧线程记录下来返回,并且将当前线程的上下文加载器设置为webappLoader的类加载器

再看unbindThread是将记录的加载器设置回去

所以说多个应用下的ContextClassLoader在最终都是同一个

protected ClassLoader bindThread() {

ClassLoader oldContextClassLoader =

Thread.currentThread().getContextClassLoader();

if (getResources() == null)

return oldContextClassLoader;

if (getLoader() != null && getLoader().getClassLoader() != null) {

//首次调用时getLoader().getClassLoader()应该是空的,并未运行本段代码,在后续的loader.start()中创建了新的classloader,再进来这段方法把刚才创建的classloader

Thread.currentThread().setContextClassLoader

(getLoader().getClassLoader());

}

DirContextURLStreamHandler.bindThread(getResources());

if (isUseNaming()) {

try {

ContextBindings.bindThread(this, this);

} catch (NamingException e) {

// Silent catch, as this is a normal case during the early

// startup stages

}

}

return oldContextClassLoader;

}

protected void unbindThread(ClassLoader oldContextClassLoader) {

if (isUseNaming()) {

ContextBindings.unbindThread(this, this);

}

DirContextURLStreamHandler.unbindThread();

Thread.currentThread().setContextClassLoader(oldContextClassLoader);

}

3.bind了什么

先从Thread.currentThread().getContextClassLoader()开始看起

@CallerSensitive

public ClassLoader getContextClassLoader() {

if (contextClassLoader == null)

return null;

...permission check code

return contextClassLoader;

}

方法返回的是当前线程的contextClassLoader,然后我们寻找其初始化的代码

private void init(ThreadGroup g, Runnable target, String name,

                      long stackSize, AccessControlContext acc) {

Thread parent = currentThread();

...

this.contextClassLoader = parent.getContextClassLoader();   

}

顺带还看到了线程之间的关系,线程创建时的线程作为被创建线程的parent,同时用了同一个contextClassLoader,如果没有调用setContextClassLoader,那么我们整个代码过程中的contextClassLoader将会是主线程的类加载器,也就是AppClassLoader

回想到tomcat启动的时候,在Bootstrap类中,有一个init方法

public void init() throws Exception {

// Set Catalina path

setCatalinaHome();

setCatalinaBase();

initClassLoaders();

Thread.currentThread().setContextClassLoader(catalinaLoader);

...

}

在方法initClassLoaders中看到catalinaLoader = createClassLoader("server", commonLoader);

createClassLoader中获取了server.loader这个系统参数的值(这个值是catalina包和启动脚本传的值),解析出来是tomcat的lib目录,将lib目录添加进去,然后创建了一个URLClassLoader,这个loader可以解析jar包,且父加载器为appClassloader

了解了ContextClassLoader后回到StandardContext处,webappLoader构造方法追踪后发现其parentClassLoader是系统的appClassloader,且其classLoader属性为一个WebappClassLoaderBase,webappLoader.classLoader解析加载的资源包括当前应用的classes,WEB-INF/lib目录,以及一个MANIFEST.MF文件

到这里基本上就明白了,每一个类加载器都有自己加载的范围,我们的tomcat由于需要加载各个目录下的jar和classes,而路径是启动前不知道的,所以引入了contextClassLoader来动态加载需要的资源,且context的父加载器为系统最开始的appClassloader能够获取公用的java类,不影响整体,由此将不同的应用隔离开来,brilliant!

三)正题

为什么会说这里违反了双亲委派,WebappClassLoaderBase#loadClass重写了ClassLoader的loadClass方法

查询类是否加载并没有直接交给父类,而是按照

// (0) Check our previously loaded local class cache

clazz = findLoadedClass0(name);

// (0.1) Check our previously loaded class cache

clazz = findLoadedClass(name);

前面两个是查看是否已经加载过

clazz = j2seClassLoader.loadClass(name);//这个是用extloader检查一些通用类,防止加载J2SE classes(加载类是通过全路径判断的,防止应用中重新定义一些通用类,如String这种)

boolean delegateLoad = delegate || filter(name);//filter(name)判断类的包路径是否要交给父类加载

clazz = Class.forName(name, false, parent);//如果判断delegateLoad=true就由父类加载,通过debug几个项目发现delegateLoad=false始终是false

//****findClass****自己加载类,假如自己的资源中没有这个类,才会到下一步去让父类加载,说了半天就这两行代码

clazz = findClass(name);

clazz = Class.forName(name, false, parent);

bind和unbind之间代码应该都是针对非应用下的操作,防止有类被覆盖了

还有一点,类加载过程中会根据带\的全路径到资源列表中查找类.class文件,假如这个类在jar包中,会调用获取对应的文件(jar包在系统中实际是一个一个的Url),如果资源不在自己这里就会找不到

举个栗子

在eclipse的开发过程中,使用到tomcat的servlet.jar包的时候,pom中会带上这个包,在发布时假如不将这个包除去,在tomcat启动时会加载一次servlet包,随后在应用处会再尝试加载一次,就有一个冲突的错误出现

附上一个图

tomcat是如何破坏双亲委派的,第1张

https://www.xamrdz.com/backend/3vb1934950.html

相关文章: