文档章节

Tomcat源码学习(五)-- Tomcat_7.0.70 类加载体系分析

火龙战士
 火龙战士
发布于 2016/07/08 10:00
字数 1737
阅读 58
收藏 2

1、前言

Tomcat遵循J2EE规范,实现了Web容器。Java虚拟机有自己的一套类加载体系,同样Tomcat也有自己的一套类加载体系。

2、概述

首先简单介绍下Java虚拟机的主要的类加载器:

  1. 启动类加载器(bootstrap classloader)

    它用来加载 Java 的核心库,是用原生代码(本地代码,与平台有关)来实现的,并不继承自java.lang.ClassLoader。这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识加的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接引用。

  2. 扩展类加载器(extensions classloader)

    扩展类加载器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 < Java_Runtime_Home >/lib/ext 或者由系统变量java.ext.dir 指定位置中的类库加载到内存中

  3. 应用程序类加载器(application classloader)

    系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,由于这个类加载器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序默认的类加载器。

  4. 用户自定义的类装载器

    用户自定义的类装载器是普通的Java对象,它的类必须派生自java.lang.ClassLoader类。ClassLoader中定义的方法为程序为程序提供了访问类装载器机制的接口。此外,对于每一个被装载的类型,Java虚拟机都会为它创建一个java.lang.Class类的实例来代表该类型。和所有其它对象一样,用户自定义的类装载器以有Class类的实例都放在内存中的堆区,而装载的类型信息则都放在方法区。

然后在来一张图简要说明Tomcat的类加载体系(图画的不好):

  • ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器需要继承实现
  • commonLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问
  • catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见
  • sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见
  • WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见

3、分析

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

/**
 * Initialize daemon.
 */
public void init()
    throws Exception
{
	setCatalinaHome();
    setCatalinaBase();

    initClassLoaders();

    Thread.currentThread().setContextClassLoader(catalinaLoader);

    SecurityClassLoad.securityClassLoad(catalinaLoader);
	.....
}

initClassLoaders方法:

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

创建类加载器的createClassLoader方法的实现:

private ClassLoader createClassLoader(String name, ClassLoader parent)
    throws Exception {

    String value = CatalinaProperties.getProperty(name + ".loader");
    if ((value == null) || (value.equals("")))
        return parent;

    value = replace(value);

    List<Repository> repositories = new ArrayList<Repository>();

    StringTokenizer tokenizer = new StringTokenizer(value, ",");
    while (tokenizer.hasMoreElements()) {
        String repository = tokenizer.nextToken().trim();
        if (repository.length() == 0) {
            continue;
        }

        // Check for a JAR URL repository
        try {
            @SuppressWarnings("unused")
            URL url = new URL(repository);
            repositories.add(
                    new Repository(repository, RepositoryType.URL));
            continue;
        } catch (MalformedURLException e) {
            // Ignore
        }

        // Local repository
        if (repository.endsWith("*.jar")) {
            repository = repository.substring
                (0, repository.length() - "*.jar".length());
            repositories.add(
                    new Repository(repository, RepositoryType.GLOB));
        } else if (repository.endsWith(".jar")) {
            repositories.add(
                    new Repository(repository, RepositoryType.JAR));
        } else {
            repositories.add(
                    new Repository(repository, RepositoryType.DIR));
        }
    }

    return ClassLoaderFactory.createClassLoader(repositories, parent);
}

createClassLoader最终使用ClassLoaderFactory.createClassLoader(locations, types, parent)方法创建ClassLoader。

