Connector实例的创建已经在Spring Boot启动过程(四):Spring Boot内嵌Tomcat启动中提到了:

  

  Connector是LifecycleMBeanBase的子类,先是设置LifecycleState为LifecycleState.NEW,构造首先执行setProtocol,设置protocolHandlerClassName为"org.apache.coyote.http11.Http11NioProtocol"事实上它默认值就是这个,然后通过反射创建此协议处理器的实例,此时开始执行Http11NioProtocol的构造函数:

publicHttp11NioProtocol() {super(newNioEndpoint());}

  初始化NioEndpoint过程中初始化了NioSelectorPool,NioSelectorShared默认为true,即所有的SocketChannel共享一个Selector;设置pollerThreadCount,socket超时时间等。然后就是将new出来的NioEndPoint一路super,直到AbstractProtocol:

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

  关于soLinger可以参考内嵌Tomcat的Connector对象的静态代码块。之后是外层AbstractHttp11Protocol的构造函数,Handler就是这里初始化并set的,这部分和上一块所有的set最后都是到endpoint的:

public AbstractHttp11Protocol(AbstractEndpoint<S>endpoint) {super(endpoint);setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);setHandler(cHandler);getEndpoint().setHandler(cHandler);}

  回到Connector将初始化好的Http11NioProtocol传给this.protocolHandler(AbstractProtocol<S>实现了protocolHandler),之后就是下面这几句代码就结束Connector初始化了:

if (!Globals.STRICT_SERVLET_COMPLIANCE) {URIEncoding= "UTF-8";URIEncodingLower=URIEncoding.toLowerCase(Locale.ENGLISH);}

  之后就是启动了,在Spring Boot启动过程(二)提到过一点,在finishRefresh中,由于AbstractApplicationContext被EmbeddedWebApplicationContext实现,所以执行的是:

@Overrideprotected voidfinishRefresh() {super.finishRefresh();EmbeddedServletContainer localContainer=startEmbeddedServletContainer();if (localContainer != null) {publishEvent(new EmbeddedServletContainerInitializedEvent(this, localContainer));}}

  startEmbeddedServletContainer方法中的localContainer.start的前几句代码:

addPreviouslyRemovedConnectors();Connector connector= this.tomcat.getConnector();if (connector != null && this.autoStart) {startConnector(connector);}

  addPreviouslyRemovedConnectors方法将Spring Boot启动过程(四):Spring Boot内嵌Tomcat启动中提到的从Service中解绑的Connector绑定回来了。具体绑定方法:

