Tomcat源码阅读(初窥)

Posted Narule

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat源码阅读(初窥)相关的知识,希望对你有一定的参考价值。

第一次工作的时候,公司项目就是用Tomcat在来运行项目的,用于小项目服务,启动快,使用方便。
Tomcat是一个服务器项目,由Apache组织维护,开源代码;
tomcat连接前端页面与后端,将一个个浏览器的请求发送给server,server接收请求处理完再放回给前端处理结果
这一步步是怎么做到的,肯定是不一般的设计,因此就有想对Tomcat深入了解的想法。

Tomcat的启动运行

启动方式

windows系统环境启动

startup.bat  ## 运行startup.bat 文件

linux系统环境启动

netstat -tnlp # 查看网络端口占用情况
startup.sh  # 运行 startup.sh脚本文件

启动脚本

1.确定java的jre环境 JAVA_HOME
2.确定tomcat的家,默认跟路径 CATALINA_HOME
3.用java命令运行Bootstrap.java 的main方法,设置全局变量,启动Tomcat

启动类-BootStrap.java

tomcat服务器的启动类,main方法中启动tomcat
BootStrap类的main方法中主要做了:
1.初始化类加载器
2.设置tomcat上下文类加载器
3.创建Catalina实例
4.调用catalina的load方法初始化lifecycle等基础组件
5.调用catalina的start方法启动tomcat

main方法

代码:

/**
     * Main method and entry point when starting Tomcat via the provided
     * scripts.
     *
     * @param args Command line arguments to be processed
     */
    public static void main(String args[]) {

        if (daemon == null) {
            // Don't set daemon until init() has completed
            Bootstrap bootstrap = new Bootstrap();
            try {
                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")) {
                daemon.setAwait(true);
                daemon.load(args);
                daemon.start();
                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 instanceof InvocationTargetException &&
                    t.getCause() != null) {
                t = t.getCause();
            }
            handleThrowable(t);
            t.printStackTrace();
            System.exit(1);
        }

    }

1.判断deamon 是否为空,为空则需要初始化

Bootstrap bootstrap = new Bootstrap();
try {
    bootstrap.init();
} catch (Throwable t) {
    handleThrowable(t);
    t.printStackTrace();
    return;
}
daemon = bootstrap;

2.根据启动命令参数决定 deamon的下一步动作

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

3.load为启动做准备,然后start开始运行
很明显,如果启动命令没有任何参数, Tomcat直接默认启动

else if (command.equals("start")) {
    daemon.setAwait(true);
    daemon.load(args);
    daemon.start();
    if (null == daemon.getServer()) {
        System.exit(1);
    }
}

init() 类加载初始设置

/**
 * Initialize daemon.
 * @throws Exception Fatal initialization error
 */
public void init() throws Exception {

    initClassLoaders();

    Thread.currentThread().setContextClassLoader(catalinaLoader);

    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.getConstructor().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;

}
初始化类加载器

initClassLoaders();
初始化 Tomcat自己定义的三种类加载器

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);
    }
}

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

    String[] repositoryPaths = getPaths(value);

    for (String repository : repositoryPaths) {
        // 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);
}

在初始化的时候,
1.创建了三个类加载器,commonLoadercatalinaLoadersharedLoader
类加载器:加载类文件的类
所以创建三个类加载器,就是Tomcat要实现对类文件进行一套自己的管理方案的具体方法(区别于Java默认的类文件加载)
2.设置线程上下文类加载器Thread.currentThread().setContextClassLoader(catalinaLoader);
上下文类加器

配置类加载环境

上写文类加载器
Thread.currentThread().setContextClassLoader(catalinaLoader);
设置自己的安全类加载器
SecurityClassLoad.securityClassLoad(catalinaLoader);

创建CataLina实例(org.apache.catalina.startup.Catalina)
// 1.反射创建CataLina对象
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();

// 2.反射获取setParentClassLoader方法对象
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);
// 3.反射设置Catalina的父类加载器为sharedLoader
method.invoke(startupInstance, paramValues);

// 4.将Catalina赋值给CatalinaDaemon 用于后续服务启动关闭重启的关键对象
catalinaDaemon = startupInstance;

daemon.load(args) 初始化Tomcat各组件(生命周期为主)

/**
 * Load daemon.
 */
private 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;
    }
    Method method =
        catalinaDaemon.getClass().getMethod(methodName, paramTypes);
    if (log.isDebugEnabled())
        log.debug("Calling startup class " + method);
    method.invoke(catalinaDaemon, param);

}

Catalina

load() 方法初始化Tomcat组件

BootStrap.java 类中 daemon.load(args) 方法最后通过反射调用了catalina实例对象的load方法
主要就是通过反射执行catalina对象的load方法(org.apache.catalina.startup.Catalina),load代码有点多,稍微了解下:

