`
lengyun3566
  • 浏览: 453761 次
  • 性别: Icon_minigender_1
  • 来自: 大连
博客专栏
D59180b9-02f1-3380-840c-ea34da46143c
《Spring Secur...
浏览量:384296
社区版块
存档分类
最新评论

Tomcat源码解读系列(四)——Tomcat类加载机制概述

阅读更多

声明:源码版本为Tomcat 6.0.35

         在本系列的第二篇文章中,曾经介绍过在Tomcat启动时会初始化类加载器(ClassLoader),来处理整个Web工程中Class的加载问题。

         类加载机制是Java平台中相当重要的核心技术,待笔者有所积累后会再次讨论这个话题。在一般的业务开发中我们可能较少接触和使用ClassLoader,但是在进行框架级程序开发时,设计良好的类加载机制能够实现更好地模块划分和更优的设计,如Java模块化技术OSGi就是通过为每个组件声明独立的类加载器来实现组件的动态部署功能。在Tomcat的代码实现中,为了优化内存空间以及不同应用间的类隔离,Tomcat通过内置的一些类加载器来完成了这些功能。

         Java语言中,ClassLoader是以父子关系存在的,Java本身也有一定的类加载规范。在Tomcat中基本的ClassLoader层级关系如下图所示:



 Tomcat启动的时候,会初始化图示所示的类加载器。而上面的三个类加载器:CommonClassLoaderCatalinaClassLoaderSharedClassLoader是与具体部署的Web应用无关的,而WebappClassLoader则对应Web应用,每个Web应用都会有独立的类加载器,从而实现类的隔离。

         我们首先来看Tomcat的初始化,在Bootstrapinit方法中,会调用initClassLoaders方法,该方法负责前图中前三个类加载器的初始化:

private void initClassLoaders() {
        try {
            //初始化CommonClassLoader
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
                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);
        }
    }

 我们可以看到,此书初始化了三个类加载器,并且catalinaLoadersharedLoader都以commonLoader作为父类加载器,在这个方法中,将核心的业务交给了createClassLoader方法来实现:

private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
        //读取配置属性,相关的配置属性在catalina.properties文件中
        String value = CatalinaProperties.getProperty(name + ".loader");
        //如果没有对应的配置,将不会创建新的类加载器,而是返回传入的父类加载器
        if ((value == null) || (value.equals("")))
            return parent;

       //解析得到的配置文件,确定本ClassLoader要加载那些目录下的资源和JAR包等
        StringTokenizer tokenizer = new StringTokenizer(value, ",");
        while (tokenizer.hasMoreElements()) {
            String repository = tokenizer.nextToken();

          //此处省略的代码为将配置文件中的${catalina.base}、${catalina.home}等变量转
//换为绝对路径

        //格式化得到的位置路径和类型
        String[] locations = (String[]) repositoryLocations.toArray(new String[0]);
        Integer[] types = (Integer[]) repositoryTypes.toArray(new Integer[0]);
 //生成真正的类加载器 
        ClassLoader classLoader = ClassLoaderFactory.createClassLoader
            (locations, types, parent);

       //以下的代码为将生成的类加载器注册为MBean

        return classLoader;

    }

 而每个类加载器所加载的路径或JAR是在catalina.properties文件中定义的,默认的配置如下:

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

 按照默认的配置,catalinaLoadersharedLoader的配置项为空,因此不会创建对应的ClassLoader,而只会创建CommonClassLoader,该类加载器对应的Java实现类为:org.apache.catalina.loader. StandardClassLoader,该类继承自org.apache.catalina.loader. URLClassLoader,有关Tomcat基础类都会有该类加载器加载。例如在Bootstrapinit方法中,会调用Catalina类的init方法来完成相关操作:

public void init() throws Exception{

       //将当前线程的类加载器设置为catalinaLoader
        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);

       //使用catalinaLoader来加载Catalina类
        Class startupClass =
            catalinaLoader.loadClass
            ("org.apache.catalina.startup.Catalina");
        Object startupInstance = startupClass.newInstance();

        //调用Catalina的setParentClassLoader方法,设置为sharedLoader
        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;
    }

 以上为基础的三个类加载器的初始化过程。在每个Web应用初始化的时候,StandardContext对象代表每个Web应用,它会使用WebappLoader类来加载Web应用,而WebappLoader中会初始化org.apache.catalina.loader. WebappClassLoader来为每个Web应用创建单独的类加载器,在上一篇文章中,我们介绍过,当处理请求时,容器会根据请求的地址解析出由哪个Web应用来进行对应的处理,进而将当前线程的类加载器设置为请求Web应用的类加载器。让我们看一下WebappClassLoader的核心方法,也就是loadClass

public synchronized Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException {

        Class clazz = null;

        //首先检查已加载的类
        // (0) Check our previously loaded local class cache
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return (clazz);
        }

        // (0.1) Check our previously loaded class cache
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return (clazz);
        }

        // (0.2) Try loading the class with the system class loader, to prevent
        //       the webapp from overriding J2SE classes
        try {
            clazz = system.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // (0.5) Permission to access this class when using a SecurityManager
        if (securityManager != null) {
            int i = name.lastIndexOf('.');
            if (i >= 0) {
                try {
                    securityManager.checkPackageAccess(name.substring(0,i));
                } catch (SecurityException se) {
                    String error = "Security Violation, attempt to use " +
                        "Restricted Class: " + name;
                    log.info(error, se);
                    throw new ClassNotFoundException(error, se);
                }
            }
        }

        boolean delegateLoad = delegate || filter(name);
        //Tomcat允许按照配置来确定优先使用本Web应用的类加载器加载还是使用父类
//加载器来进行类加载,此处先使用父类加载器进行加载
        // (1) Delegate to our parent if requested
        if (delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader1 " + parent);
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = loader.loadClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                ;
            }
        }
        //使用本地的类加载器进行加载
        // (2) Search local repositories
        if (log.isDebugEnabled())
            log.debug("  Searching local repositories");
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Loading class from local repository");
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
        } catch (ClassNotFoundException e) {
            ;
        }
        //如果没有特殊配置的话,使用父类加载器加载类
        // (3) Delegate to parent unconditionally
        if (!delegateLoad) {
            if (log.isDebugEnabled())
                log.debug("  Delegating to parent classloader at end: " + parent);
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = loader.loadClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                ;
            }
        }
        //若最终类还是没有找到,抛出异常
        throw new ClassNotFoundException(name);

    }
 以上就是Web应用中类加载的机制。在默认情况下,WebappClassLoader的父类加载器就是CommonClassLoader,但是我们可以通过修改catalina.properties文件来设置SharedClassLoader,从而实现多个Web应用共用类库的效果。

  • 大小: 29.6 KB
2
1
分享到:
评论
2 楼 lengyun3566 2012-09-24  
jinnianshilongnian 写道
给点小建议,我觉得tomcat类加载可以从类冲突的情况开始,比如tomcat common loader加载了A, webapp loader也加载了A 因此肯定会发生冲突 此时再深入讲loader组成及加载顺序更好。

好主意,我也是觉得对ClassLoader这样一个复杂的概念,一篇短短的文章难以讲透,看国庆期间是不是有空,到时候再写一篇
1 楼 jinnianshilongnian 2012-09-24  
给点小建议,我觉得tomcat类加载可以从类冲突的情况开始,比如tomcat common loader加载了A, webapp loader也加载了A 因此肯定会发生冲突 此时再深入讲loader组成及加载顺序更好。

相关推荐

    tomcat 类加载机制 —— ClassLoader

    《Tomcat类加载机制——ClassLoader详解》 在Java Web开发中,Tomcat作为最常用的Servlet容器,其类加载机制对于理解和优化应用性能至关重要。本文将深入探讨Tomcat的ClassLoader是如何工作的,以及它如何影响到...

    tomcat 源码分析系列文档

    【标题】"Tomcat源码分析系列文档"深入解析了Apache Tomcat服务器的内部工作原理,涵盖了一系列关键知识点,如HTTP协议、类加载机制、容器设计模式等。这些文档为理解Tomcat的运行机制提供了宝贵的资源。 【描述】...

    tomcat源码+文档pdf+源码解析

    源码解析部分则是对Tomcat源码的深度剖析,涵盖了关键类和方法的作用、设计模式的运用以及性能优化技巧。这有助于开发者理解Tomcat内部的工作流程,例如,如何处理HTTP请求的生命周期,以及线程池是如何调度和管理的...

    tomcat源码阅读(一)——环境搭建

    本篇将聚焦于"Tomcat源码阅读(一)——环境搭建",探讨如何搭建一个适合源码学习的开发环境。 首先,我们需要了解Tomcat是什么。Tomcat是一款开源的Java Servlet容器,由Apache软件基金会维护,实现了Java EE中的...

    「Tomcat源码剖析」.pdf

    Tomcat源码剖析 : 整体架构 层层分析 源码解析 架构分析 (Http服务器功能:Socket通信(TCP/IP)、解析Http报文 Servlet容器功能:有很多Servlet(自带系统级Servlet+自定义Servlet),Servlet处理具体的业务逻辑...

    Syske#person-learning-note#Tomcat源码分析-壹——启动过程值init方法1

    前言从今天开始,我们开始分析tomcat的源码,至于原因嘛,第一Tomcat是非常优秀的web服务器,它占据着全球一半以上的市场份额,就连spring boot

    tomcat工作原理深入详解——HowTomcatWorks中文版.pdf

    tomcat工作原理深入详解——HowTomcatWorks中文版.pdf

    Ant编译Tomcat源码、MyEclipse导入Tomcat源码、执行Tomcat源码启动Tomcat

    在IT行业中,Ant和Tomcat是两个非常关键的组件,分别用于...通过Ant编译源码,MyEclipse导入和运行,开发者可以更深入地理解Tomcat的工作机制,并进行定制化开发。在实践中不断探索,将有助于提升你的Java EE开发技能。

    tomcat源码

    Apache Tomcat源码分析 Apache Tomcat是一款广泛应用的开源Java Servlet容器,它是Java EE Web应用程序的标准实现。Tomcat源码的深入理解对于Java Web开发者来说是至关重要的,它可以帮助我们了解HTTP服务器的工作...

    tomcat源码解析

    ### tomcat源码解析 #### 简介与概览 Tomcat作为一款开源的Servlet容器,被广泛应用于Java Web应用的开发与部署环境中。它不仅支持Servlet API,还支持JSP规范,使得开发者能够轻松地构建动态网页。本文旨在深入...

    tomcat8源码

    Tomcat使用自定义的类加载器来加载Web应用中的类,以实现不同应用之间的类隔离。主要有CommonClassLoader、SharedClassLoader和WebappClassLoader,它们按层次加载类。 5. **连接器(Connector)与协议处理器...

    02-Tomcat源码解读1

    Tomcat作为一款广泛使用的开源Java Servlet容器,其源码解读对于理解其工作原理至关重要。在深入源码之前,我们需要了解Tomcat的基本架构和关键组件。Tomcat的顶层由Server和Service两个核心组件构成。 **Server...

    tomcat源码导入myeclipse

    【标题】"Tomcat源码导入MyEclipse"是一个针对Java开发者的重要实践操作,它涉及到两个关键组件:Tomcat服务器和MyEclipse集成开发环境。Tomcat是Apache软件基金会的一个开源项目,它作为Servlet和JavaServer Pages...

    编译tomcat源码所需jar包

    本文将深入探讨“编译Tomcat源码所需jar包”这一主题,帮助开发者了解如何从源码构建Tomcat,以及在这个过程中需要用到的关键jar包。 首先,我们来了解一下为什么要从源码编译Tomcat。直接下载预编译的二进制版本...

    Tomcat深入剖析pdf+源码(Tomcat运行原理)

    3. **Servlet生命周期**:Servlet在Tomcat中的生命周期包括加载、初始化、服务、销毁四个阶段。Tomcat通过Servlet容器管理Servlet实例,确保其正确地创建、初始化和销毁。 4. **请求处理**:当一个HTTP请求到达时,...

    eclipse运行tomcat源码:修改源码:重新编译:重新打包

    文件描述: 1.tomcat源码文件 2.tomcat程序文件 3.Ant程序文件 4.tomcat源码在eclipse运行,重新编译,重新打包步骤文件 ...1.用于学习tomcat源码和了解tomcat运行机制 2.学习如何修改tomcat源码后如何重新编译,打包。

    tomcat7源码

    源码分析是提升开发者对服务器内部运作机制理解的重要途径,尤其对于Tomcat这样的核心组件,源码的学习能够帮助我们更深入地理解Web应用的部署、运行以及性能优化。 首先,我们要了解Tomcat的架构。Tomcat7基于...

Global site tag (gtag.js) - Google Analytics