public voidaddConnector(Connector connector) {synchronized(connectorsLock) {connector.setService(this);Connector results[]= new Connector[connectors.length + 1];System.arraycopy(connectors,0, results, 0, connectors.length);results[connectors.length]=connector;connectors=results;if(getState().isAvailable()) {try{connector.start();}catch(LifecycleException e) {log.error(sm.getString("standardService.connector.startFailed",connector), e);}}//Report this property change to interested listenerssupport.firePropertyChange("connector", null, connector);}}

  加了同步锁,绑定了一下service,start流程之前说过好多次,就不细说了。Connector的initInternal,先是super(LifecycleMBeanBase);之后两句引入了CoyoteAdapter:protected Adapter adapter = new CoyoteAdapter(this),protocolHandler.setAdapter(adapter);确保parseBodyMethodsSet有默认值,没有就设置HTTP方法为支持请求体的POST方法为默认;接着初始化protocolHandler,先是关于ALPN支持的,我这里没走,直接进入spuer.init(AbstractProtocol),生成ObjectName(MBean之前说过的):并注册Registry.getRegistry(null, null).registerComponent(this, oname, null),生成并注册线程池和Global Request Processor:

  接着endpoint的init,首先是testServerCipherSuitesOrderSupport方法,这个方法只判断jdk7及以下版本,我这不走也没什么内容其实;然后是super.init(AbstractEndpoint),然而此时其实并没有走:

public void init() throwsException {if(bindOnInit) {bind();bindState=BindState.BOUND_ON_INIT;}}

  Connetor的init结束了。然后Connetor的startInternal,这里其实只做了一件事:protocolHandler.start()。协议处理器的start又start了endpoint:

public final void start() throwsException {if (bindState ==BindState.UNBOUND) {bind();bindState=BindState.BOUND_ON_START;}startInternal();}

  这次走到了bind,首先ServerSocketChannel.open:

public static ServerSocketChannel open() throwsIOException {returnSelectorProvider.provider().openServerSocketChannel();}

  然后设置超时时间,绑定端口和IP,设置积压(backlog)数量,配置阻塞serverSock.configureBlocking(true)关于阻塞模式,在网上摘抄了一段:

Tomcat在使用Java NIO的时候,将ServerSocketChannel配置成阻塞模式,这样可以方便地对ServerSocketChannel编写程序。当accept方法获得一个SocketChannel,并没有立即从线程池中取出一个线程来处理这个SocketChannel,而是构建一个OP_REGISTER类型的PollerEvent,并放到Poller.events队列中。Poller线程会处理这个PollerEvent,发现是OP_REGISTER类型,会在Poller.selector上注册一个这个SocketChannel的OP_READ就绪事件。因为Java NIO的wakeup特性,使用wakeupCount信号量控制Selector.wakeup()方法,非阻塞方法Selector.selectNow()和阻塞方法Selector.select()的调用。我们在编写Java NIO程序时候也可以参考这种方式。在SocketChannel上读的时候,分成非阻塞模式和阻塞模式。非阻塞模式,如果读不到数据,则直接返回了;如果读到数据则继续读。
阻塞模式。如果第一次读取不到数据,会在NioSelectorPool提供的Selector对象上注册OP_READ就绪事件,并循环调用Selector.select(long)方法,超时等待OP_READ就绪事件。如果OP_READ事件已经就绪,并且接下来读到数据,则会继续读。read()方法整体会根据readTimeout设置进行超时控制。若超时,则会抛出SocketTimeoutException异常。在SocketChannel上写的时候也分成非阻塞模式和阻塞模式。非阻塞模式,写数据之前不会监听OP_WRITE事件。如果没有成功,则直接返回。
阻塞模式。第一次写数据之前不会监听OP_WRITE就绪事件。如果没有写成功,则会在NioSelectorPool提供的selector注册OP_WRITE事件。并循环调用Selector.select(long)方法,超时等待OP_WRITE就绪事件。如果OP_WRITE事件已经就绪,并且接下来写数据成功,则会继续写数据。write方法整体会根据writeTimeout设置进行超时控制。如超时,则会抛出SocketTimeoutException异常。在写数据的时候,开始没有监听OP_WRITE就绪事件,直接调用write()方法。这是一个乐观设计,估计网络大部分情况都是正常的,不会拥塞。如果第一次写没有成功,则说明网络可能拥塞,那么再等待OP_WRITE就绪事件。阻塞模式的读写方法没有在原有的Poller.selector上注册就绪事件,而是使用NioSelectorPool类提供的Selector对象注册就绪事件。这样的设计可以将各个Channel的就绪事件分散注册到不同的Selector对象中,避免大量Channel集中注册就绪事件到一个Selector对象,影响性能。http://www.linuxidc.com/Linux/2015-02/113900p2.htm

  为了注释,贴一下接下来的代码:

//Initialize thread count defaults for acceptor, pollerif (acceptorThreadCount == 0) {//FIXME: Doesn't seem to work that well with multiple accept threadsacceptorThreadCount = 1;}if (pollerThreadCount <= 0) {//minimum one poller threadpollerThreadCount = 1;}

  实例化private volatile CountDownLatch stopLatch为pollerThreadCount数量,用闭锁应该是希望多个pollerThread同时开始执行吧,后面确定一下。如果有需要初始化SSL:initialiseSsl吐槽下这里命名,方法体里用的SSL偏偏这里首字母大写,大家不要学。selectorPool.open():

public void open() throwsIOException {enabled= true;getSharedSelector();if(SHARED) {blockingSelector= newNioBlockingSelector();blockingSelector.open(getSharedSelector());}}

}

  AbstractEndpoint的bind方法就执行完了,绑定状态设为BOUND_ON_START然后执行startInternal,这个方法在NioEndpoint中。先判断是否是正在运行状态,如果不是就置为是,暂停状态置为否,然后初始化了三个SynchronizedStack,这是Tomcat自定义的简化同步栈,自定义的结构好处就是既能满足需要又能提高时间空间利用率,最合适自己的场景:

processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, socketProperties.getProcessorCache());eventCache= new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, socketProperties.getEventCache());nioChannels= new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, socketProperties.getBufferPool());

  createExecutor线程池并设置给任务队列:

  initializeConnectionLatch根据配置设定最大允许的并发连接数maxConnections,实现方法是自定义的锁结构LimitLatch:

