Tomcat8 source parsing

Overall Tomcat Architecture

Connector: Opens the Socket, listens to client requests, and returns response data. Container: handles specific requests;

A Service maintains multiple Connectors and a Container. Requests from the Connector can only be handled by the Container maintained by the Service to which the Service belongs.

Engine: represents the entire servlet Engine. Host: represents a virtual Host. Context: represents an application

Tomcat source code construction

  1. Tomcat software and source code file download link: tomcat.apache.org/download-80…
  1. Create a tomcat directory folder to store the downloaded files, and then create a pom.xml file. Then use idea to open the Tomcat directory.
  • The pom. XML file is as follows
<? xml version="1.0" encoding="UTF-8"? > <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0. 0</modelVersion>

    <groupId>com.apache.tomcat</groupId>
    <artifactId>tomcat</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>tomcat</name>
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.7</maven.compiler.source>
        <maven.compiler.target>1.7</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.easymock</groupId>
            <artifactId>easymock</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.ant</groupId>
            <artifactId>ant</artifactId>
            <version>1.101.</version>
        </dependency>
        <dependency>
            <groupId>wsdl4j</groupId>
            <artifactId>wsdl4j</artifactId>
            <version>1.62.</version>
        </dependency>
        <dependency>
            <groupId>javax.xml</groupId>
            <artifactId>jaxrpc</artifactId>
            <version>1.1</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jdt</groupId>
            <artifactId>org.eclipse.jdt.core</artifactId>
            <version>3.13. 0</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jdt.core.compiler</groupId>
            <artifactId>ecj</artifactId>
            <version>4.51.</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.61.</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
Copy the code
  1. The idea Settings are as shown in the figure below:
  1. Finally start. The access path is http://localhost:8080/

Tomcat source code Analysis

1. The Tomcat initialization phase
# 1.Bootstrap starts the main method of the classpublic static void main(String args[]) {
  if (daemon == null) {
        // Instantiate BootStrap
        Bootstrap bootstrap = new Bootstrap();
        try {
            // Initialize BootStrap
            bootstrap.init();
        } catch (Throwable t) {
            handleThrowable(t);
            t.printStackTrace();
            return;
        }
        daemon = bootstrap;
    } else {
        // When running as a service the call to stop will be on a new
        // thread so make sure the correct class loader is used to prevent
        // a range of class not found exceptions.
        Thread.currentThread().setContextClassLoader(daemon.catalinaLoader);
    }

    try {
        String command = "start";
        if (args.length > 0) {
            command = args[args.length - 1];
        }

        if (command.equals("startd")) {
            args[args.length - 1] = "start";
            daemon.load(args);
            daemon.start();
        } else if (command.equals("stopd")) {
            args[args.length - 1] = "stop";
            daemon.stop();
        } else if (command.equals("start")) {
            // If the command is start, perform the following operations
            daemon.setAwait(true); // In order for Tomcat to block listening to the shutdown command on the shutdown port
            daemon.load(args);     // Actually call the catalina.load() method to initialize the server, service, engine, executor, connector
            daemon.start();        // Actually call catalina.start() to start the server, service, engine, executor, connector; Host,Context,Wrapper
            if (null == daemon.getServer()) {
                System.exit(1); }}else if (command.equals("stop")) {
            daemon.stopServer(args);
        } else if (command.equals("configtest")) {
            daemon.load(args);
            if (null == daemon.getServer()) {
                System.exit(1);
            }
            System.exit(0);
        } else {
            log.warn("Bootstrap: command \"" + command + "\" does not exist."); }}catch (Throwable t) {
        // Unwrap the Exception for clearer error reporting
        if (t instanceofInvocationTargetException && t.getCause() ! =null) {
            t = t.getCause();
        }
        handleThrowable(t);
        t.printStackTrace();
        System.exit(1); }} #2.Initialize theBootStrap
public void init(a) throws Exception {
    // Initialize the class loader
    initClassLoaders();

    // The current thread sets the context classloader
    Thread.currentThread().setContextClassLoader(catalinaLoader);

    // Set the class loader for the security mechanism
    SecurityClassLoad.securityClassLoad(catalinaLoader);

    // Load our startup class and call its process() method
    if (log.isDebugEnabled())
        log.debug("Loading startup class");
    // Load Catalina classClass<? > startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
    // Create an instance of Catalina using the constructor of class Catalina
    Object startupInstance = startupClass.getConstructor().newInstance();

    // Run Catalina's setParentClassLoader method to set the parent 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);

    / / set catalinaDaemon
    catalinaDaemon = startupInstance;
}

# 3.Initialize the class loaderprivate void initClassLoaders(a) {
    try {
        // The parent class loader of commonLoader is set to NULL, breaking the parent delegation mechanism.
        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();
        }
        // Set the parent class loader for catalinaLoader and sharedLoader to commonLoader
        catalinaLoader = createClassLoader("server", commonLoader);
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1); }} #4.daemon.load(args); Initialization operationprivate void load(String[] arguments)
    throws Exception {

    // Call the load() method
    String methodName = "load"; Object param[]; Class<? > paramTypes[];if (arguments==null || arguments.length==0) {
        paramTypes = null;
        param = null;
    } else {
        paramTypes = new Class[1];
        paramTypes[0] = arguments.getClass();
        param = new Object[1];
        param[0] = arguments;
    }
    // Execute catalina's load() method.
    Method method =
        catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    if (log.isDebugEnabled())
        log.debug("Calling startup class " + method);
    method.invoke(catalinaDaemon, param);

}

