zoukankan      html  css  js  c++  java
  • Tomcat源码解读:ClassLoader的设计

             Tomcat是一个经典的web server,学习tomcat的源码对于我们是有很大的帮助的。前一段时间了解了tomcat的工作的大致流程,对我的新工作有了很大的帮助。刚学习了ClassLoader(学习classloader的初衷源于公司产品的一个bug),也将我对classloaderp写成了一篇博客。为了对ClassLoader有更多的理解,现在就来看看Tomcat 6 的ClassLoader设计。

             之前通过对tomcat的启动过程、tomcat处理request的过程进行简单的了解,了解tomcat的各个组件及其功能。在这两个过程中,都有ClassLoader的影子,所以今天依旧从这两个方面入手,来了解Tomcat 如何使用ClassLoader。

    StandardClassLoader

    Bootstrap.main()方法简单点说就是执行4个方法:init , setAwait, load, start。

    init

    public void init()
            throws Exception
        {
    
            // Set Catalina path
            setCatalinaHome();
            setCatalinaBase();
    
            initClassLoaders();
    
            Thread.currentThread().setContextClassLoader(catalinaLoader);
    
            SecurityClassLoad.securityClassLoad(catalinaLoader);
    
            // Load our startup class and call its process() method
            if (log.isDebugEnabled())
                log.debug("Loading startup class");
            Class startupClass =
                catalinaLoader.loadClass
                ("org.apache.catalina.startup.Catalina");
            Object startupInstance = startupClass.newInstance();
    
            // Set the shared extensions class loader
            if (log.isDebugEnabled())
                log.debug("Setting startup class properties");
            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;
    
        }

    在init方法中调用首先initClassLoader方法来初始化Tomcat的ClassLoader模块,然后是使用刚自定义类加载器加载catalinaLoader 来加载org.apache.catalina.startup.Catalina 类。接下来是调用Catalina中的setParentClassLoader方法。

    那就看看initClassLoader中将Tomcat的ClassLoader模块初始化成什么样子的:

    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) {
                log.error("Class loader creation threw exception", t);
                System.exit(1);
            }
    }

    这里其实就是创建了3个ClassLoader,分别是commonLoader, catalinaLoader, sharedLoader。而其实它们之间是有这某种关系的:

    private ClassLoader createClassLoader(String name, ClassLoader parent)
            throws Exception {
    String value = CatalinaProperties.getProperty(name + ".loader");
            if ((value == null) || (value.equals("")))
                return parent;
    // some statement
    ClassLoader classLoader = ClassLoaderFactory.createClassLoader
                (locations, types, parent);
       // some statement
    }

    也就是说启动过程初始化后的ClassLoader模型为:

    其中顶部的3个classLoader对象都由JDK提供的。commonLoader, catalinaLoader, sharedLoader它们三个有一个共同的名字:StandardClassLoader。

    bootstrapClassLoader是加载java_home/jre/lib目录下的个别jar包(不是全部)

    extClassLoader是加载java_home/jre/lib/ext目录下的jar包

    AppClassLoader是加载classpath中指定的jar包

    在catalina.properties文件中则指出了这3个ClassLoader默认的加载路径。

    common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar
    server.loader=
    shared.loader=

    这个文件是在jar包中的,在使用tomcat时是不可以修改的。如果想要修改默认的加载路径,该怎么办呢?可以在catalina.config配置

    想要了解是什么原因,可以参考CatalinaProperties类的实现。

    上面说了Bootstrap的main方法中其实就是调用了4个方法init,setAwait, start, load,除了init外,另外3个方法其实就是调用Catalina对象的对应的setAwait,start, load方法。

    从init方法的实现中,可以知道,在Bootstrap类中,除了Catalina类是由catalinaClassLoader加载的之外,其余的类都是JDK提供的ClassLoader加载的。也就是说根据上节学习的内容,Catalina类在当前类Bootstrap类中是不能直接调用的。然而这里又要调用Catalina中的方法,我在上节的测试中,采取的是另外启动一个线程来解决的。今天就来看看Tomcat中如何解决这样的应用场景的:

    1)init中调用Catalina的setParentClassLoader方法

    Object startupInstance = startupClass.newInstance();
    
            // Set the shared extensions class loader
            if (log.isDebugEnabled())
                log.debug("Setting startup class properties");
            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);

    2) setAwait中调用catalina的setAwait方法

    public void setAwait(boolean await)
            throws Exception {
    
            Class paramTypes[] = new Class[1];
            paramTypes[0] = Boolean.TYPE;
            Object paramValues[] = new Object[1];
            paramValues[0] = new Boolean(await);
            Method method = 
                catalinaDaemon.getClass().getMethod("setAwait", paramTypes);
            method.invoke(catalinaDaemon, paramValues);
    
        }

    3)start调用catalina的start方法

    public void start()
            throws Exception {
            if( catalinaDaemon==null ) init();
    
            Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
            method.invoke(catalinaDaemon, (Object [])null);
    
        }

    4)load调用catalina的load方法

    private void load(String[] arguments)
            throws Exception {
    
            // Call the load() method
            String methodName = "load";
            Object param[];
            Class paramTypes[];
            if (arguments==null || arguments.length==0) {
                paramTypes = null;
                param = null;
            } else {
                paramTypes = new Class[1];
                paramTypes[0] = arguments.getClass();
                param = new Object[1];
                param[0] = arguments;
            }
            Method method = 
                catalinaDaemon.getClass().getMethod(methodName, paramTypes);
            if (log.isDebugEnabled())
                log.debug("Calling startup class " + method);
            method.invoke(catalinaDaemon, param);
    
        }

    它们都是使用反射来处理。

    现在看来,要解决由不同classLoader加载器加载的类之前的方法调用,就有2种处理方案了:

    方案一:另启动一个线程

    方案二:使用反射。

    Catalina是由catalinaClassLoader加载的,Tomcat启动过程会将Tomcat的相关组件加载并初始化,而这些过程的入口都在Catalina中。所以呢Tomcat中绝在部分组件应当都是由catalinaClassLoader加载的。这句话说的可能有些满,不过呢,我这么说也是有原因的:

    在init方法中有这么一个过程:SecurityClassLoad.securityClassLoad(catalinaLoader);

    下面来看看这个过程的真面目:

    public static void securityClassLoad(ClassLoader loader)
            throws Exception {
    
            if( System.getSecurityManager() == null ){
                return;
            }
            
            loadCorePackage(loader);
            loadLoaderPackage(loader);
            loadServletsPackage(loader);
            loadSessionPackage(loader);
            loadUtilPackage(loader);
            loadJavaxPackage(loader);
            loadCoyotePackage(loader);
            loadHttp11Package(loader);
            loadTomcatPackage(loader);
    }
    
    private final static void loadCorePackage(ClassLoader loader)
            throws Exception {
            String basePackage = "org.apache.catalina.";
            loader.loadClass
                (basePackage +
                 "core.ApplicationContextFacade$1");
            loader.loadClass
                (basePackage +
                 "core.ApplicationDispatcher$PrivilegedForward");
            loader.loadClass
                (basePackage +
                 "core.ApplicationDispatcher$PrivilegedInclude");
            loader.loadClass
                (basePackage +
                 "core.ContainerBase$PrivilegedAddChild");
            loader.loadClass
                (basePackage +
                 "core.StandardWrapper$1");
            loader.loadClass
                (basePackage +
                  "core.ApplicationHttpRequest$AttributeNamesEnumerator");
        }

    securityClassLoad(ClassLoader loader)采用了门面(facade)模式来加载各个类。这里只列出了loadCorePackage的实现。其它方法的实现与这个是完全一样的。所以上面 我才说Tomcat中绝在部分组件应当都是由catalinaClassLoader加载的。

    同时根据这个方法,也可以看出,要加载内部类,要用外部类与内部类类名之间加上$

    ===============================================================================

    WebappClassLoader

    上面说的其实就是Tomcat中的StandardClassLoader,Tomcat中还有一种WebappClassLoader。根据名字就可以看出来,这个ClassLoader是用于加载各个Web Application而设计的。Tomcat的各个组件中与Web Application有对应关系的,也就是StandardContext。为何这么说呢?我们在Web应用中使用的ServletConext(Java EE的标准API)在Tomcat中由ApplicationContext来实现,而ApplicationContext其实就是StandardContext的委托。

    这个Classloader是用于加载web应用程序中的类。这个类以后会有专门了解一下。

  • 相关阅读:
    Spring4+SpringMVC+MyBatis登录注册详细
    Spring MVC登录注册以及转换json数据
    MyBatis+mysql查询和添加数据
    html5中的选择器
    倒影(转)
    bi包
    函数作用域
    节点开始
    window.onload中失效的问题
    Node.js简介
  • 原文地址:https://www.cnblogs.com/f1194361820/p/4186232.html
Copyright © 2011-2022 走看看