tomcat类加载器 - 969251639/study GitHub Wiki

tomcat从设计开始就支持多个context一起运行,那么多个context运行又如何保证它们之间的jar互不影响,而且也能共用tomcat的公共jar包呢?为此tomcat设计了自己的类加载模式

在tomcat中,最重要的一个类加载器时Common加载器,它的父类时jdk应用程序类加载器,负责加载$CATALINA_BASE/lib、$CATALINA_HOME/lib两个目录下的所有class和jar包

在tomcat的启动类Bootstrap中,会调用Bootstrap的初始化方法,初始化方法中会创建好一套tomcat自己的类加载器

public final class Bootstrap {
    ...
    public static void main(String args[]) {
           ...
            Bootstrap bootstrap = new Bootstrap();
            ...
                bootstrap.init();
    ...
    }

    public void init() throws Exception {

        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);

        // Load our startup class and call its process() method
        if (log.isDebugEnabled())
            log.debug("Loading startup class");
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();

        // Set the shared extensions class loader
        if (log.isDebugEnabled())
            log.debug("Setting startup class properties");
        String methodName = "setParentClassLoader";
        Class<?> paramTypes[] = new Class[1];
        paramTypes[0] = Class.forName("java.lang.ClassLoader");
        Object paramValues[] = new Object[1];
        paramValues[0] = sharedLoader;
        Method method =
            startupInstance.getClass().getMethod(methodName, paramTypes);
        method.invoke(startupInstance, paramValues);

        catalinaDaemon = startupInstance;

    }
    ...
    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);
        }
    }
    ...
    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;
        ...
    }
}

上面的代码就创建了如下的类加载器结构

需要注意的是catalina(server)加载器和shared加载器在catalina.properties中都是空的,所以它们都是指向common加载器,如果需要改变它,则需要配置catalina.properties

common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar

server.loader=

shared.loader=

另外tomcat还有一个很重要的类加载器是webapp class loader,主要用于加载每个context下面的web应用程序,它的父类是common类加载器,一个webapp应用程序对应一个webapp class loader。这样就能很好的隔离tomcat多个webapp应用程序互不影响,而Tomcat的common类加载器又能共用的效果。

因为webapp class loader是在context级别的加载器那么在创建context的时候也肯定会去创建自己的webapp class loader

public class StandardContext extends ContainerBase
        implements Context, NotificationEmitter {
    protected synchronized void startInternal() throws LifecycleException {
        ...
        if (getLoader() == null) {
            //getParentClassLoader返回的时common类加载器
            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }
        ...
                Loader loader = getLoader();
                if ((loader != null) && (loader instanceof Lifecycle))
                    ((Lifecycle) loader).start();
        ...
    }
}

public class WebappLoader extends LifecycleMBeanBase
    implements Loader, PropertyChangeListener {
    ...
    private String loaderClass = WebappClassLoader.class.getName();
    ...
    @Override
    protected void startInternal() throws LifecycleException {
         ...
         classLoader = createClassLoader();
         classLoader.setResources(context.getResources());
         classLoader.setDelegate(this.delegate);
         ...
         ((Lifecycle) classLoader).start();
         ...
    }

    private WebappClassLoaderBase createClassLoader()
        throws Exception {

        Class<?> clazz = Class.forName(loaderClass);
        WebappClassLoaderBase classLoader = null;

        if (parentClassLoader == null) {
            parentClassLoader = context.getParentClassLoader();
        }
        Class<?>[] argTypes = { ClassLoader.class };
        Object[] args = { parentClassLoader };
        Constructor<?> constr = clazz.getConstructor(argTypes);
        classLoader = (WebappClassLoaderBase) constr.newInstance(args);

        return classLoader;
    }
}

从上面的代码可以看到在context中创建了WebappLoader,而WebappLoader在内部又去创建了WebappClassLoader去真正加载jar和class

//WebappClassLoader的父类
public abstract class WebappClassLoaderBase extends URLClassLoader
        implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
    //对应WebappLoader.startInternal方法中的((Lifecycle) classLoader).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()));
            }
        }
        ...
    }
}

可以看到WebappLoader是委托WebappClassLoader去加载自己webapp下面的/WEB-INF/classes和/WEB-INF/lib下的jar包和class文件