Springboot内嵌式tomcat--源码分析

tomcat在springboot的创建点是和启动点如下图

spring启动后把容器包装成servletinitial 然后在servletcontext启动好后注入进去 这样spring就和tomcat融合 tomcat启动时候会创建servletcontext给Spring的ioc待着


image.png

如上图,我们是在onReFresh创建内置tomcat,在finishRefresh启动tomcat
onRefresh:初始化其他的特殊的子容器,我们这边就是ServletWebServerApplicationContext

tomcat的编码有点意思 我们获取任何一个容器的时候,在他内部都会包含获取其父容器的方法

创建tomcat

private void createWebServer() {
        WebServer webServer = this.webServer;
        ServletContext servletContext = getServletContext();
如果webServer 和servletContext 都为空
        if (webServer == null && servletContext == null) {
通过spring去加载ServletWebServerFactory
            ServletWebServerFactory factory = getWebServerFactory();
创建webServer,这边getSelfInitializer里面就是我们spring容器
            this.webServer = factory.getWebServer(getSelfInitializer());
        }
        else if (servletContext != null) {
            try {
如果servletContext 不为空,调用ServletContextInitializer数组去启动该容器,即给改servletContext配置servlet,filters,listeners context-params 和attribute
                getSelfInitializer().onStartup(servletContext);
            }
            catch (ServletException ex) {
                throw new ApplicationContextException("Cannot initialize servlet context",
                        ex);
            }
        }
把我们创建好的额webServer好servletcontext,更新到ConfigurableWebEnvironment中
前提是本身ConfigurableWebEnvironment就有这两个属性
        initPropertySources();
    }
创建webServer的过程,initializers就是spring的容器
    public WebServer getWebServer(ServletContextInitializer... initializers) {
创建tomcat
        Tomcat tomcat = new Tomcat();
创建一个临时目录个给当前webServer,并注册了一个钩子在程序退出的时候删除文件夹
        File baseDir = (this.baseDirectory != null ? this.baseDirectory
                : createTempDir("tomcat"));
设置tomcat的baseDir,baseDir是给临时文件使用的,应该是第一个被调用的方法,如果该方法没被调用
,我们默认调用系统属性system properties - catalina.base, catalina.home或者[user.dir]/tomcat.$PORT
        tomcat.setBaseDir(baseDir.getAbsolutePath());
根据protocol(默认是Http11NioProtocol)创建connector,这边的逻辑是先看下是否是http协议,然后再看看是否是AJP协议,如果都不是直接塞入协议的名字,然后下一步根据协议的名字调用class.forName
得到协议类,然后通过org.apache.catalina.STRICT_SERVLET_COMPLIANCE属性设置整个tomcat的编码是ISO_8859_1还是UTF-8
        Connector connector = new Connector(this.protocol);
给第一个service添加connector,这其中会初始化server(StandardServer)(server会初始化baseDir,设置初始化监听关闭端口为-1,这样就不会被从端口关闭,创建标准的service(StandardServer))然后绑定两者
addConnector:寻找现有的connector数组,将connector添加进去并把connector和service互相绑定,然后启动该connector
        tomcat.getService().addConnector(connector);
设置我们自定义的端口号,添加server,bindOnInit属性,添加我们的协议,添加urlEncoding,设置ssl,compression
        customizeConnector(connector);
tomcat本身设置connector
        tomcat.setConnector(connector);
关闭自动部署
        tomcat.getHost().setAutoDeploy(false);
设置backgroundProcessorDelay机制,如果backgroundProcessorDelay为正值,那么子容器的一些任务会有后台线程帮忙处理,为负值,则由当前容器一并处理。这些任务都是周期性的比如例如重新加载等。
为engine配置上Valve,设置container
        configureEngine(tomcat.getEngine());
给这个service添加额外的connector,从这可以看出connector和service的关系是多对一 container和service是1对1
        for (Connector additionalConnector : this.additionalTomcatConnectors) {
            tomcat.getService().addConnector(additionalConnector);
        }
准备一个context给engine,这里我们设置TomcatEmbeddedContext,这个是spring自己写的,然后配置好
这个context的一些属性
        prepareContext(tomcat.getHost(), initializers);
启动tomcat(即tomcat的容器被启动,但是connector没有启动)
        return getTomcatWebServer(tomcat);
    }

启动TomcatWebServer,这边主要是启动connector

