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<String, Map<String, String>> 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<String, Map<String, String>> 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<String> 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和第三方的依赖又是那个优先的,我们在下篇分析。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355

推荐阅读更多精彩内容