Tomcat 内部各个组件、启动、请求解析原理分析

目录

Tomcat 组件简介

Tomcat的执行请求流程

Tomcat启动流程

tomcat类加载机制及双亲委派机制


Tomcat 组件简介

tomcat的各个组件

Http请求的执行图示:

 tomcat启动源码时序图

 tomcat接受请求源码时序图

带着问题看源码:发起http请求后,tomcat是如何解析的?这个需要看tomcat的整体执行请求的过程

Tomcat的执行请求流程

发起一个请求到tomcat,在connector这里的细节是:

EndPoint:首先会在NoiEndpoint中setSocketOptions()被捕获到,然后设置socket属性,从NioChannel栈中获取NioChannel,注册到 一直监听到达的TCP / IP连接的后台线程,从socket缓存栈中pop出一个SocketProcessorBase(套接字),然后丢到Executor线程中去执行后续逻辑。(若debug源码,可以将源码变更在当前线程执行就可以继续debug。)通过doRun()方法getHandler().process(socketWrapper, event);通过Handler处理器进行处理。

ConnectionHandler:process()方法:连接处理器从连接缓存池中获取processor来处理socketWrapper。

Http11Processor:后续通过getAdapter().service(request, response);适配器模式,通过适配器来处理相应逻辑,在此处只有CoyoteAdapter实现。

CoyoteAdapter:service()中将org.apache.coyote.Request 转化为org.apache.catalina.connector.Request,而这个是HttpServletRequest的子类,所以后续其实是对HttpServletRequest、HttpServletResponse的操作。后续的postParseRequest(req, request, res, response);会根据请求通过mapperListener进行一系列匹配规则,解析出具体的host、context、wrapper类,接着通过责任链模式在组件中找到目标Servlet及FilterChain拦截器链。FilterChain->Servlet依次执行。

// 通过service的mapper中的Map<Context, ContextVersion> contextObjectToContextVersionMap来匹配出执行链路
connector.getService().getMapper().map(serverName, decodedURI,
                    version, request.getMappingData());

// 内部调用internalMap(host.getCharChunk(), uri.getCharChunk(), version,
                mappingData);实现
// 可以看到通过mapper中的host名称匹配出具体执行的host后,获取该host下context和wrapper,
// 通过一系列匹配规则(精确匹配、前缀匹配、扩展匹配、欢迎资源处理servlet)过滤生成mappingData对象
        MappedHost mappedHost = exactFindIgnoreCase(hosts, host);
        // ...省略

        // Context mapping
        ContextList contextList = mappedHost.contextList;
        MappedContext[] contexts = contextList.contexts;

// 通过pipeline责任链模式来执行后续操作
       connector.getService().getContainer().getPipeline().getFirst().invoke(
                        request, response);

Container执行细节:

engine:getService().getContainer()自然就是engine。根据request的mappingData.host来执行具体host的pipeline执行逻辑,而mappingData就是通过匹配规则得出的执行host、context、wrapper的对象。后续的逻辑类似,在责任链执行时会通过request中的context、wrapper来执行后续的逻辑。

Host、context:pipeline责任链执行

Wrapper:得到Servlet实例,获取当前context上下文,获取过滤器链,执行完过滤器链后,执行servlet方法。

 servlet = wrapper.allocate();
// ...省略
// Create the filter chain for this request 给该request创建Filter过滤链。Filter过滤链执行完之后,执行Servelt
        ApplicationFilterChain filterChain =
                ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);

业务数据返回,和请求解析的路径一致,依次return完成。

这时就完成了一个请求的解析到执行,那么整个流程的url解析最关键的就在于CoyoteAdapter会拿到service的mapper,那么mapper到底是什么时候点被加载到数据中、engine、host、context、wrapper之间的管理关系又是什么样的?下面看下启动流程。

Tomcat启动流程

Tomcat启动分为两个主要部分分别是:load()、start()

Tomcat启动:会通过Bootstrap的main方法,先初始化daemon,主要是初始化commonLoader、catalinaLoader、sharedLoader三个类加载器(三个类加载本质都是 urlClassLoader),通过类加载加载,构造器创建Catalina类实例,便于后续init()。(问题一:jdk已经自带有类加载器了,这里创建类加载器的意义是什么?)

