开篇
这是一篇尝试讲解清楚Tomcat的类加载器的文章,估摸着能讲清楚六成左右,待后续再理理思路。
文末有彩蛋,可以直接翻到文章末尾。
Tomcat 类加载器概览

说明:
- BootstrapClassLoader : 系统类加载器,加载%JAVA_HOME%/lib目录下的jar
 
- ExtClassLoader : 扩展类加载器,加载%JAVA_HOME%/ext/lib目录下的jar
 
- AppClassLoader : 普通类加载器,加载CLASSPATH指定目录下的jar
 
- commonLoader : Tomcat 通用类加载器, 加载的资源可被 Tomcat 和 所有的 Web 应用程序共同获取
 
- catalinaLoader : Tomcat 类加载器, 加载的资源只能被 Tomcat 获取(但 所有 WebappClassLoader 不能获取到 catalinaLoader 加载的类)
 
- sharedLoader : Tomcat 各个Context的父加载器, 这个类是所有 WebappClassLoader 的父类, sharedLoader 所加载的类将被所有的 WebappClassLoader 共享获取
 
- 这个版本 (Tomcat 8.x.x) 中, 默认情况下 commonLoader = catalinaLoader = sharedLoader
 
- (PS: 为什么这样设计, 主要这样这样设计 ClassLoader 的层级后, WebAppClassLoader 就能直接访问 tomcat 的公共资源, 若需要tomcat 有些资源不让 WebappClassLoader 加载, 则直接在 ${catalina.base}/conf/catalina.properties 中的 server.loader 配置一下 加载路径就可以了)
 

说明:
- Common、Catalina、Shared类加载器继承自URLCLassLoader。
 
- WebappClassLoader继承WebappClassLoaderBase继承URLCLassLoader。
 
- 3、JVM自带的ExtClassLoader也是URLClassLoader的子类。 
- 4、JVM自带的AppClassLoader也是URLClassLoader的子类。 
Tomcat 各类ClassLoader初始化
Common&Catalina&Shared ClassLoader
- Bootstrap.init()初始化Tomcat的类加载器。
 
- Bootstrap的initClassLoaders()初始化Common、Catalina、Shared的类加载器。
 
- Common ClassLoader的父加载器是AppClassLoader。
 
- Catalina ClassLoader = Common ClassLoader。
 
- Shared ClassLoader = Common ClassLoader。
 
public final class Bootstrap {
    public static void main(String args[]) {
        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                // 初始化Bootstrap
                bootstrap.init();
            } catch (Throwable t) {
                handleThrowable(t);
                t.printStackTrace();
                return;
            }
            daemon = bootstrap;
        }
    }
    public void init() throws Exception {
        // 初始化Tomcat的类加载器
        initClassLoaders();
    }
    private void initClassLoaders() {
        try {
            // 创建commonLoader并且未指定父节点,默认为
            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并且指定parent为commonLoader
            catalinaLoader = createClassLoader("server", commonLoader);
            // 创建SharedLoader并且指定parent为commonLoader
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }
}
ClassLoader创建过程
- CatalinaProperties的getProperty方法加载配置conf/Catalina/catalina.properties。
 
- catalina.properties的配置文件内容中:
 common.loader="${catalina.base}/lib","${catalina.base}/lib/.jar","${catalina.home}/lib","${catalina.home}/lib/.jar"。
 
- catalina.properties的配置文件内容中:
- catalina.properties的配置文件内容中:server.loader=,返回common classLoader。
 
- catalina.properties的配置文件内容中:shared.loader=,返回common classLoader。
 
public final class Bootstrap {
    private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        
        // common.loader配置jar路径,负责加载${catalina.base}/lib和{catalina.home}/lib
        // server.loader和shared.loader配置路径为空,所以返回parent即common classloader
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;
        value = replace(value);
        List<Repository> repositories = new ArrayList<>();
        String[] repositoryPaths = getPaths(value);
        for (String repository : repositoryPaths) {
            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositories.add(
                        new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(
                        new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(
                        new Repository(repository, RepositoryType.DIR));
            }
        }
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }
}
catalina.properties配置解析
- 解析tomcat目录下的conf/Catalina/catalina.properties配置文件。
 
- server.loader和shared.loader的值为空
 
- common.loader="${catalina.base}/lib","${catalina.base}/lib/.jar","${catalina.home}/lib","${catalina.home}/lib/.jar"
 
