美文网首页
Tomcat WebappClassloader 原理分析--上

Tomcat WebappClassloader 原理分析--上

作者: 绝尘驹 | 来源:发表于2020-04-12 20:56 被阅读0次

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

    相关文章

      网友评论

          本文标题:Tomcat WebappClassloader 原理分析--上

          本文链接:https://www.haomeiwen.com/subject/yvytuctx.html