Tomcat WebappClassloader 原理分析--上篇

Tomcat 的classload 是tomcat能同时部署多个应用,而每个应用之家不冲突的核心技术,所以要分析tomcat的classload机制,必须要知道他的classload是怎么创建的,在哪里使用的,具体的加载规则是怎么实现的,打算分上下两篇来完成,一篇太长,效果不好。

Tomcat 怎么创建classload

tomcat 在启动的时候会为为每个webapp 创建一个StandardContext,
StandardContext在初始化的时候一个WebappClassLoad,代码如下:

 if (getLoader() == null) {
        WebappLoader webappLoader = new 
        WebappLoader(getParentClassLoader());
        webappLoader.setDelegate(getDelegate());
        //下面会用到,用来创建真正的WebappClassLoaderBase
        setLoader(webappLoader);
 }

WebappLoader 只是一个代理,创建完成了,通过start方法来初始化类加载器,代码如下:

    Loader loader = getLoader();
    if (loader instanceof Lifecycle) {
          ((Lifecycle) loader).start();
    }

WebappLoader的start方法,里面是调用了WebappLoader的startInternal方法,代码如下:

 try {
        //这里是tomcat创建他的WebappClassLoaderBase的地方
        classLoader = createClassLoader();
        classLoader.setResources(context.getResources());
        classLoader.setDelegate(this.delegate);

        // Configure our repositories
        setClassPath();

        setPermissions();

        classLoader.start();

        String contextName = context.getName();
        if (!contextName.startsWith("/")) {
            contextName = "/" + contextName;
        }
        ObjectName cloname = new ObjectName(context.getDomain() + ":type=" +
                classLoader.getClass().getSimpleName() + ",host=" +
                context.getParent().getName() + ",context=" + contextName);
        Registry.getRegistry(null, null)
            .registerComponent(classLoader, cloname, null);

    } catch (Throwable t) {
        t = ExceptionUtils.unwrapInvocationTargetException(t);
        ExceptionUtils.handleThrowable(t);
        throw new LifecycleException(sm.getString("webappLoader.startError"), t);
    }

createClassLoader的代码如下:

private WebappClassLoaderBase createClassLoader()
    throws Exception {
    //通过应用类classload加载loaderClass,这个loaderClass为
   // ParallelWebappClassLoader继承WebappClassLoaderBase
    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;
}

tomcat写死了loaderClass为ParallelWebappClassLoader,通过他的构造函数,创建一个牛叉的classload,WebappClassLoaderBase,并
指定了parent的classload为AppClassload,javaseClassLoader的class为ExtClassload,这两个在tomcat加载类的时候都会用到。

这里创建的WebappClassLoaderBase是tomcat的StandardContext级别的,tomcat会为每个webapp都创建一个StandardContext,即每个app也就有一个独立的classload,就这样可以各自依赖自己的jar包了。

tomcat的WebappClassLoader 创建好了,但是我们还要知道在哪里用到,怎么用的才行,下面就一步一步分析

JVM 类加载的全面性。

有个前提,就是tomcat加载我们自己定义的第一个class,肯定是我们定义的servlet,从servlet开始,就都是我们自己的写的class和依赖第三方的jar,只要保证servlet的加载是用WebappClassLoader加载,那servlet依赖的其他类都会用这个WebappClassLoader加载,从而都按
tomcat的加载规则来执行,这个是前提。

Servlet 的加载