public class CatalinaProperties {
    private static Properties properties = null;
    static {
        loadProperties();
    }
    public static String getProperty(String name) {
        return properties.getProperty(name);
    }
    private static void loadProperties() {
        InputStream is = null;
        if (is == null) {
            try {
                // conf/Catalina/catalina.properties
                // common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
                // server.loader=
                // shared.loader=
                File home = new File(Bootstrap.getCatalinaBase());
                File conf = new File(home, "conf");
                File propsFile = new File(conf, "catalina.properties");
                is = new FileInputStream(propsFile);
            } catch (Throwable t) {
            }
        }
        if (is != null) {
            try {
                properties = new Properties();
                properties.load(is);
            } catch (Throwable t) {
            } finally {
                try {
                    is.close();
                } catch (IOException ioe) {
                }
            }
        }
        Enumeration<?> enumeration = properties.propertyNames();
        while (enumeration.hasMoreElements()) {
            String name = (String) enumeration.nextElement();
            String value = properties.getProperty(name);
            if (value != null) {
                System.setProperty(name, value);
            }
        }
    }
}
ClassLoaderFactory的ClassLoader工厂
- 加载repositories对应的jar目录
 
- new URLClassLoader(array)创建没传parent的ClassLoader,这种情况下父加载器是AppClassLoader。
 
- new URLClassLoader(array, parent)创建以parent作为父加载器的ClassLoader。
 
public final class ClassLoaderFactory {
    public static ClassLoader createClassLoader(List<Repository> repositories,
                                                final ClassLoader parent)
        throws Exception {
        Set<URL> set = new LinkedHashSet<>();
        if (repositories != null) {
            for (Repository repository : repositories)  {
                if (repository.getType() == RepositoryType.URL) {
                    URL url = buildClassLoaderUrl(repository.getLocation());
                    set.add(url);
                } else if (repository.getType() == RepositoryType.DIR) {
                    File directory = new File(repository.getLocation());
                    URL url = buildClassLoaderUrl(directory);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.JAR) {
                    File file=new File(repository.getLocation());
                    file = file.getCanonicalFile();
                    URL url = buildClassLoaderUrl(file);
                    set.add(url);
                } else if (repository.getType() == RepositoryType.GLOB) {
                    File directory=new File(repository.getLocation());
                    directory = directory.getCanonicalFile();
                    String filenames[] = directory.list();
                    if (filenames == null) {
                        continue;
                    }
                    for (int j = 0; j < filenames.length; j++) {
                        String filename = filenames[j].toLowerCase(Locale.ENGLISH);
                        File file = new File(directory, filenames[j]);
                        file = file.getCanonicalFile();
                        URL url = buildClassLoaderUrl(file);
                        set.add(url);
                    }
                }
            }
        }
        final URL[] array = set.toArray(new URL[set.size()]);
        return AccessController.doPrivileged(
                new PrivilegedAction<URLClassLoader>() {
                    @Override
                    public URLClassLoader run() {
                        if (parent == null)
                            return new URLClassLoader(array);
                        else
                            return new URLClassLoader(array, parent);
                    }
                });
    }
}
URLClassLoader的实现

说明:
- URLClassLoder是JVM当中的实现方式。
 
- URLClassLoder继承SecureClassLoader。
 
- SecureClassLoader继承ClassLoader。
 
public class URLClassLoader extends SecureClassLoader implements Closeable {
    public URLClassLoader(URL[] urls) {
        super();
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        this.acc = AccessController.getContext();
        ucp = new URLClassPath(urls, acc);
    }
    public URLClassLoader(URL[] urls, ClassLoader parent) {
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        this.acc = AccessController.getContext();
        ucp = new URLClassPath(urls, acc);
    }
}
public class SecureClassLoader extends ClassLoader {
    protected SecureClassLoader() {
        super();
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        initialized = true;
    }
}
抽象类ClassLoader
- ClassLoader()方法使用getSystemClassLoader作为parent。
 
- getSystemClassLoader()返回sun.misc.Launcher.getLauncher().getClassLoader()。
 
- sun.misc.Launcher.getLauncher().getClassLoader()是。AppClassLoader对象
 
public abstract class ClassLoader {
    private final ClassLoader parent;
    private static ClassLoader scl;
    protected ClassLoader() {
        this(checkCreateClassLoader(), getSystemClassLoader());
    }
    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }
    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) {
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                }
               
            }
            sclSet = true;
        }
    }
}
Launcher
- Launcher.AppClassLoader.getAppClassLoader(var1)返回AppClassLoader对象。
 