load():

Catalina的load方法:

// 解析server.xml输入流数据,填充属性(指定使用digester解析的输入源内容,从堆栈对象返回根元素)
// 这里就会建立service->engine->host的对应关系,并作为catalina的server属性记录
digester.parse(inputSource);
// 初始化解析出的service
getServer().init();

从下图也能看出,解析server.xml后,基本的映射关系已经建立,且server、service、engine有属性关系来进行保存的,这样各个组件之间关联关系也建立了。

 Server:组件的init都是由同一个父类LifecycleBase来定义的,各子类只需要实现initInternal()方法即可(模板方法),server具体实现会services[i].init();将server.xml解析出来的services初始化。

Service:看下面service代码具体实现和注释        

protected void initInternal() throws LifecycleException {

        super.initInternal();

        if (engine != null) {
            // 初始化engine(service 一对一)
            engine.init();
        }

        // Initialize any Executors
        for (Executor executor : findExecutors()) {
            if (executor instanceof JmxEnabled) {
                ((JmxEnabled) executor).setDomain(getDomain());
            }
            // 初始化线程池(一对多,由此也可以看出线程池是service之间是隔离的,组件间共享)
            executor.init();
        }

        // Initialize mapper listener
        // 初始化mapperListener,mapper监听器
        mapperListener.init();

        // Initialize our defined Connectors
        // 初始化connectors,连接器(HTTP、AJP两种)
        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);
                }
            }
        }
    }

Connector:

super.initInternal();

        // Initialize adapter 初始化适配器(可以看到初始化连接器时,协议处理器和适配器已经绑定了)
        adapter = new CoyoteAdapter(this);
        protocolHandler.setAdapter(adapter);

        // ***省略

        try {
            // 初始化协议处理器(内部会初始化endpoint)
            protocolHandler.init();
        } catch (Exception e) {
            throw new LifecycleException(
                    sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
        }

这样整个load()过程已经结束了,接下来看下

Start():

和load()类似的会从server先进行start(),通过模板方法,执行固定逻辑,然后在server的startInternal()方法中,start()所有的service。

Service:看下service的具体start()实现:和load类似依次执行相关的start()。

protected void startInternal() throws LifecycleException {

        if(log.isInfoEnabled())
            log.info(sm.getString("standardService.start.name", this.name));
        setState(LifecycleState.STARTING);

        // Start our defined Container first
        // 启动engine引擎
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }

        // 线程池启动
        synchronized (executors) {
            for (Executor executor: executors) {
                executor.start();
            }
        }

        // mapper监听器启动
        mapperListener.start();

        // Start our defined Connectors second
        // 连接器启动
        synchronized (connectorsLock) {
            for (Connector connector: connectors) {
                try {
                    // If it has already failed, don't try and start it
                    if (connector.getState() != LifecycleState.FAILED) {
                        connector.start();
                    }
                } catch (Exception e) {
                    log.error(sm.getString(
                            "standardService.connector.startFailed",
                            connector), e);
                }
            }
        }
    }

这里debug可以看到,mapperListener中host下的关联数据还未加载关联,整个组件的关联关系还不完善,host是如何加载到context,又如何加载到wrapper的呢?接下来看下engine的start()流程

 engine():StartEngine的startInternal()方法,后续host组件在创建时通过线程池启动,Future来等待执行结果。此时可以通过注释掉,以单线程方式去执行,即可debug后续的组件start()流程。通过下面代码可见:engine的children是host,而host在解析xml时,已经成功注入mapperListener中,而host此时仍没有context数据。继续往下走:host.start();