tomcat servlet 如果订阅了loadstartup>=0,则在启动的时候初始化,否则在第一次请求的时候初始化 ,这个servlet的classload就是用的上面创建的classload来加载的,代码在StandardWrapper的loadServlet方法,核心代码如下:

    Servlet servlet;
    try {
        long t1=System.currentTimeMillis();
        // Complain if no servlet class has been specified
        if (servletClass == null) {
            unavailable(null);
            throw new ServletException
                (sm.getString("standardWrapper.notClass", getName()));
        }
        //这里是会用前面创建的webappclassload来加载我们订阅的servlet
        InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
        try {
            servlet = (Servlet) instanceManager.newInstance(servletClass);
        } catch (ClassCastException e) {
            unavailable(null);
            // Restore the context ClassLoader
            throw new ServletException
                (sm.getString("standardWrapper.notServlet", servletClass), e);
        } catch (Throwable e) {
            e = ExceptionUtils.unwrapInvocationTargetException(e);
            ExceptionUtils.handleThrowable(e);
            unavailable(null);

            // Added extra log statement for Bugzilla 36630:
            // https://bz.apache.org/bugzilla/show_bug.cgi?id=36630
            if(log.isDebugEnabled()) {
                log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
            }

            // Restore the context ClassLoader
            throw new ServletException
                (sm.getString("standardWrapper.instantiate", servletClass), e);
        }

InstanceManager

Tomcat通过InstanceManager来管理代理classload的。

InstanceManager 又是在哪里创建的呢,这个是在初始化StandardContext时,在创建完classload时,会初始化InstanceManager,代码如下:

    if (ok ) {
            if (getInstanceManager() == null) {
                setInstanceManager(createInstanceManager());
            }
            ...
     }

createInstanceManager 代码如下:

public InstanceManager createInstanceManager() {
    javax.naming.Context context = null;
    if (isUseNaming() && getNamingContextListener() != null) {
        context = getNamingContextListener().getEnvContext();
    }
    Map> injectionMap = buildInjectionMap(
            getIgnoreAnnotations() ? new NamingResourcesImpl(): getNamingResources());
   //this.getClass().getClassLoader()是tomcat启动的AppClassload
   return new DefaultInstanceManager(context, injectionMap,
           this, this.getClass().getClassLoader());
}

DefaultInstanceManager 的构造函数如下:

public DefaultInstanceManager(Context context,
        Map> injectionMap,
        org.apache.catalina.Context catalinaContext,
        ClassLoader containerClassLoader) {
    //这里指定了我们前面创建的WebappClassLoaderBase
    classLoader = catalinaContext.getLoader().getClassLoader();
    privileged = catalinaContext.getPrivileged();
    //containerClassLoader 是传进来的即AppClassload
    this.containerClassLoader = containerClassLoader;
    ignoreAnnotations = catalinaContext.getIgnoreAnnotations();
    Log log = catalinaContext.getLogger();
    Set classNames = new HashSet<>();
    loadProperties(classNames,
            "org/apache/catalina/core/RestrictedServlets.properties",
            "defaultInstanceManager.restrictedServletsResource", log);
    loadProperties(classNames,
            "org/apache/catalina/core/RestrictedListeners.properties",
            "defaultInstanceManager.restrictedListenersResource", log);
    loadProperties(classNames,
            "org/apache/catalina/core/RestrictedFilters.properties",
            "defaultInstanceManager.restrictedFiltersResource", log);
    restrictedClasses = Collections.unmodifiableSet(classNames);
    this.context = context;
    this.injectionMap = injectionMap;
    this.postConstructMethods = catalinaContext.findPostConstructMethods();
    this.preDestroyMethods = catalinaContext.findPreDestroyMethods();
}

DefaultInstanceManager 我们目前只要关心classLoader和containerClassLoader,tomcat在加载class时,会判断如果是tomcat自己的class,就用containerClassLoader加载,否则就classLoader也就是WebappClassLoaderBase加载。

现在我们知道InstanceManager是怎么来的,下面我们看这个InstanceManager是怎么加载我们定义的Servlet的,上面InstanceManager的newInstance方法最终会调用到DefaultInstanceManager的loadClass方法,代码如下:

protected Class loadClass(String className, ClassLoader classLoader)
        throws ClassNotFoundException {
    //如果是tomcat自己实现的类,则用appClassLoad加载
    if (className.startsWith("org.apache.catalina")) {
        return containerClassLoader.loadClass(className);
    }
    try {
         //如果是tomcat自己实现的类,则用appClassLoad加载
        Class clazz = containerClassLoader.loadClass(className);
        if (ContainerServlet.class.isAssignableFrom(clazz)) {
            return clazz;
        }
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
    }
    //这里就是WebappClassLoaderBase开始加载我们自己定义的Servlet的地方,这里面就涉及到加载顺序的问题
    return classLoader.loadClass(className);
}

到这里我们才分析完,tomcat的WebappClassLoaderBase的创建,和使用的地方,对于WebappClassLoaderBase具体怎么加载的,他为啥打破了jdk的双亲委派模型,以及对我们自己的class和第三方的依赖又是那个优先的,我们在下篇分析。

你可能感兴趣的:(Tomcat WebappClassloader 原理分析--上篇)