public class Launcher {
    private static Launcher launcher = new Launcher();
    private ClassLoader loader;
    public static Launcher getLauncher() {
        return launcher;
    }
    public Launcher() {
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
        }
        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
        }
    }
}
Webapp Classloader
整个Class loader的传递过程如下,证明Webapp classLoader的parent节点。
StandardEngine通过xml解析获得了Catalina的class loader。
StandardHost通过xml解析获得了StandardEngine的class loader。
StandardContext通过getParentClassLoader获得了StandardHost的class loader。
设置Bootstrap的common、catalina、shared等Loader
Catalina的parent设置
- Bootstrap通过反射调用Catalina的setParentClassLoader设置Catalina的parentClassLoader为sharedLoader。
 
public final class Bootstrap {
    ClassLoader commonLoader = null;
    ClassLoader catalinaLoader = null;
    ClassLoader sharedLoader = null;
    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);
        }
    }
    public void init() throws Exception {
        initClassLoaders();
        Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.getConstructor().newInstance();
        // Set the shared extensions class loader
        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;
    }
}
StandardEingine的parent设置
- StandardEingine的parentClassLoder设置为Catalina当中的parentClassLoader。
 
- Catalina当中的parentClassLoader是sharedLoader。
 
- StandardEingine通过如下方式注入parentClassLoader: digester.addRule("Server/Service/Engine,
 new SetParentClassLoaderRule(parentClassLoader));
 