//        // 获取所有的子节点,并启动(如:host)
        // Start our child containers, if any
        Container children[] = findChildren();
        List<Future<Void>> results = new ArrayList<>();
        for (int i = 0; i < children.length; i++) {
            results.add(startStopExecutor.submit(new StartChild(children[i])));
        }

        MultiThrowable multiThrowable = null;

        // Future设计模式,拿着票据等待
        for (Future<Void> result : results) {
            try {
                result.get();
            } catch (Throwable e) {
                log.error(sm.getString("containerBase.threadedStartFailed"), e);
                if (multiThrowable == null) {
                    multiThrowable = new MultiThrowable();
                }
                multiThrowable.add(e);
            }

        }
        if (multiThrowable != null) {
            throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
                    multiThrowable.getThrowable());
        }

HOST():模板方法执行子类具体实现前,会通过发送before_start事件监听的方式,来处理关联host的START事件(观察者模式),

try {
            // 识别该host的xml、war中的配置、解析ContextClass()
            host = (Host) event.getLifecycle();
            if (host instanceof StandardHost) {
                setCopyXML(((StandardHost) host).isCopyXML());
                setDeployXML(((StandardHost) host).isDeployXML());
                setUnpackWARs(((StandardHost) host).isUnpackWARs());
                setContextClass(((StandardHost) host).getContextClass());
            }
        } catch (ClassCastException e) {
            log.error(sm.getString("hostConfig.cce", event.getLifecycle()), e);
            return;
        }

        // Process the event that has occurred
        if (event.getType().equals(Lifecycle.PERIODIC_EVENT)) {
            // Lifecycle.PERIODIC_EVENT周期性事件,若host支持自动部署,则会定期自动检查资源是否变更
            // 变更后,重新load()所有组件,这也就是热部署的实现。
            check();
        } else if (event.getType().equals(Lifecycle.BEFORE_START_EVENT)) {
            // 创建host的class文件夹、生成文件
            beforeStart();
        } else if (event.getType().equals(Lifecycle.START_EVENT)) {
            start();
        } else if (event.getType().equals(Lifecycle.STOP_EVENT)) {
            stop();
        }

后续执行engine的pipeline责任链的start();去初始化host的信息,通过LifecycleBase的init方法,走的standardpipeline的startInternal()方法。下面可以看出责任链的调用情况。

 当host创建成功状态变为START时,会被hostConfig监听到start()事件,然后去发现自动部署war包,将解析出的组件信息关联到host组件中,此时host后续的context数据进行了关系映射。

        Context context = null;
        File xml = new File(dir, Constants.ApplicationContextXml);
        File xmlCopy =
                new File(host.getConfigBaseFile(), cn.getBaseName() + ".xml");


        DeployedApplication deployedApp;
        boolean copyThisXml = isCopyXML();
        boolean deployThisXML = isDeployThisXML(dir, cn);

        try {
            // 各种方式去解析构造context信息
            if (deployThisXML && xml.exists()) {
                synchronized (digesterLock) {
                    try {
                        context = (Context) digester.parse(xml);
                    } catch (Exception e) {
                        log.error(sm.getString(
                                "hostConfig.deployDescriptor.error",
                                xml), e);
                        context = new FailedContext();
                    } finally {
                        digester.reset();
                        if (context == null) {
                            context = new FailedContext();
                        }
                    }
                }

                if (copyThisXml == false && context instanceof StandardContext) {
                    // Host is using default value. Context may override it.
                    copyThisXml = ((StandardContext) context).getCopyXML();
                }

                if (copyThisXml) {
                    Files.copy(xml.toPath(), xmlCopy.toPath());
                    context.setConfigFile(xmlCopy.toURI().toURL());
                } else {
                    context.setConfigFile(xml.toURI().toURL());
                }
            } else if (!deployThisXML && xml.exists()) {
                // Block deployment as META-INF/context.xml may contain security
                // configuration necessary for a secure deployment.
                log.error(sm.getString("hostConfig.deployDescriptor.blocked",
                        cn.getPath(), xml, xmlCopy));
                context = new FailedContext();
            } else {
                context = (Context) Class.forName(contextClass).getConstructor().newInstance();
            }
            // 设置context属性和host构造子属性的关系
            Class<?> clazz = Class.forName(host.getConfigClass());
            LifecycleListener listener = (LifecycleListener) clazz.getConstructor().newInstance();
            context.addLifecycleListener(listener);

            context.setName(cn.getName());
            context.setPath(cn.getPath());
            context.setWebappVersion(cn.getVersion());
            context.setDocBase(cn.getBaseName());
            host.addChild(context);
        }

