Tomcat7.0源码分析——类加载体系

来源:互联网 发布:雪平锅优缺点 知乎 编辑:程序博客网 时间:2024/06/11 18:33

前言

Tomcat遵循J2EE规范,实现了Web容器。很多有关web的书籍和文章都离不开对Tomcat的分析,初学者可以从Tomcat的实现对J2EE有更深入的了解。此外,Tomcat还根据Java虚拟机规范实现了经典的双亲委派模式的类加载体系。本文基于Tomcat7.0的Java源码,对其类加载体系进行分析。

概述

首先简单介绍下Java虚拟机规范中提到的主要类加载器;
  • Bootstrap Loader:加载lib目录下或者System.getProperty(“sun.boot.class.path”)、或者-XBootclasspath所指定的路径或jar。
  • Extended Loader:加载lib\ext目录下或者System.getProperty(“java.ext.dirs”) 所指定的 路径或jar。在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld。
  • AppClass Loader:加载System.getProperty("java.class.path")所指定的 路径或jar。在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld。
根据java虚拟机的双亲委派模式的原则,类加载器在加载一个类时,首先交给父类加载器加载,层层往上直到Bootstrap Loader。也就是一个类最先由Bootstrap Loader加载,如果没有加载到,则交给下一层的类加载器加载,如果没有加载到,则依次层层往下,直到最下层的类加载器。这也就是说,凡是能通过父一级类加载器加载到的类,对于子类也是可见的。因此可以利用双亲委派模式的特性,使用类加载器对不同路径下的jar包或者类进行环境隔离。

然后用一张图片来展示Tomcat的类加载体系:

这里结合之前对双亲委派模式的类加载过程的描述,对上图所示类加载体系进行介绍:
ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器需要继承实现
commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问;
catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见;

WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;

源码分析

commonLoader、catalinaLoader和sharedLoader在Tomcat容器初始化的一开始,即调用Bootstrap的init方法时创建。catalinaLoader会被设置为Tomcat主线程的线程上下文类加载器,并且使用catalinaLoader加载Tomcat容器自身容器下的class。Bootstrap的init方法的部分代码见代码清单1。

代码清单1Bootstrap的init方法的部分实现

    /**     * Initialize daemon.     */    public void init()        throws Exception    {        // Set Catalina path        setCatalinaHome();        setCatalinaBase();        initClassLoaders();        Thread.currentThread().setContextClassLoader(catalinaLoader);        SecurityClassLoad.securityClassLoad(catalinaLoader);        // 省略后边的代码

 代码清单1中,我们首先关注initClassLoaders方法的实现,见代码清单2.initClassLoaders方法用来初始化commonLoader、catalinaLoader、sharedLoader。

代码清单2initClassLoaders方法的实现

    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);        }    }

 从代码清单2中看到创建类加载器是通过调用createClassLoader方法实现的,createClassLoader的实现见代码清单3.

