通过tomcat源码查看其如何实现应用相互隔离

前言

tomcat之所以创造了一堆自己的classloader体系,其目的之一就是为了实现应用的相互隔离,而对于许多应用,需要有共享的lib以便不浪费资源。那么,tomcat是如何实现这些功能的呢?通过tomcat源码,我会为大家详细介绍

tomcat的classloader体系

tomcat的具体的classloader体系如下图

这里写图片描述

BootstrapClassLoader,ExtensionClassLoader,SystemClassLoader这三个类我就不多介绍了,具体可参考我的《tomcat类加载体系》这篇博客

从Bootstrap的init开始

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

                            代码清单1
/**
     * Initialize daemon.
     */


     public void init()
        throws Exception
    {

        // Set Catalina path
        setCatalinaHome();
        setCatalinaBase();
        //初始化commonLoader、catalinaLoader和sharedLoader;
        initClassLoaders();
        //将catalinaLoader设置为Tomcat主线程的线程上下文类加载器;
        Thread.currentThread().setContextClassLoader(catalinaLoader);
        //线程安全的加载class。 
        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;

    }

其中有关类加载器的执行步骤如下:

  1. 初始化commonLoader、catalinaLoader和sharedLoader;
  2. 将catalinaLoader设置为Tomcat主线程的线程上下文类加载器;
  3. 线程安全的加载class。

再之后就是加载Catalina,设置catalinaDaemon参数

初始化类加载器initClassLoaders()

initClassLoaders方法的实现如代码清单2:

                            代码清单2
    private void initClassLoaders() {
        try {
            commonLoader = createClassLoader("common", null);
            if( commonLoader == null ) {
//no config file, default to thisloader- 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);
        }
    }

我们可以看到initClassLoaders调用createClassLoader方法来创建commonLoader、catalinaLoader和sharedLoader,我们来看看createClassLoader的实现,其代码清单3:

                            代码清单3
    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);

        // 省略无关代码

        return classLoader;

    }

createClassLoader的处理步骤如下:

  1. 定位资源路径与资源类型;
  2. 使用ClassLoaderFactory创建类加载器org.apache.catalina.loader.StandardClassLoader,这个StandardClassLoader仅仅继承了URLClassLoader而没有其他更多改动。

需要注意的是,Tomcat默认只会指定commonLoader,catalinaLoader和sharedLoader实际也是commonLoader。(在catalina.properties配置文件中,我们可以看到common属性默认值为{catalina.base}/lib/.jar,{catalina.home}/lib/.jar,如下配置所示,属性catalina.home默认为Tomcat的根目录。)

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

tomcat将类加载进来

到现在可能会有疑问:tomcat如何将自己和webapp的所有类用自己的classloader加载进来呢?现在已经有了classloader,那么tomcat是怎么讲自己和webapp的所有类用自己的classloader加载进来呢? 是否需要有个专门的地方遍历所有的类并将其加载,可是代码里并不能找到这样的地方。而且相对来说,将不用的类显式的加载进来也是一种浪费,那么,tomcat(或者说jvm)是如何做到这点呢?

这里有个隐式加载的问题,所谓的隐式加载,就是指在当前类中所有new的对象,如果没有被加载,则使用当前类的类加载器加载,即this.getClass(),getClassLoader()会默认加载该类中所有被new出来的对象的类(前提是他们没在别处先被加载过)。从这里思考,我们一个一个的应用,本质上是什么样子,事实上,正如所有程序都有一个main函数一样,所有的应用都有一个或多个startup的类(即入口),这个类是被最先加载的,并且随后的所有类都像树枝一样以此类为根被加载,只要控制了加载该入口的classloader,等于就控制了所有其他相关类的classloader。

搞明白这一点,再来看Bootstap的init代码(代码清单1),我们可以发现, 在catalinaLoader.loadClass之后,Catalina事实上就由 catalinaClassLoader(其实就是StandardClassLoader)加载进来了,而下一句newInstance时,所有以Catalina为根的对象的类也会全部被隐式加载进来。这样,tomcat就用自己的classloader把类加载进来。

可能有人又会问:为什么控制了classloader,就控制了其他类呢?这里因为,jvm表示一个是否是同一个类有两个条件:(1)这个类的完整类名是否一样(这个类名包括类所在的包名);(2)加载这个类的Classloader是否是同一个(这里所有的同一个是指classloader的实例是否是同一个实例,如果是同一个Classloader类的两个实例,加载同一个类也会不一样)

( ps:可以利用这个性质实现类的热部署,我们知道,JVM在加载类之前会检查请求的类是否已经被加载过来,也就是要调用findLoadedClass()方法查看是否能够返回类实例。如果类已经加载过来,再调用loadClass() 将会导致类冲突,所以要实现类的热部署,可以创建不同的ClassLoader的实例对象,然后通过这个不同的实例对象来加载同名的类,参看《 深入分析java web技术内幕》中第6章 –深入分析ClassLoader工作机制中“实现类的热部署”一节。当然tomcat的热部署并不是这么实现的,tomcat的实现机制可以参看《 通过tomcat源码查看tomcat如何实现热部署》)

tomcat隔离不同的应用

那么,tomcat又是如何隔离不同的webapp的加载呢?

对于每个webapp应用,都会对应唯一的StandContext,在StandContext中会引用WebappLoader,该类又会引用WebappClassLoader,WebappClassLoader就是真正加载webapp的classloader。下面我们通过tomcat代码进一步体会一下:

