Kenan
Assistant Engineer
Assistant Engineer
  • UID621
  • Fans0
  • Follows0
  • Posts55
Reads:1742Replies:0

Tomcat source code analysis - class loading system

Created#
More Posted time:Oct 8, 2016 14:55 PM
Preface
Tomcat follows the J2EE specification and implements Web containers. Many books and articles about Web cannot go without analysis of Tomcat. New starters can start from Tomcat implementation to know J2EE better. In addition, Tomcat also implements the classic class loading system of double-parent delegation mode according to the Java virtual machine specifications. This article is based on the Java source code of Tomcat7.0 and analyzes its class loading system.
Overview
First, let’s introduce the major class loaders in the Java virtual machine specifications;
• Bootstrap Loader: load the path or jar in the lib directory, or System.getProperty(“sun.boot.class.path”), or that specified by -XBootclasspath.
• Extended Loader: load the path or jar in the lib\ext directory or that specified by System.getProperty(“java.ext.dirs”). You can also specify the search path when running a program using Java, for example: java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld.
• AppClass Loader: load the path or jar specified by System.getProperty(”java.class.path”). You can add the -cp to overwrite the original Classpath settings when running a program using Java, for example: java -cp ./lavasoft/classes HelloWorld.
Next, let’s look at the Tomcat class loading system with a picture:


Here we will introduce the class loading system shown in the figure above:
ClassLoader: the class loader abstract class provided by Java. Your custom class loader requires inheritance implementation;
commonLoader: the most basic class loader in Tomcat. The class in the loading path can be accessed by the Tomcat container and various Webapp;
catalinaLoader: the private class loader of Tomcat container, and the class in the loading path is invisible to the Webapp;
sharedLoader: the class loader shared by various Webapps. The class in the loading path is visible to all Webapps but invisible to the Tomcat container;
WebappClassLoader: the private class loaders of various Webapps. The class in the loading path is visible only to the current Webapp.
Source code analysis
The commonLoader, catalinaLoader and sharedLoader are created by calling the init method of Bootstrap at the beginning of Tomcat container initialization. The catalinaLoader will be set to the thread context class loader on the main Tomcat thread and load the class under the Tomcat container using catalinaLoader. Some code list of the Bootstrap init method is as follows:
   /**
     * Initialize daemon.
     */
    public void init()
        throws Exception
    {

        // Set Catalina path
        setCatalinaHome();
        setCatalinaBase();

        initClassLoaders();

        Thread.currentThread().setContextClassLoader(catalinaLoader);

        SecurityClassLoad.securityClassLoad(catalinaLoader);
        // Omitting the following code


 Let’s continue to take a look at the implementation of the initClassLoaders method:
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);
        }
    }


 Implementation of the createClassLoader method for creating class loaders:
  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;

    }


The createClassLoader uses ClassLoaderFactory.createClassLoader(locations, types, parent) method to create ClassLoader. Let’s look back at the implementation of SecurityClassLoad.securityClassLoad(catalinaLoader):
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);
    }


The securityClassLoad method mainly loads the class required by the Tomcat container, including:
• Tomcat core class, that is, the class in the path of org.apache.catalina.core;
• The org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName;
• Class related to the session of Tomcat, that is, the class in the path of org.apache.catalina.session;
• Tomcat tool class, that is, the class in the path of org.apache.catalina.util;
• javax.servlet.http.Cookie;
• Tomcat class for processing requests, that is, the class in the path of org.apache.catalina.connector;
• Other Tomcat tool classes, that is, the class in the path of org.apache.catalina.util;
Taking the loadCorePackage method that loads the Tomcat core class as an example, let’s look at its implementation:
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");
    }


 So far, we haven’t seen WebappClassLoader. WebappLoader will be created when StandardContext is started, and some code of the StandardContext method startInternal is as follows:
/**
     * 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 {

        // Omitting the preceding code

        if (getLoader() == null) {
            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }
       // Omitting the code in the middle
       // Start our subordinate components, if any
       if ((loader != null) && (loader instanceof Lifecycle))
            ((Lifecycle) loader).start();
       // Omitting the following code
    }


 From the code above, we can see that the start method of WebappLoader will be called finally and the start method calls the startInternal method. The implementation of startInternal is as follows:
/**
     * 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);
            }


 At last, let’s look at the implementation of createClassLoader:
/**
     * Create associated classLoader.
     */
    private WebappClassLoader createClassLoader()
        throws Exception {
//loaderClass, that is the character stringorg.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;

    }


 So far, the entire Tomcat class loading system has been established.
Guest