public void start() throws WebServerException {,
        synchronized (this.monitor) {
如果已经启动直接返回
            if (this.started) {
                return;
            }
            try {
在上面tomcat启动的时候删除了每个service的connectors被删除,我们等spring完全加载好了,再把connectors加进去(之所以删除是因为,那时候spring还没有完全加载成功),这边加入的时候就启动了
connectors
                addPreviouslyRemovedConnectors();
获取一个connector,只启动这个connector
                Connector connector = this.tomcat.getConnector();
                if (connector != null && this.autoStart) {
寻找TomcatEmbeddedContext然后启动类似于(struts 等老框架的servlet),这些老框架的servlet一般都是用线程上下文加载器去加载,所以我们在这边先把我们的classloader替换到线程上下文,然后加载结束在替换回去 
                    startConnector();
                }
检测connectors启动是否失败了,失败了抛出异常
                checkThatConnectorsHaveStarted();
                this.started = true;
                TomcatWebServer.logger
                        .info("Tomcat started on port(s): " + getPortsDescription(true)
                                + " with context path '" + getContextPath() + "'");
            }
            catch (ConnectorStartFailedException ex) {
                stopSilently();
                throw ex;
            }
            catch (Exception ex) {
                throw new WebServerException("Unable to start embedded Tomcat server",
                        ex);
            }
            finally {
解绑TomcatEmbeddedContext于classloder
                Context context = findContext();
                ContextBindings.unbindClassLoader(context, context.getNamingToken(),
                        getClass().getClassLoader());
            }
        }
    }

prepareContext--生成TomcatEmbeddedContext

protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
        File documentRoot = getValidDocumentRoot();
        TomcatEmbeddedContext context = new TomcatEmbeddedContext();
        if (documentRoot != null) {
            context.setResources(new LoaderHidingResourceRoot(context));
        }
这个name就是我们一般定义的一个项目的访问路径
        context.setName(getContextPath());
        context.setDisplayName(getDisplayName());
这个path就是我们一般定义的一个项目的访问路径
https://www.cnblogs.com/starhu/p/5599773.html 这个地址有一些配置的信息。appBase 只是告诉tomcat 会把这个目录下的项目部署,docBase指定的项目实际位置
        context.setPath(getContextPath());
        File docBase = (documentRoot != null ? documentRoot
                : createTempDir("tomcat-docbase"));
        context.setDocBase(docBase.getAbsolutePath());
添加监听者LifecycleListener包含context和当前发送的event类型
        context.addLifecycleListener(new FixContextListener());
        context.setParentClassLoader(
                this.resourceLoader != null ? this.resourceLoader.getClassLoader()
                        : ClassUtils.getDefaultClassLoader());
修改语言的编码
        resetDefaultLocaleMapping(context);
添加其他国家的类似国际化的编码
        addLocaleMappings(context);
设置是否使用相对地址重定向
        context.setUseRelativeRedirects(false);
设置StandardJarScanFilter JarScanner 和需要跳过的tld
        configureTldSkipPatterns(context);
设置WebappLoader
        WebappLoader loader = new 
WebappLoader(context.getParentClassLoader());
        设置loaderclass
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
true代表遵守双亲委派机制
        loader.setDelegate(true);
        context.setLoader(loader);
        if (isRegisterDefaultServlet()) {
添加默认的servlet
            addDefaultServlet(context);
        }
添加jspServlet,JasperInitializer
        if (shouldRegisterJspServlet()) {
            addJspServlet(context);
            addJasperInitializer(context);
        }
添加监听者StaticResourceConfigurer
        context.addLifecycleListener(new StaticResourceConfigurer(context));
合并所以的initializer
        ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
添加子容器
        host.addChild(context);
设置context的staert,valve,监听者,errorpage,请求格式,session
        configureContext(context, initializersToUse);
目前是空的实现
        postProcessContext(context);
    }

initialize

public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
        initialize();
    }

    private void initialize() throws WebServerException {
        TomcatWebServer.logger
                .info("Tomcat initialized with port(s): " + getPortsDescription(false));
        synchronized (this.monitor) {
            try {
设置engine的id
                addInstanceIdToEngineName();
获取第一个context
                Context context = findContext();
添加一个监听者,在context启动的时候剔除connector,防止connector启
动,因为这时候spring还没加载完成,这是connector启动就可以接受请求,
会出现异常
                context.addLifecycleListener((event) -> {
                    if (context.equals(event.getSource())
                            && Lifecycle.START_EVENT.equals(event.getType())) {
                        // Remove service connectors so that protocol binding doesn't
                        // happen when the service is started.
                        removeServiceConnectors();
                    }
                });

                // Start the server to trigger initialization listeners
启动tomcat中的container
                this.tomcat.start();

    检测上述启动中是否存在异常,存在就抛出异常
                rethrowDeferredStartupExceptions();

                try {
将当前context和classloader绑定
                    ContextBindings.bindClassLoader(context, context.getNamingToken(),
                            getClass().getClassLoader());
                }
                catch (NamingException ex) {
                    // Naming is not enabled. Continue
                }


tomcat的线程都是daemon,所以设置一个non-daemon 去关闭tomcat,如果不设置daemon,那么linux会在我们tomcat中和spring中所有飞daemon退出即杀死进程
                startDaemonAwaitThread();
            }
            catch (Exception ex) {
                stopSilently();
                throw new WebServerException("Unable to start embedded Tomcat", ex);
            }
        }
    }