- StandardEingine通过如下方式注入parentClassLoader: digester.addRule("Server/Service/Engine,
public class Catalina {
    protected ClassLoader parentClassLoader =
        Catalina.class.getClassLoader();
    public void setParentClassLoader(ClassLoader parentClassLoader) {
        this.parentClassLoader = parentClassLoader;
    }
    public ClassLoader getParentClassLoader() {
        if (parentClassLoader != null) {
            return (parentClassLoader);
        }
        return ClassLoader.getSystemClassLoader();
    }
    // Engine的解析规则
    protected Digester createStartDigester() {
        digester.addRule("Server/Service/Engine",
                         new SetParentClassLoaderRule(parentClassLoader));
}
StandardHost的parent设置
- StandardHost通过StandardEngine的getParentClassLoader获取ClassLoader。
 
- StandardHost的setParentClassLoader注入StandardEngine的parentClassLoader。
 
- StandardHost的parentClassLoader为sharedLoader。
 
public class HostRuleSet extends RuleSetBase {
    public void addRuleInstances(Digester digester) {
        digester.addObjectCreate(prefix + "Host",
                                 "org.apache.catalina.core.StandardHost",
                                 "className");
        digester.addSetProperties(prefix + "Host");
        // Host的XML的解析规则
        digester.addRule(prefix + "Host",
                         new CopyParentClassLoaderRule());
    }
}
public class CopyParentClassLoaderRule extends Rule {
    public CopyParentClassLoaderRule() {
    }
    @Override
    public void begin(String namespace, String name, Attributes attributes)
        throws Exception {
        if (digester.getLogger().isDebugEnabled())
            digester.getLogger().debug("Copying parent class loader");
        Container child = (Container) digester.peek(0);
        Object parent = digester.peek(1);
        Method method =
            parent.getClass().getMethod("getParentClassLoader", new Class[0]);
        ClassLoader classLoader =
            (ClassLoader) method.invoke(parent, new Object[0]);
        child.setParentClassLoader(classLoader);
    }
}
StandardContext的parent设置
- StandardContext通过parent.getParentClassLoader()返回parentClassLoader。
 
- StandardContext的parent是StandardHost对象。
 
- StandardHost.getParentClassLoader()返回StandardHost的sharedLoader。
 
public class StandardContext extends ContainerBase
        implements Context, NotificationEmitter {
    public ClassLoader getParentClassLoader() {
        if (parentClassLoader != null)
            return (parentClassLoader);
        if (getPrivileged()) {
            return this.getClass().getClassLoader();
        } else if (parent != null) {
            // parent是StandardHost
            return (parent.getParentClassLoader());
        }
        return (ClassLoader.getSystemClassLoader());
    }
}
类加载过程双亲委派
WebappClassLoader
- WebappClassLoader继承自WebappClassLoaderBase。
 
- 核心实现在于WebappClassLoaderBase类。
 
public class WebappClassLoader extends WebappClassLoaderBase {
    public WebappClassLoader() {
        super();
    }
    public WebappClassLoader(ClassLoader parent) {
        super(parent);
    }
   @Override
    public WebappClassLoader copyWithoutTransformers() {
        WebappClassLoader result = new WebappClassLoader(getParent());
        super.copyStateWithoutTransformers(result);
        try {
            result.start();
        } catch (LifecycleException e) {
            throw new IllegalStateException(e);
        }
        return result;
    }
    @Override
    protected Object getClassLoadingLock(String className) {
        return this;
    }
}
WebappClassLoaderBase
- 调用 findLocaledClass0 从 resourceEntries 中判断 class 是否已经加载 OK。
 
- 调用 findLoadedClass(内部调用一个 native 方法) 直接查看对应的 WebappClassLoader 是否已经加载过。
 
- 调用 binaryNameToPath 判断是否 当前 class 是属于 J2SE 范围中的, 若是的则直接通过 ExtClassLoader, BootstrapClassLoader 进行加载 (这里是双亲委派)。
 
- 在设置 JVM 权限校验的情况下, 调用 securityManager 来进行权限的校验(当前类是否有权限加载这个类, 默认的权限配置文件是 ${catalina.base}/conf/catalina.policy)。
 
- 判断是否设置了双亲委派机制 或 当前 WebappClassLoader 是否能加载这个 class (通过 filter(name) 来决定), 将最终的值赋值给 delegateLoad。
 
- 根据上一步中的 delegateLoad 来决定是否用 WebappClassloader.parent(也就是 sharedClassLoader) 来进行加载, 若加载成功, 则直接返回。
 
- 上一步若未加载成功, 则调用 WebappClassloader.findClass(name) 来进行加载。
 
- 若上一还是没有加载成功, 则通过 parent 调用 Class.forName 来进行加载。
 
- 若还没加载成功的话, 那就直接抛异常。
 

public abstract class WebappClassLoaderBase extends URLClassLoader
        implements Lifecycle, InstrumentableClassLoader, WebappProperties, PermissionCheck {
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return (loadClass(name, false));
    }
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            Class<?> clazz = null;
            // Log access to stopped class loader
            checkStateForClassLoading(name);
            //首先调用findLoaderClass0() 方法检查WebappClassLoader中是否加载过此类
            // WebappClassLoader 加载过的类都存放在 resourceEntries 缓存中。
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
            // 调用 findLoadedClass(内部调用一个 native 方法) 
            // 直接查看对应的 WebappClassLoader 是否已经加载过
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
            // 调用 binaryNameToPath 判断是否 当前 class 是属于 J2SE 范围中的, 
            // 若是的则直接通过 ExtClassLoader, BootstrapClassLoader 进行加载     
            // (这里是双亲委派)
            String resourceName = binaryNameToPath(name, false);
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                tryLoadingFromJavaseLoader = (javaseLoader.getResource(resourceName) != null);
            } catch (Throwable t) {
                tryLoadingFromJavaseLoader = true;
            }
            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
            // 判断是否需要委托给父类加载器进行加载,
            // delegate属性默认为false,那么delegatedLoad的值就取决于filter的返回值了
            // filter中是优先加载tomcat的lib下的class文件
            // filter方法中根据包名来判断是否需要进行委托加载,
            // 默认情况下会返回false.因此delegatedLoad为false
            boolean delegateLoad = delegate || filter(name, true);
            // 因为delegatedLoad为false,那么此时不会委托父加载器去加载,
            // 这里其实是没有遵循parent-first的加载机制。
            if (delegateLoad) {
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
            // 调用findClass方法在webapp级别进行加载
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from local repository");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
            // 如果还是没有加载到类,并且不采用委托机制的话,则通过父类加载器去加载
            if (!delegateLoad) {
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return (clazz);
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }
        throw new ClassNotFoundException(name);
    }
    protected Class<?> findLoadedClass0(String name) {
        String path = binaryNameToPath(name, true);
        ResourceEntry entry = resourceEntries.get(path);
        if (entry != null) {
            return entry.loadedClass;
        }
        return null;
    }
    protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }
    private native final Class<?> findLoadedClass0(String name);
}
参考文章
违反ClassLoader双亲委派机制三部曲第二部——Tomcat类加载机制
Tomcat 源码分析 WebappClassLoader 分析 (基于8.0.5)
招聘信息
【招贤纳士】
欢迎热爱技术、热爱生活的你和我成为同事,和贝贝共同成长。
贝贝集团诚招算法、大数据、BI、Java、PHP、android、iOS、测试、运维、DBA等人才,有意可投递zhi.wang@beibei.com。
贝贝集团创建于2011年,旗下拥有贝贝网、贝店、贝贷等平台,致力于成为全球领先的家庭消费平台。
贝贝创始团队来自阿里巴巴,先后获得IDG资本、高榕资本、今日资本、新天域资本、北极光等数亿美金的风险投资。
公司地址:杭州市江干区普盛巷9号东谷创业园(上下班有多趟班车)