我们在看SecurityClassLoad.securityClassLoad(catalinaLoader);

 public static void securityClassLoad(ClassLoader loader)
    throws Exception {

    if( System.getSecurityManager() == null ){
        return;
    }

    loadCorePackage(loader);
    loadCoyotePackage(loader);
    loadLoaderPackage(loader);
    loadRealmPackage(loader);
    loadServletsPackage(loader);
    loadSessionPackage(loader);
    loadUtilPackage(loader);
    loadValvesPackage(loader);
    loadJavaxPackage(loader);
    loadConnectorPackage(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方法为例,查看其实现:

private static final void loadCorePackage(ClassLoader loader)
    throws Exception {
    final String basePackage = "org.apache.catalina.core.";
    loader.loadClass
        (basePackage +
         "AccessLogAdapter");
    loader.loadClass
        (basePackage +
         "ApplicationContextFacade$1");
    loader.loadClass
        (basePackage +
         "ApplicationDispatcher$PrivilegedForward");
    loader.loadClass
        (basePackage +
         "ApplicationDispatcher$PrivilegedInclude");
    loader.loadClass
        (basePackage +
        "AsyncContextImpl");
    loader.loadClass
        (basePackage +
        "AsyncContextImpl$DebugException");
    loader.loadClass
        (basePackage +
        "AsyncContextImpl$1");
    loader.loadClass
        (basePackage +
        "AsyncListenerWrapper");
    loader.loadClass
        (basePackage +
         "ContainerBase$PrivilegedAddChild");
    loader.loadClass
        (basePackage +
         "DefaultInstanceManager$1");
    loader.loadClass
        (basePackage +
         "DefaultInstanceManager$2");
    loader.loadClass
        (basePackage +
         "DefaultInstanceManager$3");
    loader.loadClass
        (basePackage +
         "DefaultInstanceManager$AnnotationCacheEntry");
    loader.loadClass
        (basePackage +
         "DefaultInstanceManager$AnnotationCacheEntryType");
    loader.loadClass
        (basePackage +
         "ApplicationHttpRequest$AttributeNamesEnumerator");
}

至此为止,我们还没有看到WebappClassLoader。启动StandardContext的时候会创建WebappLoader,StandardContext的方法startInternal的部分代码如下:

	protected synchronized void startInternal() throws LifecycleException {  
  
    ......
  
    if (getLoader() == null) {  
        WebappLoader webappLoader = new WebappLoader(getParentClassLoader());  
        webappLoader.setDelegate(getDelegate());  
        setLoader(webappLoader);  
    }  
   ......  
   if ((loader != null) && (loader instanceof Lifecycle))  
        ((Lifecycle) loader).start();   
   // 省略后边的代码   
}

从上面代码看到最后会调用WebappLoader的start方法:

public final synchronized void start() throws LifecycleException {
    
    if (LifecycleState.STARTING_PREP.equals(state) || LifecycleState.STARTING.equals(state) ||
            LifecycleState.STARTED.equals(state)) {
        
        if (log.isDebugEnabled()) {
            Exception e = new LifecycleException();
            log.debug(sm.getString("lifecycleBase.alreadyStarted", toString()), e);
        } else if (log.isInfoEnabled()) {
            log.info(sm.getString("lifecycleBase.alreadyStarted", toString()));
        }
        
        return;
    }
    
    if (state.equals(LifecycleState.NEW)) {
        init();
    } else if (state.equals(LifecycleState.FAILED)) {
        stop();
    } else if (!state.equals(LifecycleState.INITIALIZED) &&
            !state.equals(LifecycleState.STOPPED)) {
        invalidTransition(Lifecycle.BEFORE_START_EVENT);
    }

    setStateInternal(LifecycleState.STARTING_PREP, null, false);

    try {
        startInternal();//start再次调用了startInternal方法(WebappLoader中的方法)
    } catch (Throwable t) {
        // This is an 'uncontrolled' failure so put the component into the
        // FAILED state and throw an exception.
        ExceptionUtils.handleThrowable(t);
        setStateInternal(LifecycleState.FAILED, null, false);
        throw new LifecycleException(sm.getString("lifecycleBase.startFail", toString()), t);
    }

    if (state.equals(LifecycleState.FAILED)) {
        // This is a 'controlled' failure. The component put itself into the
        // FAILED state so call stop() to complete the clean-up.
        stop();
    } else if (!state.equals(LifecycleState.STARTING)) {
        // Shouldn't be necessary but acts as a check that sub-classes are
        // doing what they are supposed to.
        invalidTransition(Lifecycle.AFTER_START_EVENT);
    } else {
        setStateInternal(LifecycleState.STARTED, null, false);
    }
}

start又调用了startInternal方法,startInternal的实现如下:

protected void startInternal() throws LifecycleException {

    if (log.isDebugEnabled())
        log.debug(sm.getString("webappLoader.starting"));

    if (container.getResources() == null) {
        log.info("No resources for " + container);
        setState(LifecycleState.STARTING);
        return;
    }

    // Register a stream handler factory for the JNDI protocol
    URLStreamHandlerFactory streamHandlerFactory =
            DirContextURLStreamHandlerFactory.getInstance();
    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) {
            ExceptionUtils.handleThrowable(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.setClearReferencesRmiTargets(
                    ((StandardContext) container).getClearReferencesRmiTargets());
            classLoader.setClearReferencesStatic(
                    ((StandardContext) container).getClearReferencesStatic());
            classLoader.setClearReferencesStopThreads(
                    ((StandardContext) container).getClearReferencesStopThreads());
            classLoader.setClearReferencesStopTimerThreads(
                    ((StandardContext) container).getClearReferencesStopTimerThreads());
            classLoader.setClearReferencesHttpClientKeepAliveThread(
                    ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
        }

        for (int i = 0; i < repositories.length; i++) {
            classLoader.addRepository(repositories[i]);
        }

        // Configure our repositories
        setRepositories();
        setClassPath();

        setPermissions();

        ((Lifecycle) classLoader).start();

        // Binding the Webapp class loader to the directory context
        DirContextURLStreamHandler.bind(classLoader,
                this.container.getResources());

        StandardContext ctx=(StandardContext)container;
        String contextName = ctx.getName();
        if (!contextName.startsWith("/")) {
            contextName = "/" + contextName;
        }
        ObjectName cloname = new ObjectName
            (MBeanUtils.getDomain(ctx) + ":type=WebappClassLoader,context="
             + contextName + ",host=" + ctx.getParent().getName());
        Registry.getRegistry(null, null)
            .registerComponent(classLoader, cloname, null);

    } catch (Throwable t) {
        t = ExceptionUtils.unwrapInvocationTargetException(t);
        ExceptionUtils.handleThrowable(t);
        log.error( "LifecycleException ", t );
        throw new LifecycleException("start: ", t);
    }

    setState(LifecycleState.STARTING);
}

最后我们看看createClassLoader的实现:

    private WebappClassLoaderBase createClassLoader()
    throws Exception {

    Class<?> clazz = Class.forName(loaderClass);
    WebappClassLoaderBase classLoader = null;

    if (parentClassLoader == null) {
        parentClassLoader = container.getParentClassLoader();
    }
    Class<?>[] argTypes = { ClassLoader.class };
    Object[] args = { parentClassLoader };
    Constructor<?> constr = clazz.getConstructor(argTypes);
    classLoader = (WebappClassLoaderBase) constr.newInstance(args);

    return classLoader;

}

至此Tomcat类加载完毕。

© 著作权归作者所有

火龙战士

火龙战士

粉丝 120
博文 138
码字总数 101234
作品 0
北京
后端工程师
私信 提问
Tomcat源码学习(二)--Tomcat_7.0.70 启动分析

项目build成功后,刷新整个项目,会发现多出一个output目录: 为了让应用跑起来,可以检查一下outputbuildconf下是否已经有配置文件,这些文件实际是从项目根路径conf目录下拷贝过来的。 找到...

火龙战士
2016/07/01
166
0
Tomcat7.0源码分析——类加载体系

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/beliefer/article/details/50995516 前言Tomcat遵循J2EE规范,实现了Web容器。很多有关web的书籍和文章都离不...

泰山不老生
2016/03/28
0
0
Java Web开发入门 - 第3章 Tomcat

Tomcat安装与运行 Web服务器完成底层的网络处理,包括HTTP协议报文格式的编解码、管理具体web请求处理线程等操作。 Tomcat目前最流行最常见的基于Java的web应用服务器软件。 Tomcat Apache ...

抢小孩糖吃
2016/08/19
177
0
线上环境跑数据接口出现org.springframework.boot.web.support.ErrorPageFilter

20:24:52.905 [http-bio-882-exec-4400] ERROR org.springframework.boot.web.support.ErrorPageFilter - Forwarding to error page from request [/V24] due to exception [Could not parse......

我要五个字
2017/05/26
4.1K
0
tomcat 启动报错

新手求教。以前建的web项目文件夹,刚刚整理,删除了一些过去的servlet文件。之后发现之前可以正常显示的jsp页面都报404了。注意是所有的jsp页面。然后我发现tomcat启动也报错 八月 18, 2016...

加冕为王
2016/08/18
737
2

没有更多内容

加载失败,请刷新页面

加载更多

UAVStack功能上新:新增JVM监控分析工具

UAVStack推出的JVM监控分析工具提供基于页面的展现方式,以图形化的方式展示采集到的监控数据;同时提供JVM基本参数获取、内存dump、线程分析、内存分配采样和热点方法分析等功能。 引言 作为...

宜信技术学院
12分钟前
3
0
MySQL的5种时间类型的比较

日期时间类型 占用空间 日期格式 最小值 最大值 零值表示 DATETIME 8 bytes YYYY-MM-DD HH:MM:SS 1000-01-01 00:00:00 9999-12-31 23:59:59 0000-00-00 00:00:00 TIMESTAMP 4 bytes YYYY-MM......

物种起源-达尔文
19分钟前
4
0
云服务OpenAPI的7大挑战,架构师如何应对?

阿里妹导读:API 是模块或者子系统之间交互的接口定义。好的系统架构离不开好的 API 设计,而一个设计不够完善的 API 则注定会导致系统的后续发展和维护非常困难。比较好的API设计样板可以参...

阿里云官方博客
22分钟前
1
0
Rancher + VMware PKS实现全球数百站点的边缘K8S集群管理

Sovereign Systems是一家成立于2007年的技术咨询公司,帮助客户将传统数据中心技术和应用程序转换为更高效的、基于云的技术平台,以更好地应对业务挑战。曾连续3年提名CRN,并且在2012年到2...

RancherLabs
27分钟前
4
0
6、根据坐标,判断该坐标是否在地图区域范围内

最近在写配送区域相关的代码,具体需求如下: 根据腾讯地图划分配送区域,总站下边设多个配送分站,然后将订单中的收货地址将其分配给不同的配送分站。 1、地图区域划分(腾讯地图) 1.1、H...

有一个小阿飞
29分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部