启动StandardContext的时候会创建WebappLoader,启动StandardContext的startInternal的方法实现见代码清单4

                            代码清单4
    /**
    * 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();
      // 省略后边无关的代码
    }

代码清单4的最后会调用WebappLoader的start方法,start又调用了startInternal方法,WebappLoader的startInternal的实现见代码清单5。

                            代码清单5
    /**
    * 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 {

        // 省略无关代码// 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]);
            }

最后我们看看WebappLoader的createClassLoader方法的实现,见代码清单6。

                            代码清单6
    /**
    * 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;

    }

parentClassLoader实际就是sharedLoader,即org.apache.catalina.loader.StandardClassLoader。由此也证实了WebappClassLoader的父类加载器是sharedLoader。至此,整个Tomcat的类加载体系构建完毕。最后我们看看WebappClassLoader(见代码清单7)是如何实现以及部署在tomcat中的各个webapp的资源是如何隔离的

                            代码清单7
    @Override
    public synchronized Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {

        if (log.isDebugEnabled())
            log.debug("loadClass(" + name + ", " + resolve + ")");
        Class<?> clazz = null;

        // Log access to stopped classloader
        if (!started) {
            try {
                throw new IllegalStateException();
            } catch (IllegalStateException e) {
                log.info(sm.getString("webappClassLoader.stopped", name), e);
            }
        }

        // (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);

        // (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 = Class.forName(name, false, loader);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        // (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) {
            // Ignore
        }

        // (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 = Class.forName(name, false, loader);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from parent");
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        throw new ClassNotFoundException(name);

    }

从代码清代7中,可以看到WebappClassLoader加载class的步骤如下:

  1. 先检查webappclassloader的缓冲容器是否有该类
  2. 为防止webapp覆盖j2se类,尝试用systemclassloader加载
  3. 进行安全性检查
  4. 通过检查后,判断delegate的值来决定是否委托给父类加载器(默认是否)
  5. 通过WebappClassLoader自己加载class
  6. 最后无条件地委托给父加载器
  7. 如果都没有加载成功,则抛出ClassNotFoundException异常

至此,相信大家对webapp应用,StandContext,WebappLoader, WebappClassLoader之间的关系有了更深的了解。tomcat如何实现webapp之间的隔离,大家应该已经明白了: 不同的StandardContext有不同的WebappClassLoader,那么不同的webapp的类装载器就是不一致的。装载器的不一致带来了名称空间不一致,所以webapp之间是相互隔离的。

tomcat加载不同应用

或许大家还有个疑问,tomcat是如何利用WebappClassLoader来加载不同的应用呢?

StandContext的管理隶属于Lifecycle,在start方法中会做一系列准备工作,比如新建WebappClassLoader,另外loadOnStartup便会加载所有配置好的servlet(每个StandardWrapper负责管理一个servlet),这里同样的一个问题是,在我们自己写的web应用程序中,入口是什么?答案就是Servlet, Listener, Filter这些组件,如果我们控制好入口的classloader,便等于控制了其后所加载的全部类,那么,tomcat是如何控制的呢?且看StandardWrapper中一个重要的方法loadServlet(代码清单8),getLoader()事实上调用到了StandContext中保存的WebappLoader,于是,用该loader加载Servlet,从而控制住了Servlet中所有待加载的类。

- public synchronized Servlet loadServlet() throws ServletException {
-
-         ...
-
-         Servlet servlet;
-         try {
-             ...
-
-             // Acquire an instance of the class loader to be used
-             Loader loader = getLoader();
-             if (loader == null) {
-                 unavailable(null);
-                 throw new ServletException
-                     (sm.getString("standardWrapper.missingLoader", getName()));
-             }
-
-             ClassLoader classLoader = loader.getClassLoader();
-
-             // Special case class loader for a container provided servlet
-             //
-             if (isContainerProvidedServlet(actualClass) &&
-                     ! ((Context)getParent()).getPrivileged() ) {
-                 // If it is a priviledged context - using its own
-                 // class loader will work, since it's a child of the container
-                 // loader
-                 classLoader = this.getClass().getClassLoader();
-             }
-
-             // Load the specified servlet class from the appropriate class loader
-             Class classClass = null;
-             try {
-                 if (SecurityUtil.isPackageProtectionEnabled()){
-                     ...
-                 } else {
-                     if (classLoader != null) {
-                         classClass = classLoader.loadClass(actualClass);
-                     } else {
-                         classClass = Class.forName(actualClass);
-                     }
-                 }
-             } catch (ClassNotFoundException e) {
-                 unavailable(null);
-                 getServletContext().log( "Error loading " + classLoader + " " + actualClass, e );
-                 throw new ServletException
-                     (sm.getString("standardWrapper.missingClass", actualClass),
-                      e);
-             }
-
-             if (classClass == null) {
-                 unavailable(null);
-                 throw new ServletException
-                     (sm.getString("standardWrapper.missingClass", actualClass));
-             }
-
-             // Instantiate and initialize an instance of the servlet class itself
-             try {
-                 servlet = (Servlet) classClass.newInstance();
-                 // Annotation processing
-                 if (!((Context) getParent()).getIgnoreAnnotations()) {
-                     if (getParent() instanceof StandardContext) {
-                        ((StandardContext)getParent()).getAnnotationProcessor().processAnnotations(servlet);
-                        ((StandardContext)getParent()).getAnnotationProcessor().postConstruct(servlet);
-                     }
-                 }
-             } catch (ClassCastException e) {
-                 ...
-             } catch (Throwable e) {
-                 ...
-             }
-
-             ...
-         return servlet;
-
-     }

参考

http://www.cnblogs.com/jiaan-geng/p/4860432.html
http://blog.csdn.net/liweisnake/article/details/8470285

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页