代码清单3createClassLoader方法的实现

    private ClassLoader createClassLoader(String name, ClassLoader parent)        throws Exception {        String value = CatalinaProperties.getProperty(name + ".loader");        if ((value == null) || (value.equals("")))            return parent;        ArrayList<String> repositoryLocations = new ArrayList<String>();        ArrayList<Integer> repositoryTypes = new ArrayList<Integer>();        int i;         StringTokenizer tokenizer = new StringTokenizer(value, ",");        while (tokenizer.hasMoreElements()) {            String repository = tokenizer.nextToken();            // Local repository            boolean replace = false;            String before = repository;            while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {                replace=true;                if (i>0) {                repository = repository.substring(0,i) + getCatalinaHome()                     + repository.substring(i+CATALINA_HOME_TOKEN.length());                } else {                    repository = getCatalinaHome()                         + repository.substring(CATALINA_HOME_TOKEN.length());                }            }            while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {                replace=true;                if (i>0) {                repository = repository.substring(0,i) + getCatalinaBase()                     + repository.substring(i+CATALINA_BASE_TOKEN.length());                } else {                    repository = getCatalinaBase()                         + repository.substring(CATALINA_BASE_TOKEN.length());                }            }            if (replace && log.isDebugEnabled())                log.debug("Expanded " + before + " to " + repository);            // Check for a JAR URL repository            try {                new URL(repository);                repositoryLocations.add(repository);                repositoryTypes.add(ClassLoaderFactory.IS_URL);                continue;            } catch (MalformedURLException e) {                // Ignore            }            if (repository.endsWith("*.jar")) {                repository = repository.substring                    (0, repository.length() - "*.jar".length());                repositoryLocations.add(repository);                repositoryTypes.add(ClassLoaderFactory.IS_GLOB);            } else if (repository.endsWith(".jar")) {                repositoryLocations.add(repository);                repositoryTypes.add(ClassLoaderFactory.IS_JAR);            } else {                repositoryLocations.add(repository);                repositoryTypes.add(ClassLoaderFactory.IS_DIR);            }        }        String[] locations = repositoryLocations.toArray(new String[0]);        Integer[] types = repositoryTypes.toArray(new Integer[0]);         ClassLoader classLoader = ClassLoaderFactory.createClassLoader            (locations, types, parent);        // Retrieving MBean server        MBeanServer mBeanServer = null;        if (MBeanServerFactory.findMBeanServer(null).size() > 0) {            mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);        } else {            mBeanServer = ManagementFactory.getPlatformMBeanServer();        }        // Register the server classloader        ObjectName objectName =            new ObjectName("Catalina:type=ServerClassLoader,name=" + name);        mBeanServer.registerMBean(classLoader, objectName);        return classLoader;    }

createClassLoader方法的执行步骤如下:

  1. 获取各个类加载器相应的资源配置文件(分别为common.loader、server.loader、shared.loader),从中获取类资源路径的配置信息;
  2. 解析类资源路径下的各个资源位置和类型,也包括对jar资源的检查;
  3. 调用ClassLoaderFactory.createClassLoader(locations, types, parent)方法创建ClassLoader;
  4. ClassLoader注册到JMX服务中,有个JMX的内容可以参照《Tomcat7.0源码分析——生命周期管理 》一文中的相关介绍。

我们回头看看代码清单1中的SecurityClassLoad.securityClassLoad(catalinaLoader)的实现,见代码清单4.这说明加载Tomcat容器本身的类资源的确是使用catalinaLoader来完成的。

代码清单4securityClassLoad的实现

    public static void securityClassLoad(ClassLoader loader)        throws Exception {        if( System.getSecurityManager() == null ){            return;        }                loadCorePackage(loader);        loadLoaderPackage(loader);        loadSessionPackage(loader);        loadUtilPackage(loader);        loadJavaxPackage(loader);        loadCoyotePackage(loader);                loadTomcatPackage(loader);    }

securityClassLoad方法主要加载Tomcat容器所需的class,包括:

  • Tomcat核心class,即org.apache.catalina.core路径下的class;
  • org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName;
  • Tomcat有关session的class,即org.apache.catalina.session路径下的class;
  • Tomcat工具类的class,即org.apache.catalina.util路径下的class;
  • javax.servlet.http.Cookie;
  • Tomcat处理请求的class,即org.apache.catalina.connector路径下的class;
  • Tomcat其它工具类的class,也是org.apache.catalina.util路径下的class;

我们以加载Tomcat核心class的loadCorePackage方法为例,其实现见代码清单5所示。

代码清单5loadCorePackage的实现

    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.AsyncContextImpl");        loader.loadClass            (basePackage +            "core.AsyncContextImpl$AsyncState");        loader.loadClass            (basePackage +            "core.AsyncContextImpl$DebugException");        loader.loadClass            (basePackage +            "core.AsyncContextImpl$1");        loader.loadClass            (basePackage +            "core.AsyncContextImpl$2");        loader.loadClass            (basePackage +            "core.AsyncListenerWrapper");        loader.loadClass            (basePackage +             "core.ContainerBase$PrivilegedAddChild");        loader.loadClass            (basePackage +             "core.DefaultInstanceManager$1");        loader.loadClass            (basePackage +             "core.DefaultInstanceManager$2");        loader.loadClass            (basePackage +             "core.DefaultInstanceManager$3");        loader.loadClass            (basePackage +             "core.DefaultInstanceManager$4");        loader.loadClass            (basePackage +             "core.DefaultInstanceManager$5");        loader.loadClass            (basePackage +             "core.ApplicationHttpRequest$AttributeNamesEnumerator");    }

 至此,有关commonLoader、catalinaLoader和sharedLoader三个类加载器的初始化以及使用catalinaLoader加载Tomcat容器自身类资源的内容已经介绍完了,但是我们还没有看到WebappClassLoader。启动StandardContext的时候会创建WebappLoader,根据Tomcat7.0源码分析——生命周期管理 》一文的内容,我们知道启动StandardContext时会最终调用其startInternal方法,其实现见代码清单6.

代码清单6StandardContext的startInternal方法

    /**     * Start this component and implement the requirements     * of {@link LifecycleBase#startInternal()}.     *     * @exception LifecycleException if this component detects a fatal error     *  that prevents this component from being used     */    @Override    protected synchronized void startInternal() throws LifecycleException {        // 省略前边的代码         if (getLoader() == null) {            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());            webappLoader.setDelegate(getDelegate());            setLoader(webappLoader);        }       // 省略中间的代码        // Start our subordinate components, if any       if ((loader != null) && (loader instanceof Lifecycle))            ((Lifecycle) loader).start();        // 省略后边的代码     }

 从代码清单6看到首先创建WebappLoader实例,然后调用WebappLoader的start方法,start又调用了startInternal方法,WebappLoader的startInternal的实现见代码清单7.

代码清单7WebappLoader的startInternal实现

    /**     * Start associated {@link ClassLoader} and implement the requirements     * of {@link LifecycleBase#startInternal()}.     *     * @exception LifecycleException if this component detects a fatal error     *  that prevents this component from being used     */    @Override    protected void startInternal() throws LifecycleException {                // Register a stream handler factory for the JNDI protocol        URLStreamHandlerFactory streamHandlerFactory =            new DirContextURLStreamHandlerFactory();        if (first) {            first = false;            try {                URL.setURLStreamHandlerFactory(streamHandlerFactory);            } catch (Exception e) {                // Log and continue anyway, this is not critical                log.error("Error registering jndi stream handler", e);            } catch (Throwable t) {                // This is likely a dual registration                log.info("Dual registration of jndi stream handler: "                          + t.getMessage());            }        }        // Construct a class loader based on our current repositories list        try {            classLoader = createClassLoader();            classLoader.setResources(container.getResources());            classLoader.setDelegate(this.delegate);            classLoader.setSearchExternalFirst(searchExternalFirst);            if (container instanceof StandardContext) {                classLoader.setAntiJARLocking(                        ((StandardContext) container).getAntiJARLocking());                classLoader.setClearReferencesStatic(                        ((StandardContext) container).getClearReferencesStatic());                classLoader.setClearReferencesStopThreads(                        ((StandardContext) container).getClearReferencesStopThreads());                classLoader.setClearReferencesStopTimerThreads(                        ((StandardContext) container).getClearReferencesStopTimerThreads());                classLoader.setClearReferencesThreadLocals(                        ((StandardContext) container).getClearReferencesThreadLocals());            }            for (int i = 0; i < repositories.length; i++) {                classLoader.addRepository(repositories[i]);            }

 我们看到代码清单7中通过调用createClassLoader来创建类加载器,并且设置其资源路径为当前Webapp下的类资源。最后我们看看createClassLoader的实现,见代码清单8.

代码清单8createClassLoader的实现

    /**     * Create associated classLoader.     */    private WebappClassLoader createClassLoader()        throws Exception {        //loaderClass即字符串org.apache.catalina.loader.WebappClassLoader        Class<?> clazz = Class.forName(loaderClass);        WebappClassLoader classLoader = null;        if (parentClassLoader == null) {            parentClassLoader = container.getParentClassLoader();        }        Class<?>[] argTypes = { ClassLoader.class };        Object[] args = { parentClassLoader };        Constructor<?> constr = clazz.getConstructor(argTypes);        classLoader = (WebappClassLoader) constr.newInstance(args);        return classLoader;    }

 这里loaderClass的值是字符串org.apache.catalina.loader.WebappClassLoader,通过反射来实例化WebappClassLoader。由于每个Webapp下的类资源由不同的WebappClassLoader负责加载,因此各个Webapp下的类资源是独立的。至此,整个Tomcat的类加载体系构建完毕。


后记:个人总结整理的《深入理解Spark:核心思想与源码分析》一书现在已经正式出版上市,目前京东、当当、天猫等网站均有销售,欢迎感兴趣的同学购买。


京东(现有满150减50活动)):http://item.jd.com/11846120.html 

当当:http://product.dangdang.com/23838168.html 


6 0
原创粉丝点击