/**
 * Start a new server instance.
 */
public void load() {

    if (loaded) {
        return;
    }
    loaded = true;

    long t1 = System.nanoTime();

    initDirs();

    // Before digester - it may be needed
    initNaming();
        // 1.创建Digester对象,用于后面的解析Server.xml 文件,创建StandardServer等生命周期对象
    // Create and execute our Digester
    Digester digester = createStartDigester();

    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);
            }
        }
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                    .getResourceAsStream(getConfigFile());
                inputSource = new InputSource
                    (getClass().getClassLoader()
                     .getResource(getConfigFile()).toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail",
                            getConfigFile()), e);
                }
            }
        }

        // This should be included in catalina.jar
        // Alternative: don't bother with xml, just create it manually.
        if (inputStream == null) {
            try {
                inputStream = getClass().getClassLoader()
                        .getResourceAsStream("server-embed.xml");
                inputSource = new InputSource
                (getClass().getClassLoader()
                        .getResource("server-embed.xml").toString());
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.debug(sm.getString("catalina.configFail",
                            "server-embed.xml"), e);
                }
            }
        }


        if (inputStream == null || inputSource == null) {
            if  (file == null) {
                log.warn(sm.getString("catalina.configFail",
                        getConfigFile() + "] or [server-embed.xml]"));
            } else {
                log.warn(sm.getString("catalina.configFail",
                        file.getAbsolutePath()));
                if (file.exists() && !file.canRead()) {
                    log.warn("Permissions incorrect, read permission is not allowed on the file.");
                }
            }
            return;
        }

        try {
            inputSource.setByteStream(inputStream);
            digester.push(this); // 2.cataLina与digester关联,设置cataLina为root
            digester.parse(inputSource); // 3 解析文件并在这里创建server listener  Connector Executor的实例
        } catch (SAXParseException spe) {
            log.warn("Catalina.start using " + getConfigFile() + ": " +
                    spe.getMessage());
            return;
        } catch (Exception e) {
            log.warn("Catalina.start using " + getConfigFile() + ": " , e);
            return;
        }
    } finally {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                // Ignore
            }
        }
    }

    getServer().setCatalina(this); // 4.获取StandardServer对象,并与catalina关联
    getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
    getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());

    // Stream redirection
    initStreams();

    // Start the new server
    try {
        getServer().init(); // 5.standardServer初始化,下面一行代码是LifecycleException,说明方法与生命周期初始化有关 
    } 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"); // 6.打印记录Tomcat启动时初始化的时间
    }
}

daemon.start();

/**
 * Start a new server instance.
 */
public void start() {

    if (getServer() == null) {
        load(); //1 判断是否创建Server实例 , 没有调用load方法试图创建Server实例
    }

    if (getServer() == null) {
        log.fatal("Cannot start server. Server instance is not configured.");
        return;
    }

    long t1 = System.nanoTime();

    // Start the new server
    try {
        getServer().start(); // 2.调用server的start()方法,这里是Server接口的实现类:StandardServer的实例
    } catch (LifecycleException e) {
        log.fatal(sm.getString("catalina.serverStartFail"), e);
        try {
            getServer().destroy();
        } catch (LifecycleException e1) {
            log.debug("destroy() failed for failed Server ", e1);
        }
        return;
    }

    long t2 = System.nanoTime();
    if(log.isInfoEnabled()) {
        log.info("Server startup in " + ((t2 - t1) / 1000000) + " ms"); //3 日志打印tomcat启动时长
    }

    // Register shutdown hook
    if (useShutdownHook) {
        if (shutdownHook == null) {
            shutdownHook = new CatalinaShutdownHook();
        }
        Runtime.getRuntime().addShutdownHook(shutdownHook);

        // If JULI is being used, disable JULI's shutdown hook since
        // shutdown hooks run in parallel and log messages may be lost
        // if JULI's hook completes before the CatalinaShutdownHook()
        LogManager logManager = LogManager.getLogManager();
        if (logManager instanceof ClassLoaderLogManager) {
            ((ClassLoaderLogManager) logManager).setUseShutdownHook(
                    false);
        }
    }

    if (await) {
        await();
        stop();
    }
}

Tomcat类

以上是关于Tomcat源码阅读(初窥)的主要内容,如果未能解决你的问题,请参考以下文章

初窥Java--2(下载Eclipse,安装tomcat插件)

mysql jdbc源码分析片段 和 Tomcat's JDBC Pool

tomcat 8.0.x源码阅读系列:源代码运行环境搭建

Tomcat 源码阅读(序)

搭建Tomcat源码阅读环境,无bug运行。答应我,和我一起学习Tomcat,不要放弃!

详解Tomcat系列-从源码分析Tomcat的启动