if (maxConnections==-1) return null;if (connectionLimitLatch==null) {connectionLimitLatch= newLimitLatch(getMaxConnections());}

  启动poller线程的代码直接看吧,没啥好解释的:

pollers = newPoller[getPollerThreadCount()];for (int i=0; i<pollers.length; i++) {pollers[i]= newPoller();Thread pollerThread= new Thread(pollers[i], getName() + "-ClientPoller-"+i);pollerThread.setPriority(threadPriority);pollerThread.setDaemon(true);pollerThread.start();}

  启动acceptor线程是startAcceptorThreads一样的方式,代码就不贴了。endpoint的start之后,启动了一个异步超时线程(例Thread[http-nio-8080-AsyncTimeout,5,main]),这个线程会每隔一段时间检查每一个等待队列中的协议处理器,判断如果超时了,就会给它发一个超时事件:socketWrapper.processSocket(SocketEvent.TIMEOUT, true)

while(asyncTimeoutRunning) {try{Thread.sleep(1000);}catch(InterruptedException e) {//Ignore
}long now =System.currentTimeMillis();for(Processor processor : waitingProcessors) {processor.timeoutAsync(now);}//Loop if endpoint is pausedwhile (endpoint.isPaused() &&asyncTimeoutRunning) {try{Thread.sleep(1000);}catch(InterruptedException e) {//Ignore
}}}

while(asyncTimeoutRunning) {try{Thread.sleep(1000);}catch(InterruptedException e) {//Ignore
}long now =System.currentTimeMillis();for(Processor processor : waitingProcessors) {processor.timeoutAsync(now);}//Loop if endpoint is pausedwhile (endpoint.isPaused() &&asyncTimeoutRunning) {try{Thread.sleep(1000);}catch(InterruptedException e) {//Ignore
}}}

  至此connector.start执行结束,因为我也没注册什么监听器,所以这部分没提,而且相关内容之前都有写。当循环绑定connect结束后,暂存的绑定信息也就没用了,移除掉。

  回到TomcatEmbeddedServletContainer,接下来:

if (connector != null && this.autoStart) {startConnector(connector);}

  startConnector:

for (Container child : this.tomcat.getHost().findChildren()) {if (child instanceofTomcatEmbeddedContext) {((TomcatEmbeddedContext) child).deferredLoadOnStartup();}}

//Earlier versions of Tomcat used a version that returned void. If that//version is used our overridden loadOnStart method won't have been called//and the original will have already run.super.loadOnStartup(findChildren());

  具体什么版本会怎么样,就不考证了,反正该执行的都会执行,只不过位置可能不一样。实际上,方法取出了所有的Wapper(StandardWrapper),执行了它们的load方法。load方法取出了Servlet绑定并记录加载时间,并设置了jsp监控的mbean,期间还检查了servlet的安全注解比如是否允许访问和传输是否加密(EmptyRoleSemantic,TransportGuarantee):

InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();

                servlet = (Servlet) instanceManager.newInstance(servletClass);

processServletSecurityAnnotation(servlet.getClass());

  初始化servlet,比如输入输出的buffer sizes大小是否合理(最小256):

instanceInitialized = true;

  在load前后都有判断如果bean加载器和当前线程上下文加载器不同时用在用的bean加载器替换当前线程上下文类加载器,因为用上下文加载器的话,这一步加载的东西就不能被多个Context共享了,后面又来了一次,推测是为了防止加载的过程中为了避免SPI的加载问题而被替换为线程上下文加载器。其实这里已经和本篇博客没什么关系了,但是秉着流水账的风格,把它贴完:

Context context =findContext();ContextBindings.unbindClassLoader(context, getNamingToken(context), getClass().getClassLoader());

if(ContextAccessController.checkSecurityToken(obj, token)) {Object o=clObjectBindings.get(classLoader);if (o == null || !o.equals(obj)) {return;}clBindings.remove(classLoader);clObjectBindings.remove(classLoader);}

  本来是因为Tomcat一个BUG造成CLOSE_WAIT问题屡的这些代码,然而这里其实别没有直接到,因为只是启动,还没到运行,以后如果有机会写运行部分再说吧。

  以上。

==========================================================

咱最近用的github:https://github.com/saaavsaaa

微信公众号:

                      

转载于:https://my.oschina.net/saaavsaaa/blog/909885

