谈起Tomcat的诞生,最早可以追溯到1995年。近20年来,Tomcat始终是使用最广泛的Web服务器,由于其使用Java语言开发,所以广为Java程序员所熟悉。很多人早期的J2EE项目,由程序员自己实现Jsp页面或者Servlet接受请求,后来借助Struts1、Struts2、Spring等中间件后,实际也是利用Filter或者Servlet处理请求,大家肯定要问了,这些Servlet处理的请求来自哪里?Tomcat作为Web服务器是怎样将HTTP请求交给Servlet的呢?

  本文就Tomcat对HTTP的请求处理细节进行分析。

  提示:阅读本文前,请确保首先理解了《Tomcat源码分析——生命周期管理》中的内容。

CONNECTOR的初始化

  根据《Tomcat源码分析——生命周期管理》一文的内容,我们知道Tomcat中有很多容器,包括Server、Service、Connector等。其中Connector正是与HTTP请求处理相关的容器。Service是Server的子容器,而Connector又是Service的子容器。那么这三个容器的初始化顺序为:Server->Service->Connector。Connector的实现分为以下几种:

  • Http Connector:基于HTTP协议,负责建立HTTP连接。它又分为BIO Http Connector与NIO Http Connector两种,后者提供非阻塞IO与长连接Comet支持。
  • AJP Connector:基于AJP协议,AJP是专门设计用于Tomcat与HTTP服务器通信定制的协议,能提供较高的通信速度和效率。如与Apache服务器集成时,采用这个协议。
  • APR HTTP Connector:用C实现,通过JNI调用的。主要提升对静态资源(如HTML、图片、CSS、JS等)的访问性能。现在这个库已独立出来可用在任何项目中。由于APR性能较前两类有很大提升,所以目前是Tomcat的默认Connector。

现在我们直接来看Connector的initInternal方法吧,见代码清单1。

代码清单1