tomcat关闭,spring注册的钩子会在收到关闭信号时候在关闭spring之后调用stopInternal 设置stopAwait为true 去关闭tomcat,这个不仅仅失去关闭tomcat,如果我们不启动这个线程,那么tomcat的方法执行完成就会退出去,从而spring和tomcat都会执行完成就关闭

 public void await() {
当为负值代表是嵌入的
        if( port == -2 ) {
            // undocumented yet - for embedding apps that are around, alive.
            return;
        }
还记得我们上文分析的为什么关闭端口是8005 我们改成了-1,因为我们是借助于spring去关闭
        if( port==-1 ) {
            try {
每隔10秒检测一次状态,如果未true直接退出,从而tomcat就可以关闭了
                awaitThread = Thread.currentThread();
                while(!stopAwait) {
                    try {
                        Thread.sleep( 10000 );
                    } catch( InterruptedException ex ) {
                        // continue and check the flag
                    }
                }
            } finally {
                awaitThread = null;
            }
            return;
        }

     当port是正常的时候,我们启动一个socket去监听这个端口,监听到关闭连接请求就退出该线程从而使得tomcat和spring退出
        try {
            awaitSocket = new ServerSocket(port, 1,
                    InetAddress.getByName(address));
        } catch (IOException e) {
            log.error("StandardServer.await: create[" + address
                               + ":" + port
                               + "]: ", e);
            return;
        }

        try {
            awaitThread = Thread.currentThread();

            // Loop waiting for a connection and a valid command
            while (!stopAwait) {
                ServerSocket serverSocket = awaitSocket;
                if (serverSocket == null) {
                    break;
                }

                // Wait for the next connection
                Socket socket = null;
                StringBuilder command = new StringBuilder();
                try {
                    InputStream stream;
                    long acceptStartTime = System.currentTimeMillis();
                    try {
                        socket = serverSocket.accept();
                        socket.setSoTimeout(10 * 1000);  // Ten seconds
                        stream = socket.getInputStream();
                    } catch (SocketTimeoutException ste) {
                        // This should never happen but bug 56684 suggests that
                        // it does.
                        log.warn(sm.getString("standardServer.accept.timeout",
                                Long.valueOf(System.currentTimeMillis() - acceptStartTime)), ste);
                        continue;
                    } catch (AccessControlException ace) {
                        log.warn("StandardServer.accept security exception: "
                                + ace.getMessage(), ace);
                        continue;
                    } catch (IOException e) {
                        if (stopAwait) {
                            // Wait was aborted with socket.close()
                            break;
                        }
                        log.error("StandardServer.await: accept: ", e);
                        break;
                    }

                    // Read a set of characters from the socket
                    int expected = 1024; // Cut off to avoid DoS attack
                    while (expected < shutdown.length()) {
                        if (random == null)
                            random = new Random();
                        expected += (random.nextInt() % 1024);
                    }
                    while (expected > 0) {
                        int ch = -1;
                        try {
                            ch = stream.read();
                        } catch (IOException e) {
                            log.warn("StandardServer.await: read: ", e);
                            ch = -1;
                        }
                        // Control character or EOF (-1) terminates loop
                        if (ch < 32 || ch == 127) {
                            break;
                        }
                        command.append((char) ch);
                        expected--;
                    }
                } finally {
                    // Close the socket now that we are done with it
                    try {
                        if (socket != null) {
                            socket.close();
                        }
                    } catch (IOException e) {
                        // Ignore
                    }
                }

                // Match against our command string
                boolean match = command.toString().equals(shutdown);
                if (match) {
                    log.info(sm.getString("standardServer.shutdownViaPort"));
                    break;
                } else
                    log.warn("StandardServer.await: Invalid command '"
                            + command.toString() + "' received");
            }
        } finally {
            ServerSocket serverSocket = awaitSocket;
            awaitThread = null;
            awaitSocket = null;

            // Close the server socket and return
            if (serverSocket != null) {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    // Ignore
                }
            }
        }
    }

你可能感兴趣的:(Springboot内嵌式tomcat--源码分析)