阅读 103

Tomcat9的类加载器

Tomcat为了提高系统的灵活性,设计ClassLoader的总体结构, 引入了common、cataina、shared三个公共的classloader。tomcat/conf目录的catalina.properties中有common.loader、server.loader、shared.loader的配置项,这分别对应着commonLoader、catalinaLoader和sharedLoader。默认情况下,serverl.loader和shared.loader的配置是空的,这意味着此两者在运行时和commonLoader相同。Tomcat可以通过catalina.properties的common、server和shared设置,为webapp提供公用类库。使一些公用的、不需要与webapp放在一起的设置信息单独保存,在更新webapp的war的时候无需更改webapp的设置

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);
    }
}复制代码


启动类加载器、扩展类加载器、应用程序类加载器这三个类加载器属于JDK级别的加载器,他们是唯一的,我们一般不会对其做任何更改。Tomcat的common类加载器的父类加载器是应用类程序加载器,common负责加载$CATALINA_BASE\lib、$CATALINA_HOME\lib两个目录下所有的 .class文件与 .jar文件。

tomcat为每个app分配了一个WebappClassLoader,这样来避免多个app会加载相同jar包的问题,WebappClassLoader的parent是org.apache.catalina.loader.CatalinaLoader,这样多个app就能共享tomcat的类库。当tomcat为每个请求启动线程后,会将该线程的classloader设为WebappClassLoader,如果不设置,那么新建的线程默认为父线程的classloader。该WebappClassLoader的delegate属性为false,它使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器,这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。在实现上,WebAppClassLoader打破了系统默认规则,它打破了java.lang.ClassLoader逻辑中的“双亲委派”模式,提供了一套自定义的类加载流程。默认情况下,对于一个未加载过的类,WebappClassLoader会先让系统加载java.lang.Object等Java本身的基础类,如果不是基础类则优先在当前Web app范围内查找并加载,如果没加载到,再交给common loader走标准的双亲委派模式加载。

Java的ClassLoader机制有parent-first的机制,而这种机制是在loadClass方法保证的,一般情况下,我们只需要重写findClass()就好了,而对于WebAppClassLoader,通过查看源代码,我们发现loadClass()和findClass()都进行了重写。而针对每个webapp也就是context(对应Tomcat的StandardContext类),都有自己的WebappClassLoader来加载每个应用自己的类。为了支持和分隔多个web应用,使用了WebappClassLoaderWebappClassLoader创造了各个Webapp空间


Bootstrap ---> System ---> /WEB-INF/classes ---> /WEB-INF/lib/*.jar ---> Common ---> Server ---> Shared 

查看org.apache.catalina.core.StandardContext#startInternal,StandardContext启动的时候,会创建webapploader,并将getParentClassLoader方法返回的结果(这里返回的其实就是sharedLoader)赋值给自己的parentClassLoader变量,接着又会调用到Webapploader的start方法,因为WebappLoader符合Tomcat组件生命周期管理的模板方法模式,因此会调用到它的startInternal方法。 

org.apache.catalina.loader.WebappLoader#startInternal org.apache.catalina.loader.WebappLoader#createClassLoader 

org.apache.catalina.loader.WebappClassLoaderBase#start()

@Override
public void start() throws LifecycleException {

    state = LifecycleState.STARTING_PREP;

    WebResource classes = resources.getResource("/WEB-INF/classes");
    if (classes.isDirectory() && classes.canRead()) {
        localRepositories.add(classes.getURL());
    }
    WebResource[] jars = resources.listResources("/WEB-INF/lib");
    for (WebResource jar : jars) {
        if (jar.getName().endsWith(".jar") && jar.isFile() && jar.canRead()) {
            localRepositories.add(jar.getURL());
            jarModificationTimes.put(
                    jar.getName(), Long.valueOf(jar.getLastModified()));
        }
    }

    state = LifecycleState.STARTED;
}复制代码



在Java体系中,可以将系统分为以下三种类加载器:

启动类加载器(Bootstrap ClassLoader):加载对象是Java核心库,把一些核心的Java类加载进JVM中,这个加载器使用原生代码(C/C++)实现,并不是继承java.lang.ClassLoader,它是所有其他类加载器的最终父加载器,负责加载<JAVA_HOME>/jre/lib目录下JVM指定的类库。其实它属于JVM整体的一部分,JVM一启动就将这些指定的类加载到内存中,避免过多的I/O操作,提高系统的运行效率。启动类加载器无法被Java程序直接使用。它在JVM中唯一

扩展类加载器(Extension ClassLoader):加载的对象为Java的扩展库,即加载<JAVA_HOME>/jre/lib/ext目录里面的类。这个类由启动类加载器加载,但因为启动类加载器并非用Java实现,已经脱离了Java体系,所以如果尝试调用扩展类加载器的getParent()方法获取父加载器会得到null。然而,它的父类加载器是启动类加载器。它在JVM唯一

应用程序类加载器(Application ClassLoader): 亦叫系统类加载器(System ClassLoader),它负责加载用户类路径(CLASSPATH)指定的类库,如果程序没有自己定义类加载器,就默认使用应用程序类加载器。它也由启动类加载器加载,但它的父加载类被设置成了扩展类加载器。如果要使用这个加载器,可通过ClassLoader.getSystemClassLoader()获取。


类图:www.processon.com/view/link/5…

启动停止:www.processon.com/view/link/5…