@Overrideprotected void initInternal() throws LifecycleException {super.initInternal();// Initialize adapteradapter = new CoyoteAdapter(this);protocolHandler.setAdapter(adapter);// Make sure parseBodyMethodsSet has a defaultif (null == parseBodyMethodsSet) {setParseBodyMethods(getParseBodyMethods());}if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",getProtocolHandlerClassName()));}if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&protocolHandler instanceof AbstractHttp11JsseProtocol) {AbstractHttp11JsseProtocol<?> jsseProtocolHandler =(AbstractHttp11JsseProtocol<?>) protocolHandler;if (jsseProtocolHandler.isSSLEnabled() &&jsseProtocolHandler.getSslImplementationName() == null) {// OpenSSL is compatible with the JSSE configuration, so use it if APR is availablejsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());}}try {protocolHandler.init();} catch (Exception e) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);}}

代码清单1说明了Connector的初始化步骤如下:

步骤一 构造网络协议处理的COYOTEADAPTER

  代码清单1构造了CoyoteAdapter对象,并且将其设置为ProtocolHandler的Adapter。ProtocolHandler是做什么的呢?Tomcat处理HTTP请求,需要有一个ServerSocket监听网络端口来完成任务。接口ProtocolHandler被设计成控制网络端口监听组件运行,负责组件的生命周期控制,这个接口实际并没有定义网络端口监听功能的规范,而是用于负责维护组件的生命周期。从ProtocolHandler的名字来看,它应该是网络协议的处理者,但它实际不负责这个功能,而是将其交给org.apache.coyote.Adapter来完成,这么设计估计是为了方便维护和拓展新功能。Http11Protocol是ProtocolHandler接口的一个实现(是Connector的默认处理协议),被设计用来处理HTTP1.1网络协议的请求,通过该类可以完成在某个网络端口上面的监听,同时以HTTP1.1的协议来解析请求内容,然后将请求传递到Connector所寄居的Container容器pipeline流水工作线上处理。此处的ProtocolHandler是何时生成的呢?还记得《TOMCAT源码分析——SERVER.XML文件的加载与解析》一文中的Digester和Rule吗?Digester在解析到<Connector>标签的时候,会执行startElement方法,startElement中会调用Rule的begin(String namespace, String name, Attributes attributes)方法,Connector对应的Rule包括ConnectorCreateRule,ConnectorCreateRule的begin方法的实现见代码清单2。

代码清单2

@Overridepublic void begin(String namespace, String name, Attributes attributes)throws Exception {Service svc = (Service)digester.peek();Executor ex = null;if ( attributes.getValue("executor")!=null ) {ex = svc.getExecutor(attributes.getValue("executor"));}Connector con = new Connector(attributes.getValue("protocol"));if (ex != null) {setExecutor(con, ex);}String sslImplementationName = attributes.getValue("sslImplementationName");if (sslImplementationName != null) {setSSLImplementationName(con, sslImplementationName);}digester.push(con);}

代码清单2中调用了Connector的构造器,传递的参数为属性protocol。我们知道server.xml中的Connector有两个:

    <Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" /><!-- Define an AJP 1.3 Connector on port 8009 --><Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />

我们看看Connector的构造器实现,见代码清单3。

代码清单3

public Connector(String protocol) {      //DeprcatedsetProtocol(protocol);// Instantiate protocol handlerProtocolHandler p = null;try {Class<?> clazz = Class.forName(protocolHandlerClassName);p = (ProtocolHandler) clazz.getConstructor().newInstance();} catch (Exception e) {log.error(sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"), e);} finally {this.protocolHandler = p;}if (Globals.STRICT_SERVLET_COMPLIANCE) {uriCharset = StandardCharsets.ISO_8859_1;} else {uriCharset = StandardCharsets.UTF_8;}}

setProtocol方法已经作废。以HTTP/1.1为例,由于默认情况下Apr不可用,所以protocolHandlerClassName会被设置为"org.apache.coyote.http11.Http11NioProtocol",那么反射生成的protocolHandler就是Http11NioProtocol实例。Tomcat默认还会配置协议是AJP/1.3的Connector,那么此Connector的protocolHandler就是org.apache.coyote.ajp.AjpProtocol。

代码清单5

    public void setProtocolHandlerClassName(String protocolHandlerClassName) {this.protocolHandlerClassName = protocolHandlerClassName;}

除此之外,ProtocolHandler还有其它实现,如图1所示。

图1  ProtocolHandler类继承体系

图1中有关ProtocolHandler的实现类都在org.apache.coyote包中 。前面所说的BIO Http Connector实际就是Http11Protocol,NIO Http Connector实际就是Http11NioProtocol,AJP Connector包括AjpProtocol和AjpAprProtocol,APR HTTP Connector包括AjpAprProtocol、Http11AprProtocol,此外还有一个MemoryProtocolHandler(这个是做什么的,目前没搞清楚,有知道的同学告诉我下啊!)。

步骤二 将PROTOCOLHANDLER注册到JMX

  BIO Http Connector的ProtocolHandler(即Http11Protocol)的JMX注册名为Catalina:type=ProtocolHandler,port=8080。AJP Connector的ProtocolHandler(即AjpProtocol)的JMX注册名为Catalina:type=ProtocolHandler,port=8009。有关Tomcat中JMX注册的内容,请阅读《TOMCAT源码分析——生命周期管理》一文。

CONNECTOR的启动

  根据《Tomcat源码分析——生命周期管理》一文的内容,我们知道Tomcat中有很多容器。ProtocolHandler的初始化稍微有些特殊,Server、Service、Connector这三个容器的初始化顺序为:Server->Service->Connector。值得注意的是,ProtocolHandler作为Connector的子容器,其初始化过程并不是由Connector的initInternal方法调用的,而是与启动过程一道被Connector的startInternal方法所调用。由于本文的目的是分析请求,所以直接从Connector的startInternal方法(见代码清单6)开始。

代码清单6

@Overrideprotected void startInternal() throws LifecycleException {// Validate settings before startingif (getPort() < 0) {throw new LifecycleException(sm.getString("coyoteConnector.invalidPort", Integer.valueOf(getPort())));}setState(LifecycleState.STARTING);try {protocolHandler.start();} catch (Exception e) {throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);}}

代码清单6说明了Connector的startInternal方法的执行顺序如下:

  1. 将Connector容器的状态更改为启动中(LifecycleState.STARTING);
  2. 启动ProtocolHandler;

初始化PROTOCOLHANDLER

  简单起见,我们以AbstractProtocol为例剖析ProtocolHandler的init方法,其实现见代码清单7。

代码清单7

@Overridepublic void init() throws Exception {if (getLog().isInfoEnabled()) {getLog().info(sm.getString("abstractProtocolHandler.init", getName()));}if (oname == null) {// Component not pre-registered so register itoname = createObjectName();if (oname != null) {Registry.getRegistry(null, null).registerComponent(this, oname, null);}}if (this.domain != null) {rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());Registry.getRegistry(null, null).registerComponent(getHandler().getGlobal(), rgOname, null);}String endpointName = getName();endpoint.setName(endpointName.substring(1, endpointName.length()-1));endpoint.setDomain(domain);endpoint.init();}

从代码清单7看到,AbstractProtocol的初始化步骤如下:

步骤一 将PROTOCOLHANDLER注册到JMX

  BIO Http Connector的ProtocolHandler(即Http11Protocol)的JMX注册名为Catalina:type=ProtocolHandler,port=8080。BIO Http Connector的MapperListener的注册名为  Catalina:type=Mapper,port=8080。AJP Connector的ProtocolHandler(即AjpProtocol)的JMX注册名为Catalina:type=ProtocolHandler,port=8009。AJP Connector的MapperListener的注册名为Catalina:type=Mapper,port=8009。有关Tomcat中JMX注册的内容,请阅读《TOMCAT源码分析——生命周期管理》一文。

  AbstractProtocol的构造器中还设置了提高socket性能的tcpNoDelay等选项,见代码清单8。

代码清单8

public AbstractProtocol(AbstractEndpoint<S> endpoint) {      this.endpoint = endpoint;//Deprecated      setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);}

步骤二 初始化endpoint,代码清单9是AbstractEndpoint类的init方法

代码清单9

public void init() throws Exception {if (bindOnInit) {bind();bindState = BindState.BOUND_ON_INIT;}if (this.domain != null) {// Register endpoint (as ThreadPool - historical name)oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");Registry.getRegistry(null, null).registerComponent(this, oname, null);for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {registerJmx(sslHostConfig);}}}

启动PROTOCOLHANDLER

  我们继续以AbstractProtocol为例,剖析ProtocolHandler的start方法,其实现见代码清单10。

代码清单10

@Overridepublic void start() throws Exception {if (getLog().isInfoEnabled()) {getLog().info(sm.getString("abstractProtocolHandler.start", getName()));}endpoint.start();// Start async timeout threadasyncTimeout = new AsyncTimeout();Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");int priority = endpoint.getThreadPriority();if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {priority = Thread.NORM_PRIORITY;}timeoutThread.setPriority(priority);timeoutThread.setDaemon(true);timeoutThread.start();}

调用endpoint的start方法(见代码清单11)接受请求的创建线程池并创建一定数量的接收请求线程。清单11是endpoint的start方法:

代码清单11

//AbstractEndpoint.java
public final void start() throws Exception {if (bindState == BindState.UNBOUND) {bind();bindState = BindState.BOUND_ON_START;}startInternal();//要调用子类NioEndpoint.java类的实现方法
    }//NioEndpoint.java
代码如下:
@Overridepublic void startInternal() throws Exception {if (!running) {running = true;paused = false;processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getProcessorCache());eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getEventCache());nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,socketProperties.getBufferPool());// Create worker collectionif ( getExecutor() == null ) {createExecutor();}initializeConnectionLatch();// Start poller threadspollers = new Poller[getPollerThreadCount()];for (int i=0; i<pollers.length; i++) {pollers[i] = new Poller();Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);pollerThread.setPriority(threadPriority);pollerThread.setDaemon(true);pollerThread.start();}startAcceptorThreads();}}

从代码清单11看出NioEndpoint的start方法的执行步骤如下:

步骤一 创建线程池与任务队列

如果NioEndpoint尚未处于运行中(即running等于true),才会创建线程池和任务队列。如果尚未创建线程池(即调用getExecutor方法等于null),则需要调用createExecutor方法(见代码清单12)创建线程池和任务队列TaskQueue。

代码清单12

public void createExecutor() {internalExecutor = true;TaskQueue taskqueue = new TaskQueue();TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);taskqueue.setParent( (ThreadPoolExecutor) executor);}
protected final void startAcceptorThreads() {int count = getAcceptorThreadCount();acceptors = new Acceptor[count];for (int i = 0; i < count; i++) {acceptors[i] = createAcceptor();String threadName = getName() + "-Acceptor-" + i;acceptors[i].setThreadName(threadName);Thread t = new Thread(acceptors[i], threadName);t.setPriority(getAcceptorThreadPriority());t.setDaemon(getDaemon());t.start();}}

步骤二 创建接收请线程

  如果NioEndpoint尚未处于运行中(即running等于true),才会创建接收请求线程。从代码清单11可以看出接收请求线程的数量主要由acceptorThreadCount控制,代码清单9已经告诉我们acceptorThreadCount的默认值为1,但是我们可以通过给Connector增加acceptorThreadCount属性来修改接收请求线程的数量。这些接收请求线程的主要工作由Acceptor完成,Acceptor的实质是一个Runnable,见代码清单13。

代码清单13

public abstract static class Acceptor implements Runnable {public enum AcceptorState {NEW, RUNNING, PAUSED, ENDED}protected volatile AcceptorState state = AcceptorState.NEW;public final AcceptorState getState() {return state;}private String threadName;protected final void setThreadName(final String threadName) {this.threadName = threadName;}protected final String getThreadName() {return threadName;}}

最后初始化MAPPERLISTENER

  MapperListener继承LifecycleMBeanBase,它的startInternal方法用于初始化,见代码清单14。

代码清单14

@Overridepublic void startInternal() throws LifecycleException {setState(LifecycleState.STARTING);Engine engine = service.getContainer();if (engine == null) {return;}findDefaultHost();addListeners(engine);Container[] conHosts = engine.findChildren();for (Container conHost : conHosts) {Host host = (Host) conHost;if (!LifecycleState.NEW.equals(host.getState())) {// Registering the host will register the context and wrappers
                registerHost(host);}}}

从代码清单14看到MapperListener的初始化步骤如下:

步骤一 查找默认Host

  StandardService的子容器包括:StandardEngine、Connector和Executor。MapperListener本身会持有Connector,所以可以通过各个容器的父子关系,找到Connector的同级容器StandardEngine。StandardHost是StandardEngine的子容器,Engine和Host的默认配置如下:

    <Engine name="Catalina" defaultHost="localhost"><Realm className="org.apache.catalina.realm.UserDatabaseRealm"resourceName="UserDatabase"/><Host name="localhost"  appBase="webapps"unpackWARs="true" autoDeploy="true"><Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"  prefix="localhost_access_log." suffix=".txt"pattern="%h %l %u %t &quot;%r&quot; %s %b" resolveHosts="false"/></Host></Engine>

findDefaultHost方法(见代码清单15)可以获取上面配置中的默认Host,Engine元素的defaultHost属性值必须要与配置的某个Host元素的name属性值相同。如果defaultHost的属性值配置无误,则会添加为MapperListener的Mapper对象属性的defaultHostName。

代码清单15

private void findDefaultHost() {Engine engine = service.getContainer();String defaultHost = engine.getDefaultHost();boolean found = false;if (defaultHost != null && defaultHost.length() >0) {Container[] containers = engine.findChildren();for (Container container : containers) {Host host = (Host) container;if (defaultHost.equalsIgnoreCase(host.getName())) {found = true;break;}String[] aliases = host.findAliases();for (String alias : aliases) {if (defaultHost.equalsIgnoreCase(alias)) {found = true;break;}}}}if(found) {mapper.setDefaultHostName(defaultHost);} else {log.warn(sm.getString("mapperListener.unknownDefaultHost",defaultHost, service));}}

 步骤二 将Host及其子容器Context,Context的子容器Wrapper注册到MapperListener的Mapper对象

  Mapper的数据结构,见代码清单16。

                          代码清单16
public final class Mapper {private static final Log log = LogFactory.getLog(Mapper.class);private static final StringManager sm = StringManager.getManager(Mapper.class);// ----------------------------------------------------- Instance Variables/*** Array containing the virtual hosts definitions.*/// Package private to facilitate testingvolatile MappedHost[] hosts = new MappedHost[0];/*** Default host name.*/private String defaultHostName = null;private volatile MappedHost defaultHost = null;/*** Mapping from Context object to Context version to support* RequestDispatcher mappings.*/private final Map<Context, ContextVersion> contextObjectToContextVersionMap =new ConcurrentHashMap<>();
// ------------------------------------------------------- Host Inner Classprotected static final class MappedHost extends MapElement<Host> {public volatile ContextList contextList;/*** Link to the "real" MappedHost, shared by all aliases.*/private final MappedHost realHost;/*** Links to all registered aliases, for easy enumeration. This field* is available only in the "real" MappedHost. In an alias this field* is <code>null</code>.*/private final List<MappedHost> aliases;/*** Constructor used for the primary Host** @param name The name of the virtual host* @param host The host*/public MappedHost(String name, Host host) {super(name, host);realHost = this;contextList = new ContextList();aliases = new CopyOnWriteArrayList<>();}/*** Constructor used for an Alias** @param alias    The alias of the virtual host* @param realHost The host the alias points to*/public MappedHost(String alias, MappedHost realHost) {super(alias, realHost.object);this.realHost = realHost;this.contextList = realHost.contextList;this.aliases = null;}public boolean isAlias() {return realHost != this;}public MappedHost getRealHost() {return realHost;}public String getRealHostName() {return realHost.name;}public Collection<MappedHost> getAliases() {return aliases;}public void addAlias(MappedHost alias) {aliases.add(alias);}public void addAliases(Collection<? extends MappedHost> c) {aliases.addAll(c);}public void removeAlias(MappedHost alias) {aliases.remove(alias);}}// ------------------------------------------------ ContextList Inner Classprotected static final class ContextList {public final MappedContext[] contexts;public final int nesting;public ContextList() {this(new MappedContext[0], 0);}private ContextList(MappedContext[] contexts, int nesting) {this.contexts = contexts;this.nesting = nesting;}public ContextList addContext(MappedContext mappedContext,int slashCount) {MappedContext[] newContexts = new MappedContext[contexts.length + 1];if (insertMap(contexts, newContexts, mappedContext)) {return new ContextList(newContexts, Math.max(nesting,slashCount));}return null;}public ContextList removeContext(String path) {MappedContext[] newContexts = new MappedContext[contexts.length - 1];if (removeMap(contexts, newContexts, path)) {int newNesting = 0;for (MappedContext context : newContexts) {newNesting = Math.max(newNesting, slashCount(context.name));}return new ContextList(newContexts, newNesting);}return null;}}// ---------------------------------------------------- Context Inner Classprotected static final class MappedContext extends MapElement<Void> {public volatile ContextVersion[] versions;public MappedContext(String name, ContextVersion firstVersion) {super(name, null);this.versions = new ContextVersion[] { firstVersion };}}protected static final class ContextVersion extends MapElement<Context> {public final String path;public final int slashCount;public final WebResourceRoot resources;public String[] welcomeResources;public MappedWrapper defaultWrapper = null;public MappedWrapper[] exactWrappers = new MappedWrapper[0];public MappedWrapper[] wildcardWrappers = new MappedWrapper[0];public MappedWrapper[] extensionWrappers = new MappedWrapper[0];public int nesting = 0;private volatile boolean paused;public ContextVersion(String version, String path, int slashCount,Context context, WebResourceRoot resources,String[] welcomeResources) {super(version, context);this.path = path;this.slashCount = slashCount;this.resources = resources;this.welcomeResources = welcomeResources;}public boolean isPaused() {return paused;}public void markPaused() {paused = true;}}// ---------------------------------------------------- Wrapper Inner Classprotected static class MappedWrapper extends MapElement<Wrapper> {public final boolean jspWildCard;public final boolean resourceOnly;public MappedWrapper(String name, Wrapper wrapper, boolean jspWildCard,boolean resourceOnly) {super(name, wrapper);this.jspWildCard = jspWildCard;this.resourceOnly = resourceOnly;}}

根据代码清单16,我们知道Mapper中维护着一个MappedHost数组,每个MappedHost中有一个ContextList,这个ContextList中维护着一个Context数组。每个Context维护着一个defaultWrapper,三个Wrapper数组(exactWrappers、wildcardWrappers、extensionWrappers)。下面对Host、Context及Wrapper进行功能上的介绍:

  • Host:代表一个虚拟主机,各Host的name不能相同,appBase代表各虚拟主机的应用发布位置;
  • Context:代表一个应用,Context可以根据应用的/WEB-INF/web.xml文件中定义的servlet来处理请求。一个Host下可以有多个Context;
  • Wrapper: 代表一个Servlet或者jsp,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。

以我本地为例,在MapperListener类中的startInternal方法中的最后打个断点,并运行debugger模式,就可以看到注册到Mapper中的Host及其子容器,如图2所示。

图2  注册到Mapper中的Host及其Context子容器

图2说明Host内一共5个Context,由于我的Tomcat是从svn拉下来的,所以webapps目录下的.svn文件夹也是一个Context,除了这个天外来客,我将其它与请求有关的容器整理后用图3来展示。

图3  我本地的Host、Context及Wrapper

可以看docs这个context的所有属性,如下图:

  至此,Tomcat中为请求处理的准备工作已经完成。有关请求的处理过程请继续阅读《Tomcat源码分析——请求原理分析(中)》一文。

本文原创首发:博客园,原文链接:http://www.cnblogs.com/jiaan-geng/p/4875249.html

转载于:https://www.cnblogs.com/nizuimeiabc1/p/8672723.html

tomcat8源码分析-Connector初始化相关推荐

  1. Tomcat8源码分析系列-spring boot集成tomcat

    前言 本文基于 spring boot 1.5.9 spring boot 支持目前主流的 servlet 容器,包括 tomcat.jetty.undertow,可以在我们的项目中方便地集成这些 s ...

  2. Solr初始化源码分析-Solr初始化与启动

    用solr做项目已经有一年有余,但都是使用层面,只是利用solr现有机制,修改参数,然后监控调优,从没有对solr进行源码级别的研究.但是,最近手头的一个项目,让我感觉必须把solrn内部原理和扩展机 ...

  3. celery源码分析-worker初始化分析(下)

    celery源码分析 本文环境python3.5.2,celery4.0.2,django1.10.x系列 celery的worker启动 在上文中分析到了Hub类的初始化,接下来继续分析Pool类的 ...

  4. celery源码分析-wroker初始化分析(上)

    celery源码分析 本文环境python3.5.2,celery4.0.2,django1.10.x系列 celery与Django的配合使用 首先,在安装有django的环境中创建一个django ...

  5. velocity源码分析:初始化之日志系统

    之前在"velocity源码分析:velocity初始化"文章中粗略地介绍了velocity整体的初始化过程,包括各个系统的初始化,本文主要介绍日志系统初始化. 日志系统类图: 概 ...

  6. Tomcat8源码分析系列-启动分析(四) webapp

    前言 上一篇文章中我们分析了 Service.Engine.Host.Pipeline.Valve 组件的启动逻辑,在 HostConfig 中会实例化 StandardContext,并启动 Con ...

  7. ConcurrentHashMap的源码分析-CounterCells初始化图解

    初始化长度为2的数组,然后随机得到指定的一个数组下标,将需要新增的值加入到对应下标位置处 transfer扩容阶段 判断是否需要扩容,也就是当更新后的键值对总数baseCount >= 阈值si ...

  8. Nginx源码分析-启动初始化过程(一)

    Nginx的启动初始化在src/core/nginx.c的main函数中完成,当然main函数是整个Nginx的入口,除了完成启动初始化任务以外,也必定是所有功能模块的入口之处.Nginx的初始化工作 ...

  9. NFC源码分析之初始化流程

    Nfc的app代码位于: android/package/apps/Nfc/... 编译生成Nfc的apk和libnfc_nci_jni.so Nfc的协议栈和Hal相关的代码位于: system/n ...

最新文章

  1. 中科院sci分区表_最新 | 2020中科院SCI分区表 食品科技类
  2. 欧氏空间内积定义_MP5:内积、外积、面积、Hermite内积、辛内积
  3. awk 内嵌正则 提取字符串_使用awk提取字符串中的数字或字母
  4. ABP框架系列之六:(Value-Objects-值对象)
  5. swift UI专项训练15 PcikerView老虎机视图
  6. c#制作的简单的画图板
  7. 玩转VIM编辑器-vim的命令行
  8. ReSharper2017.3的列对齐、排版格式、列对齐错误的修复
  9. HFT-CNN:层级多标签分类,让你的模型多学习几次
  10. 手写springboot
  11. 测试开发工程师的简历和面试准备
  12. Au 音频效果参考:调制
  13. 通讯录系统图形化界面(C++,Qt5.12)(Visual Studio2019,QtCreator)(初学)
  14. Qt关于connect和拉姆达表达式
  15. AliOS Things 3.3.0 : KV组件介绍
  16. 鸿蒙os2.0第一批升级名单,首批升级鸿蒙OS名单终于来了!
  17. 前端实现文件上传的断点续传
  18. 远程部署代码debug在本地调试
  19. python学习——图形界面
  20. 卸载VMware时提示“The MSI failed”

热门文章

  1. 数据可视化组队学习:《Task06 - 场景案例显神通》笔记
  2. 【CyberSecurityLearning 33】Nginx和Tomcat服务的搭建、Nginx负载均衡
  3. Idea中一个服务按多个端口同时启动
  4. UNIX再学习 -- 系统数据文件和信息
  5. Hi3516A开发--挂载SD卡和U盘
  6. dijkstra+堆优化
  7. 【朋克智库】比特币详解2.0——比特币的沉沦
  8. Understanding Ethereum Smart Contracts
  9. Distributed Systems笔记-Web Service Design Patterns
  10. 在数据库技术中脏数据是指_数据库安全关键技术之数据库加密技术