建立host与context关系,context的startInternal(),会将先启动资源文件,创建context自己的WebAppClassLoader,并启动类加载器加载web下的class文件,并发送事件通知

// 通知生命周期监听器Configure启动事件。
                // 触发了ContextConfig的configureStart方法,configureStart执行webConfig方法,webConfig执行了configureContext启动了容器
                // Notify our interested LifecycleListeners
                fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);

contextConfig会监听并通过configureStart()的webConfig();来注入servlet,即扫描注入应用程序的web.xml的配置信息。这样整体的组件映射关系已经建立,后续就是wrapper自定义的start()操作了,没有特殊差异,关于启动流程就到这里。

在WebAppClassLoader去加载servlet的时候,这个和JAVA默认的双亲委派机制加载class文件有所差别。

tomcat类加载机制及双亲委派机制

Tomcat中web下的class类加载机制

Tomcat提供的Web应用类加载器与默认的委派机制不同。当进行类加载器时,除了JVM基础类库外,它会首先尝试通过当前类加载器加载,然后才进行委派。Servelt规范相关API禁止通过Web应用类加载器加载,因此不要在Web应用中包含这些包。
Web应用类加载器默认的加载顺序如下:
(1)从缓存中加载
(2)如果没有,则从JVM的BootStrap类加载器加载。
(3)如果没有,则从当前类加载器加载(按照WEB-INF/classes、WEB-INF/lib的顺序)
(4)如果没有,则从当前父类加载器加载,由于父类加载器采用默认的委派模式,所以加载顺序为System、Common、Shared。
Tomcat提供了delegate属性用于控制是否启用Java委派模式,默认为false(不启用)。当配置为true时,Tomcat将使用Java默认的委派模式:
(1)从缓存中加载
(2)如果没有,从JVM的Bootstrap类加载器加载
(3)如果没有,从父类的加载器加载(System、Common、Shared)
(4)如果没有,则从当前类加载器加载。
除了可以通过delegate属性控制是否启用Java的委派模式外,Tomcat还可以通过packegeTriggerDeny属性只让某些包路径采用Java的委派模式,Web应用类加载器对于符合packegeTriggerDeny指定包路径的类强制采用Java的委派模式。
Tomcat通过该机制实现对Web应用中的Jar包覆盖服务器提供包的目的,Java核心类库、Servelt规范相关类库是无法覆盖的,此外Java默认提供的诸如XML工具包,由于位于Jvm的Bootstrap类加载器也无法覆盖,只能通过endorsed方式实现。

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (getClassLoadingLock(name)) {
            if (log.isDebugEnabled())
                log.debug("loadClass(" + name + ", " + resolve + ")");
            Class<?> clazz = null;

            // Log access to stopped class loader
            checkStateForClassLoading(name);

            // (0) Check our previously loaded local class cache
            // 先在本地缓存中查找是否已经加载过该类(对于一些已经加载了的类,会被缓存在resourceEntries这个数据结构中),如果已经加载即返回.
            clazz = findLoadedClass0(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }

            // (0.1) Check our previously loaded class cache
            clazz = findLoadedClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled())
                    log.debug("  Returning class from cache");
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }

            // 尝试加载系统类装入器的类,防止webapp重写Java SE类
            // (0.2) Try loading the class with the system class loader, to prevent
            //       the webapp from overriding Java SE classes. This implements
            //       SRV.10.7.2
            String resourceName = binaryNameToPath(name, false);
            // 加載jre/lib/ext中的jar ExtClassLoader
            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {
                // Use getResource as it won't trigger an expensive
                // ClassNotFoundException if the resource is not available from
                // the Java SE class loader. However (see
                // https://bz.apache.org/bugzilla/show_bug.cgi?id=58125 for
                // details) when running under a security manager in rare cases
                // this call may trigger a ClassCircularityError.
                // See https://bz.apache.org/bugzilla/show_bug.cgi?id=61424 for
                // details of how this may trigger a StackOverflowError
                // Given these reported errors, catch Throwable to ensure any
                // other edge cases are also caught
                URL url;
                if (securityManager != null) {
                    PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                    url = AccessController.doPrivileged(dp);
                } else {
                    url = javaseLoader.getResource(resourceName);
                }
                tryLoadingFromJavaseLoader = (url != null);
            } catch (Throwable t) {
                // Swallow all exceptions apart from those that must be re-thrown
                ExceptionUtils.handleThrowable(t);
                // The getResource() trick won't work for this class. We have to
                // try loading it directly and accept that we might get a
                // ClassNotFoundException.
                tryLoadingFromJavaseLoader = true;
            }
            // javaseLoader加載jre/lib/ext中的jar
            if (tryLoadingFromJavaseLoader) {
                try {
                    clazz = javaseLoader.loadClass(name);
                    if (clazz != null) {
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }

            // (0.5) Permission to access this class when using a SecurityManager
            if (securityManager != null) {
                int i = name.lastIndexOf('.');
                if (i >= 0) {
                    try {
                        securityManager.checkPackageAccess(name.substring(0,i));
                    } catch (SecurityException se) {
                        String error = "Security Violation, attempt to use " +
                            "Restricted Class: " + name;
                        log.info(error, se);
                        throw new ClassNotFoundException(error, se);
                    }
                }
            }

            boolean delegateLoad = delegate || filter(name, true);
            // 如果是true就是用父类加载器进行加载
            // (1) Delegate to our parent if requested
            if (delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader1 " + parent);
                try {//使用父类加载器进行加载
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
            // 让系统类加载器(AppClassLoader)尝试加载该类,主要是为了防止一些基础类会被web中的类覆盖,如果加载到即返回,返回继续。
            // (2) Search local repositories
            if (log.isDebugEnabled())
                log.debug("  Searching local repositories");
            try {
                clazz = findClass(name);
                if (clazz != null) {
                    if (log.isDebugEnabled())
                        log.debug("  Loading class from local repository");
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }

            // (3) Delegate to parent unconditionally 委托父类来加载,父类默认是双亲委派,所以会走一遍双亲委派加载(兜底操作)
            if (!delegateLoad) {
                if (log.isDebugEnabled())
                    log.debug("  Delegating to parent classloader at end: " + parent);
                try {
                    clazz = Class.forName(name, false, parent);
                    if (clazz != null) {
                        if (log.isDebugEnabled())
                            log.debug("  Loading class from parent");
                        if (resolve)
                            resolveClass(clazz);
                        return clazz;
                    }
                } catch (ClassNotFoundException e) {
                    // Ignore
                }
            }
        }

        throw new ClassNotFoundException(name);
    }

双亲委派机制
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派父类加载器去完成。每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个请求(他的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。

 1)当自定义一个java.lang.String对象并且尝试在程序里面使用时,会发现程序不会调用自定义的String对象,而是调用jdk提供的String对象,是因为类加载时,会先让他的父类加载器先去加载,一直向上委托,直到启动类加载器,启动类加载器会加载java包下的类,所以String对象是jdk提供的对象。
(2)同理,如果想运行自己定义的java.lang.String的main方法,程序会报错,提示找不到main方法,因为jdk提供的String对象不存在main方法。
双亲委派机制的作用就是避免类的重复加载、保护程序安全,防止核心API被随意篡改

ClassLoader

ClassLoader的loadClass实现了双亲委派机制

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 1、首先从缓存中获取
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                	// 2、缓存中没有,就从父类加载器从加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    	// parent == null,代表parent为BootStrap类加载器。
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 如果还没有找到,就调用findClass加载类。
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

为什么不使用双亲委派机制?

参看:tomcat类加载器为什么要破坏双亲委派机制? - 牧云文仔 - 博客园

待探究:tomcat热加载机制:idea的热启动在依赖了tomcat的热加载同时,有什么改进?

猜你喜欢

转载自blog.csdn.net/kolbjbe/article/details/121062429