还有Springboot,对于新手来说觉得springboot很神奇,我不需要tomcat了,不需要把项目放到webapp里面并且在server.xml 中去配置项目路径了,其实= =,springboot是内置tomcat,并且外置tomcat有的,内置也有,只是可能springboot把tomcat进行了一个优化,更加适合了springboot。

接下是看 tomcat的细节部分,对外和对内。
对外:Connector
QQ截图20201201213236.png
BIONIO : 同步阻塞的IO,针对并发量很大的时候效率就很低,因为它一个连接就要一个线程。
NIO: 同步非阻塞的IO,并发量比较大的时候,比较占优势,它一个线程就可以处理并发。
Apr: 与本地方法库进行交互。

1、默认IO是什么? 在tomcat中server.xml 配置文件中可以看到默认是HTTP1.1 那么在tomcat中的Connector源码中可以看到简单工厂模式去采用了NIO的IO方式。 源于8.0,具体默认的可以结合配置文件以及Connector源码可以查找到。

EndPoint: 对于传输层的抽象、里面是写Socket代码的。
Processor: EndPoint根据server.xml 中的配置找到对应的IO处理生成Socket,而Processor是将这个socket转化成tomcat的request/response,简单言之就是将一串数据解析成为想要的我所定义的规范。
Adapter:而tomcat不能把自己锁规范的东西给java直接使用,这个时候就需要一个适配器,通过这个适配器,将tomcat的request请求信息转化成java所认知的request。

Adapter:在tomcat源码中,有一个类 CoyoteAdapter.java 这个类中有那么一个方法。

 @Override
public void service(org.apache.coyote.Request req,
                    org.apache.coyote.Response res)
    throws Exception {

    Request request = (Request) req.getNote(ADAPTER_NOTES);
    Response response = (Response) res.getNote(ADAPTER_NOTES);

    if (request == null) {

        // Create objects
        request = connector.createRequest();
        request.setCoyoteRequest(req);
        response = connector.createResponse();
        response.setCoyoteResponse(res);

可以看出,传入了tomcat类型的request和response,从而生成适合servlet的request和response,也就验证了适配器的作用。

而connector中的线程池,也是在这一步采取了作用。
QQ截图20201201220042.png

请求是多个线程,一大堆请求过来,我单个线程不可能完成tomcat的res和req的转换,一个请求来了我开启一个线程去处理,多个请求来了我就多个线程去处理。生成socket交给processer,最后通过适配器去转化java的servletreq、res,之后就由对内的Container处理。

总结:对外tomcat主要通过server.xml中的配置信息去初始化connector,通过指定的协议以及对应的IO模式去获取到客户端的请求,获取到请求之后对当前连接的protocol绑定一个适配器用于后续的处理,然后初始化endpoint,生成指定的socket,之后交给制造者去通过socket信息解析出tomcat规范的req和res,在通过之前对这个连接进行绑定的适配器生成java所需要的servletreq、res剩下的交给Container。

对内:Container

成功的给我看懵逼了,就知道Tomcat 拥有一个自定义的加载器。自定义的类加载器,按照tomcat具体进行了实现,也得到了,tomcat打破了jvm的双亲委派机制类加载的方式,剩下的不知道了。

tomcat的启动,既然是Java就有一个tomcat的main这么一个入口,就会有一个BootStrap.main 进行启动
当启动的时候,会由最外层开始,逐个向内进行对象的创建。 在源码中在启动的时候创建一个服务实例,而创建会去读取server.xml 文件。
这个server.xml 通过Digester去进行解析。
根据代码思路走,先上Bootstrap的main启动入口:

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

}

可以看到 start启动会调用load(),而load点进去

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

}

这里面开始加载一些组件,而加载的组件当然是通过server.xml 去依次加载了,一步一步走,发现通过反射调用了一个叫load的方法,而通过猜想是一个叫Catalina的类,找到这个类,果然看到了load方法。

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

    long t1 = System.nanoTime();

    initDirs();

    // Before digester - it may be needed
    initNaming();

    // Create and execute our Digester
    Digester digester = createStartDigester();

    InputSource inputSource = null;
    InputStream inputStream = null;
    File file = null;
    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);
        }
    }

之前说,server.xml 是tomcat在启动的时候需要初始化的,那么可以看到会有一个文件输入流,而读取的这个文件是什么,我们再点进去,file = configFile();

protected File configFile() {

    File file = new File(configFile);
    if (!file.isAbsolute()) {
        file = new File(Bootstrap.getCatalinaBase(), configFile);
    }
    return (file);

}

可以看到一个文件叫configFile的进入了文件的加载。

/**
 * Pathname to the server configuration file.
 */
protected String configFile = "conf/server.xml";

而配置文件 就是这个server.xml 。

 // Start the new server
    try {
        getServer().init();

在load() 方法中 还有一串代码,是getServer.init() 可以看到,确实是按照之前的tomcat的架构图,从外到内进行初始化,当然现在就一个server.init() 初始化的,具体是不是这样的,还要验证,这只是我们的猜想。我们点进去看。

@Override

public final synchronized void init() throws LifecycleException {
    if (!state.equals(LifecycleState.NEW)) {
        invalidTransition(Lifecycle.BEFORE_INIT_EVENT);
    }
    setStateInternal(LifecycleState.INITIALIZING, null, false);

    try {
        initInternal();
    } catch (Throwable t) {
        ExceptionUtils.handleThrowable(t);
        setStateInternal(LifecycleState.FAILED, null, false);
        throw new LifecycleException(
                sm.getString("lifecycleBase.initFail",toString()), t);
    }

    setStateInternal(LifecycleState.INITIALIZED, null, false);
}

进入了一个叫 LifecycleBase.java 的文件,里面的代码在上面。 看到了我们之前熟悉的 initInternal() 方法。
接着点进去。
视频作者怎么知道该点这个的,我也不知道= =
我看到有很多实现了init方法的类。。先跟着走吧。然后进去了一个文件叫 StandardServer的那么一个类,里面的指定方法有那么一段代码:

// Initialize our defined Services
    for (int i = 0; i < services.length; i++) {
        services[i].init();
    }

看字面意思,对service进行加载,而遍历也表示会有多个service,一个service按照之前的图上描述,有connector,接着点进去看一下有没有相关的初始化操作。而service会对connector和线程池进行初始化,而这两个也都是for循环,所以可以得到,这两个也是多个,但是在代码中没有看到对 Engine这个东西的初始化,是因为Engine就是Container。

@Override
protected void initInternal() throws LifecycleException {

    super.initInternal();

    if (container != null) {
        container.init();
    }

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

    // Initialize mapper listener
    mapperListener.init();

    // Initialize our defined Connectors
    synchronized (connectorsLock) {
        for (Connector connector : connectors) {
            try {
                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 new LifecycleException(message);
            }
        }
    }
}

而StandardServer中除了load() 之外,下面还有一个start() 而这个start() 也是从server开始进行启动,对内逐个启动,采用了模板模式。

到底就结束了。。但是有一个问题= =,它们循环调用,循环加载或者启动下面的类,那怎么知道下一个该启动哪一个的?我没有看到代码有约束启用??

最后修改:2020 年 12 月 02 日 09 : 55 PM
如果觉得我的文章对你有用,请随意赞赏