Copy the code

The main method of the Bootstrap class is used to start tomcat. 1. Initialize Bootstrap to create catalina objects. Initialize the Tomcat class loader. 2. Call the load method of Bootstrap to initialize tomcat components.

  • The catalina.load() method is executed, followed by source trace analysis
/** * will initialize some resources and load conf/server.xml first. * In addition, the load method initializes the Server */
public void load(a) {
    if (loaded) {
        return;
    }
    loaded = true;
    long t1 = System.nanoTime();

    // Initialize the directory
    initDirs();

    // Initialize the namespace
    initNaming();

    // Parser to parse the server.xml file
    Digester digester = createStartDigester();

    // Read the server.xml file
    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    try {
        try {
            file = configFile();
            inputStream = new FileInputStream(file);
            inputSource = new InputSource(file.toURI().toURL().toString());
        } catch (Exception e) {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("catalina.configFail", file), e); }}try {
            inputSource.setByteStream(inputStream);
            digester.push(this);

            // Start parsing the server.xml file (emphasis)digester.parse(inputSource); }}// Set the server Catalina information
    getServer().setCatalina(this);
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // Stream redirection
    initStreams();

    // Start the new server
    try {

        // Initialize the Server. This init method is the method of the superclass LifecycleBase (emphasis).
        getServer().init();
    } catch (LifecycleException e) {
        if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
            throw new java.lang.Error(e);
        } else {
            log.error("Catalina.start", e); }}long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {
        log.info("Initialization processed in " + ((t2 - t1) / 1000000) + " ms"); }}Copy the code

The catalina.load method does two important things: 1. Parse the server.xml file. 2. Initialize the Server.

  • The execution of the getServer().init() method initializes the Server, source analysis
# 1.LifecycleBase init() method@Override
public final synchronized void init(a) throws LifecycleException {
    if(! state.equals(LifecycleState.NEW)) { invalidTransition(Lifecycle.BEFORE_INIT_EVENT); }try {
        setStateInternal(LifecycleState.INITIALIZING, null.false);
        // Call the initInternal method of the subclass (important)
        initInternal();
        setStateInternal(LifecycleState.INITIALIZED, null.false);
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        setStateInternal(LifecycleState.FAILED, null.false);
        throw new LifecycleException(
                sm.getString("lifecycleBase.initFail",toString()), t); }} #2.StandardServer's initInternal() method@Override