Spring Boot启动过程(七):Connector初始化相关推荐

  1. [Spring Boot] 2. Spring Boot 启动过程定制化

    在上一篇文章中,从源码角度介绍了Spring Boot的启动过程.启动的代码虽然只有短短的一行,但是背后所做的工作还真不少,其中有一些可以定制化的部分,主要分为以下几个方面: 初始化器(Initial ...

  2. Spring Boot启动过程(二)

    书接上篇 该说refreshContext(context)了,首先是判断context是否是AbstractApplicationContext派生类的实例,之后调用了强转为AbstractAppl ...

  3. Spring Boot启动过程中相关事件

    Spring容器创建前: ApplicationStartingEvent: Spring容器创建前,项目启动时执行; ApplicationEnvironmentPreparedEvent: Spr ...

  4. Spring boot 启动过程

    先Mark, https://www.cnblogs.com/trgl/p/7353782.html https://blog.csdn.net/zl1zl2zl3/article/details/7 ...

  5. 强大的Spring Boot启动监听器事件-初始化系统账号密码

    文章目录 前言 一.SpringApplicationEvents 事件类型 1.1 ApplicationStartingEvent 1.2 ApplicationEnvironmentPrepar ...

  6. Spring Boot启动过程源码分析--转

    https://blog.csdn.net/dm_vincent/article/details/76735888 关于Spring Boot,已经有很多介绍其如何使用的文章了,本文从源代码(基于Sp ...

  7. Spring Boot 启动事件顺序

    大家都知道,在 Spring 框架中事件和监听无处不在,打通了 Spring 框架的任督二脉,事件和监听也是 Spring 框架必学的核心知识之一. 一般来说,我们很少会使用到应用程序事件,但我们也不 ...

  8. Spring Boot 启动事件和监听器,太强大了!

    大家都知道,在 Spring 框架中事件和监听无处不在,打通了 Spring 框架的任督二脉,事件和监听也是 Spring 框架必学的核心知识之一. 一般来说,我们很少会使用到应用程序事件,但我们也不 ...

  9. 在Spring Boot启动时运行代码

    Spring Boot会自动为我们执行很多配置,但是迟早您将不得不做一些自定义工作. 在本文中,您将学习如何进入应用程序引导生命周期并在Spring Boot启动时执行代码 . 因此,让我们看看该框架 ...

最新文章

  1. [推荐]在JavaScript中实现命名空间
  2. JFreeChart插件使用
  3. 编写你的第一个 Django 应用,第 2 部分
  4. java中ssm付款代码,ssm实现支付宝支付功能(图文详解)
  5. vrp 节约算法 c++_滴滴技术:浅谈滴滴派单算法
  6. Android P(3)---Android P版本刘海屏适配指南
  7. python如何访问对象的属性_Python:从存储在字典中的对象访问对象属性
  8. STL源码剖析(侯捷)笔记——STL概述
  9. 蓝牙方案,蓝牙国密读卡器,TypeA/TypeB/Felca卡读写,分享蓝牙NFC读写器带USB接口,银行卡/CPU卡/NTAG213/Mifare卡蓝牙读写器,usb多通道通讯
  10. 用户用户组,与密码管理,su,sudo命令,限制root远程登陆
  11. 常见的四种EDI传输协议
  12. 小话设计模式(十三)职责链模式
  13. BDrate、BDBR、BDPSNR的计算原理和程序
  14. ThinkPad T410i 2516A21 升級手札(換SSD固態硬碟、I7 CPU、開機20秒)
  15. 斩获数亿元B轮融资,这家Tier 1抢跑「L2/L2+」主战场
  16. 解决区块链三大问题的利器
  17. 使用杉川3i-T1单线激光雷达和Cartographer库SLAM问题及解决
  18. 张量分解浅谈(四 Tucker 分解)
  19. 计算机网络总结600字,电脑的自述600字
  20. 计算机stem案例,一篇文章让你读懂STEM!

热门文章

  1. 职高计算机专业可以考哪些大学,职业高中可以考什么大学?怎么选择合适的大学?...
  2. Android国际化-图片国际化和文本字符国际化
  3. 股市分析软件开发界面设计02
  4. 集合Collection和Map的基础掌握
  5. 虚拟幻想的图灵机竟是人工智能的起源?
  6. Java开发十大必备网站
  7. 【iintern】在美国留学,你要知道cpt和opt有什么区别!
  8. 2019年女王传奇胡荧魅惑性学堂开课现场
  9. 【ARM】STM32内置DFU的使用
  10. 视频监控存储六方面 解开视频监控的存储秘密