• tomcat源码分析一


    tomcat总体结构

    Server:接受请求并解析,完成相关任务,返回处理结果。通常情况下使用Socket监听服务器指定端口来实现该功能,一个最简单的服务设计如下:
    Start():启动服务器,打开socket连接,监听服务端口,接受客户端请求、处理、返回响应
    Stop():关闭服务器,释放资源
    缺点:请求监听和请求处理放一起扩展性很差(协议的切换 tomcat独立部署使用HTTP协议,与Apache集成时使用AJP协议
    改进一:网络协议与请求处理分离:一个Server包含多个Connector(链接器)和Container(容器)
    Connector:开启Socket并监听客户端请求,返回响应数据;
    Container:负责具体的请求处理
    缺点:Connector接受的请求由那个Container处理,需要建立映射规则
    改进二:一个Server可以包含多个Service,每一个Service都是独立的,他们共享一个JVM以及系统类库。一个Service负责维护多个Connector和一个Container,这样来自Connector的请求只能有它所属的Service维护的Container处理。
    在这里Container是一个通用的概念,为了明确功能,并与Tomcat中的组件名称相同,可以将Container命名为Engineer
    在Engine容器中需要支持管理WEB应用,当接收到Connector的处理请求时,Engine容器能够找到一个合适的Web应用来处理,因此在上面设计的基础上增加Context来表示一个WEB应用,并且一个Engine可以包含多个Context。
    缺点:应用服务器需要将每个域名抽象为一个虚拟主机
    在一个web应用中,可以包含多个Servlet实例来处理来自不同的链接请求,因此我们还需要一个组件概念来表示Servlet定义,即Wrapper。在前面的多次Container容器中,有Engine、Host、Context、Wrapper等,可以理解为Container的子类.

    tomcat源码生命周期分析:从官网上下载tomcat源码包,此次分析使用的版本tomcat-8.5.54

    一般分为两类:链接器的实现(LifecycleBase),容器的实现(container)。及相关抽象类的实现。

    BootStrap(引导程序)是Tomcat的入口。找到java目录下org.apache.catalina.startup包下的Bootstrap类。
    Main方法和static语句块:
    Static语句块的作用(看代码):在静态代码块中设置catalinaHome和catalinaBase两个路径

    static {...
    System.setProperty(Globals.CATALINA_HOME_PROP, catalinaHomeFile.getPath());//Globals.CATALINA_HOME_PROP="catalina.home",我们可以通过设置vm参数的形式传入:editConfig:-Dcatalina.home=catalina-home -Dcatalina.base=catalina-home
    // Then base
    String base = System.getProperty(Globals.CATALINA_BASE_PROP);...
    }
    

    mian方法的作用:实例化BootStrap 初始化BootStrap daemon.load(args) daemon.start();

    main{...
        try {
            bootstrap.init();
        }...
        try {
                String command = "start";
                if (command.equals("startd")) {...
                } 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")) {...
        }...
    }
    

    我们先分析init方法: 初始化commonLoader、catalinaLoader、sharedLoader,设置catalinaDaemon为Catalina实例

    init(){...
        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);
            ...
            };// 初始化commonLoader、catalinaLoader、sharedLoader 
       
            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);//此处实际是调用Catalina相关方法
            catalinaDaemon = startupInstance;
    }
    

    load、start实际上是由Bootstrap反射调用Catalina的load、start,这一部分代码将在下面的Catalina部分进行分析。load阶段主要是通过读取conf/server.xml或者server-embed.xml,实例化Server、Service、Connector、Engine、Host等组件,并调用Lifecycle#init()完成初始化动作,以及发出INITIALIZING、INITIALIZED事件

    load() {...
    initDirs();//初始化jmx的环境变量
    initNaming();// Before digester - it may be needed 初始化命名空间
    Digester digester = createStartDigester();//Create and execute our Digester 定义解析server.xml的配置,告诉Digester哪个xml标签应该解析成什么类
    ...
     // 如果还是加载不到xml,则直接return,省略部分代码......
          try {
              inputSource.setByteStream(inputStream);
              // 把Catalina作为一个顶级实例
              digester.push(this);
              // 解析过程会实例化各个组件,比如Server、Container、Connector等
              digester.parse(inputSource);
          } catch (SAXParseException spe) {
              // 处理异常......
          }
        } finally {
            // 关闭IO流......
        }
        // 给Server设置catalina信息
        getServer().setCatalina(this);
        getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
        getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
        // Stream redirection
        initStreams();
        // 调用Lifecycle的init阶段
        try {
            getServer().init();//初始化Server,找到具体实现类方法
        } catch (LifecycleException e) {
            // ......
        }
        // ......
    }
    StandardServer.initInternal(){...//组件都继承了LifeCycleBase,该类中的init会调用initInternal()由子类具体去实现
    // Initialize our defined Services
            for (int i = 0; i < services.length; i++) {
                services[i].init();//初始化service
            }...
    }
    StandardService.initInternal(){...
    engine.init();//需要注意的是此处engine初始化后并没有接着初始化子类host、context、wapper这几个是在star生命周期初始启动的
    executor.init();
    mapperListener.init();
    connector.init();...//依次类推查看其他实现类的初始化
    }
    

    在connector->ProtocolHandler->AbstractProtocol调用init初始化的时候,还会去执行AbstractEndpoint的init方法,完成请求端口绑定、初始化NIO等操作

    AbstractProtocol.init(){...
    String endpointName = getName();
            endpoint.setName(endpointName.substring(1, endpointName.length()-1));
            endpoint.setDomain(domain);
            endpoint.init();...
    }
    AbstractEndpoint.init() throws Exception {
            if (bindOnInit) {
                bind(){
    }
    NioEndpoint.bind(){
            if (!getUseInheritedChannel()) {
                serverSock = ServerSocketChannel.open();
                socketProperties.setProperties(serverSock.socket());
                InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
                serverSock.socket().bind(addr,getAcceptCount());
    

    Engine初始化StandardEngine在init阶段,需要获取Realm(域)是用于对单个用户进行身份验证的底层安全领域的只读外观,并标识与这些用户相关联的安全角色。域可以在任何容器级别上附加,但是通常只附加到Context,或者更高级别的容器。

  • 相关阅读:
    PHP时间戳常用转换
    redis基本指令
    P2501 [HAOI2006]数字序列
    P2679 子串
    P2759 奇怪的函数
    P6823 「EZEC-4」zrmpaul Loves Array
    P6631 [ZJOI2020] 序列
    P2887 [USACO07NOV]Sunscreen G
    P3287 [SCOI2014]方伯伯的玉米田
    拓展欧几里得算法揭秘
  • 原文地址:https://www.cnblogs.com/leifonlyone/p/12746385.html
Copyright © 2020-2023  润新知