protected void initInternal(a) throws LifecycleException {

     super.initInternal();

     // Register global String cache
     // Note although the cache is global, if there are multiple Servers
     // present in the JVM (may happen when embedding) then the same cache
     // will be registered under multiple names
     onameStringCache = register(new StringCache(), "type=StringCache");

     / / register JMX
     // Register the MBeanFactory
     MBeanFactory factory = new MBeanFactory();
     factory.setContainer(this);
     onameMBeanFactory = register(factory, "type=MBeanFactory");

     // Register the naming resources
     globalNamingResources.init();

     // Get the class loader
     // Populate the extension validator with JARs from common and shared
     // class loaders
     if(getCatalina() ! =null) {
         ClassLoader cl = getCatalina().getParentClassLoader();
         // Walk the class loader hierarchy. Stop at the system class loader.
         // This will add the shared (if present) and common class loaders
         while(cl ! =null&& cl ! = ClassLoader.getSystemClassLoader()) {if (cl instanceof URLClassLoader) {
                 URL[] urls = ((URLClassLoader) cl).getURLs();
                 for (URL url : urls) {
                     if (url.getProtocol().equals("file")) {
                         try {
                             File f = new File (url.toURI());
                             if (f.isFile() &&
                                     f.getName().endsWith(".jar")) { ExtensionValidator.addSystemResource(f); }}catch (URISyntaxException e) {
                             // Ignore
                         } catch (IOException e) {
                             // Ignore} } } } cl = cl.getParent(); }}// Initialize our defined Services
     for (int i = 0; i < services.length; i++) {
         // Initialize services (emphasis)services[i].init(); }}Copy the code

Server initializes init() by calling the superclass LifecycleBase init() method and then the subclass Server initInternal() method. Init –> initInternal; services, Connector, engine are initialized in the same way.

  • Services [I].init()
# 1.StandardService's initInternal() method@Override
protected void initInternal(a) throws LifecycleException {

    super.initInternal();

    // Initialize engine (emphasis)
    if(engine ! =null) {
        engine.init();
    }

    // Initialize the thread pool
    // Initialize any Executors
    for (Executor executor : findExecutors()) {
        if (executor instanceof JmxEnabled) {
            ((JmxEnabled) executor).setDomain(getDomain());
        }
        executor.init();
    }

    // Initialize the Mapper mapping listener
    // Initialize mapper listener
    mapperListener.init();

    // Initialize our defined Connectors
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            try {
                // Initialize the connector (key)
                connector.init();
            } catch (Exception e) {
                String message = sm.getString(
                        "standardService.connector.initFailed", connector);
                log.error(message, e);

                if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"))
                    throw newLifecycleException(message); }}}}Copy the code

From the initialization analysis of the services, you can see that the initialization of the services will initialize the Engine and connector. (executor, mapperListener)

  • Engine. Init (), initialize the engine, source analysis
# 1.StandardEngine's initInternal() method@Override
protected void initInternal(a) throws LifecycleException {
    // Ensure that a Realm is present before any attempt is made to start
    // one. This will create the default NullRealm if necessary.
    getRealm();
    super.initInternal();
}

# 2.ContainerBase initInternal() method@Override
protected void initInternal(a) throws LifecycleException {
    BlockingQueue<Runnable> startStopQueue = new LinkedBlockingQueue<>();
    startStopExecutor = new ThreadPoolExecutor(
            getStartStopThreadsInternal(),
            getStartStopThreadsInternal(), 10, TimeUnit.SECONDS,
            startStopQueue,
            new StartStopThreadFactory(getName() + "-startStop-"));
    startStopExecutor.allowCoreThreadTimeOut(true);
    super.initInternal();
}

# 3.LifecycleMBeanBase's initInternal() method@Override
protected void initInternal(a) throws LifecycleException {
    // If oname is not null then registration has already happened via
    // preRegister().
    if (oname == null) {
        mserver = Registry.getRegistry(null.null).getMBeanServer();
        oname = register(this, getObjectNameKeyProperties()); }}Copy the code
  • Connector.init (), initializing the connector, source analysis
# 1.The connector's initInternal() method@Override
protected void initInternal(a) throws LifecycleException {

    super.initInternal();

    // Initialize the adapter
    adapter = new CoyoteAdapter(this);
    protocolHandler.setAdapter(adapter);

    // Set parseBodyMethodsSet to a default value
    if (null == parseBodyMethodsSet) {
        setParseBodyMethods(getParseBodyMethods());
    }

    if(protocolHandler.isAprRequired() && ! AprLifecycleListener.isAprAvailable()) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
                getProtocolHandlerClassName()));
    }
    if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
            protocolHandler instanceofAbstractHttp11JsseProtocol) { AbstractHttp11JsseProtocol<? > jsseProtocolHandler = (AbstractHttp11JsseProtocol<? >) protocolHandler;if (jsseProtocolHandler.isSSLEnabled() &&
                jsseProtocolHandler.getSslImplementationName() == null) {
            // OpenSSL is compatible with the JSSE configuration, so use it if APR is availablejsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName()); }}try {
        // initialize a protocolHandler (emphasis)
        protocolHandler.init();
    } catch (Exception e) {
        throw new LifecycleException(
                sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e); }}Copy the code

From the initialization analysis of the Connector, you can get the initialization of the Connector, which initializes the protocolHandler

  • Protocolhandler.init (), initialize the protocolHandler, source analysis
# 1.AbstractProtocol init() method@Override
public void init(a) throws Exception {
    if (getLog().isInfoEnabled()) {
        getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
    }

    if (oname == null) {
        // Component not pre-registered so register it
        oname = createObjectName();
        if(oname ! =null) {
            Registry.getRegistry(null.null).registerComponent(this, oname, null); }}if (this.domain ! =null) {
        rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
        Registry.getRegistry(null.null).registerComponent(
                getHandler().getGlobal(), rgOname, null);
    }

    String endpointName = getName();
    endpoint.setName(endpointName.substring(1, endpointName.length()-1));
    endpoint.setDomain(domain);

    // Initialize endpoint (key)
    endpoint.init();
}
Copy the code

From the initialization analysis of protocolHandler, we can get the initialization of protocolHandler, which will initialize the endpoint

  • Endpoint.init (), initialize endpoint, source analysis
# 1.AbstractEndpoint init() methodpublic void init(a) throws Exception {
    if (bindOnInit) {
        bind();
        bindState = BindState.BOUND_ON_INIT;
    }
    if (this.domain ! =null) {
        // Register endpoint (as ThreadPool - historical name)
        oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\" ");
        Registry.getRegistry(null.null).registerComponent(this, oname, null);

        ObjectName socketPropertiesOname = new ObjectName(domain +
                ":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties");
        socketProperties.setObjectName(socketPropertiesOname);
        Registry.getRegistry(null.null).registerComponent(socketProperties, socketPropertiesOname, null);

        for(SSLHostConfig sslHostConfig : findSslHostConfigs()) { registerJmx(sslHostConfig); }}}Copy the code

At this point, the Initialization phase of Tomcat is complete. Using the chain of responsibility pattern, step by step initialization. Component initialization sequence: Server–>Service–>Engine–>Connector–>ProtocolHandler–>Endpoint These are initialized during tomcat’s Start startup phase.