Tomcat 类加载器

开篇

 这是一篇尝试讲解清楚Tomcat的类加载器的文章,估摸着能讲清楚六成左右,待后续再理理思路。

文末有彩蛋,可以直接翻到文章末尾。


Tomcat 类加载器概览

Tomcat类加载器

说明:

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



Tomcat类关系图

说明:

    1. Common、Catalina、Shared类加载器继承自URLCLassLoader。
    1. WebappClassLoader继承WebappClassLoaderBase继承URLCLassLoader。
  • 3、JVM自带的ExtClassLoader也是URLClassLoader的子类。
  • 4、JVM自带的AppClassLoader也是URLClassLoader的子类。


Tomcat 各类ClassLoader初始化

Common&Catalina&Shared ClassLoader

    1. Bootstrap.init()初始化Tomcat的类加载器。
    1. Bootstrap的initClassLoaders()初始化Common、Catalina、Shared的类加载器。
    1. Common ClassLoader的父加载器是AppClassLoader。
    1. Catalina ClassLoader = Common ClassLoader。
    1. 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创建过程

    1. CatalinaProperties的getProperty方法加载配置conf/Catalina/catalina.properties。
    1. catalina.properties的配置文件内容中:
      common.loader="${catalina.base}/lib","\${catalina.base}/lib/.jar","${catalina.home}/lib","\${catalina.home}/lib/.jar"。
    1. catalina.properties的配置文件内容中:server.loader=,返回common classLoader。
    1. 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配置解析

    1. 解析tomcat目录下的conf/Catalina/catalina.properties配置文件。
    1. server.loader和shared.loader的值为空
    1. common.loader="&dollar;{catalina.base}/lib","\${catalina.base}/lib/.jar","&dollar;{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工厂

    1. 加载repositories对应的jar目录
    1. new URLClassLoader(array)创建没传parent的ClassLoader,这种情况下父加载器是AppClassLoader。
    1. 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的实现

URLClassLoader

说明:

    1. URLClassLoder是JVM当中的实现方式。
    1. URLClassLoder继承SecureClassLoader。
    1. 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

    1. ClassLoader()方法使用getSystemClassLoader作为parent。
    1. getSystemClassLoader()返回sun.misc.Launcher.getLauncher().getClassLoader()。
    1. 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

    1. 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设置

    1. 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设置

    1. StandardEingine的parentClassLoder设置为Catalina当中的parentClassLoader。
    1. Catalina当中的parentClassLoader是sharedLoader。
    1. StandardEingine通过如下方式注入parentClassLoader: digester.addRule("Server/Service/Engine,
  1. SetParentClassLoaderRule(parentClassLoader));
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设置

    1. StandardHost通过StandardEngine的getParentClassLoader获取ClassLoader。
    1. StandardHost的setParentClassLoader注入StandardEngine的parentClassLoader。
    1. 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设置

    1. StandardContext通过parent.getParentClassLoader()返回parentClassLoader。
    1. StandardContext的parent是StandardHost对象。
    1. 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

    1. WebappClassLoader继承自WebappClassLoaderBase。
    1. 核心实现在于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

    1. 调用 findLocaledClass0 从 resourceEntries 中判断 class 是否已经加载 OK。
    1. 调用 findLoadedClass(内部调用一个 native 方法) 直接查看对应的 WebappClassLoader 是否已经加载过。
    1. 调用 binaryNameToPath 判断是否 当前 class 是属于 J2SE 范围中的, 若是的则直接通过 ExtClassLoader, BootstrapClassLoader 进行加载 (这里是双亲委派)。
    1. 在设置 JVM 权限校验的情况下, 调用 securityManager 来进行权限的校验(当前类是否有权限加载这个类, 默认的权限配置文件是 ${catalina.base}/conf/catalina.policy)。
    1. 判断是否设置了双亲委派机制 或 当前 WebappClassLoader 是否能加载这个 class (通过 filter(name) 来决定), 将最终的值赋值给 delegateLoad。
    1. 根据上一步中的 delegateLoad 来决定是否用 WebappClassloader.parent(也就是 sharedClassLoader) 来进行加载, 若加载成功, 则直接返回。
    1. 上一步若未加载成功, 则调用 WebappClassloader.findClass(name) 来进行加载。
    1. 若上一还是没有加载成功, 则通过 parent 调用 Class.forName 来进行加载。
    1. 若还没加载成功的话, 那就直接抛异常。

加载过程

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)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值