Spring Boot启动过程(七):Connector初始化
Connector实例的创建已经在Spring Boot启动过程(四):Spring Boot内嵌Tomcat启动中提到了:
Connector是LifecycleMBeanBase的子类,先是设置LifecycleState为LifecycleState.NEW,构造首先执行setProtocol,设置protocolHandlerClassName为"org.apache.coyote.http11.Http11NioProtocol"事实上它默认值就是这个,然后通过反射创建此协议处理器的实例,此时开始执行Http11NioProtocol的构造函数:
public Http11NioProtocol() {
super(new NioEndpoint());
}
初始化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实现,所以执行的是:
@Override
protected void finishRefresh() {
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 void addConnector(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 listeners
support.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() throws Exception {
if (bindOnInit) {
bind();
bindState = BindState.BOUND_ON_INIT;
}
}
Connetor的init结束了。然后Connetor的startInternal,这里其实只做了一件事:protocolHandler.start()。协议处理器的start又start了endpoint:
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bind();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
这次走到了bind,首先ServerSocketChannel.open:
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.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, poller
if (acceptorThreadCount == 0) {
// FIXME: Doesn't seem to work that well with multiple accept threads
acceptorThreadCount = 1;
}
if (pollerThreadCount <= 0) {
//minimum one poller thread
pollerThreadCount = 1;
}
实例化private volatile CountDownLatch stopLatch为pollerThreadCount数量,用闭锁应该是希望多个pollerThread同时开始执行吧,后面确定一下。如果有需要初始化SSL:initialiseSsl吐槽下这里命名,方法体里用的SSL偏偏这里首字母大写,大家不要学。selectorPool.open():
public void open() throws IOException {
enabled = true;
getSharedSelector();
if (SHARED) {
blockingSelector = new NioBlockingSelector();
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 = new LimitLatch(getMaxConnections());
}
启动poller线程的代码直接看吧,没啥好解释的:
pollers = 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();
}
启动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 paused
while (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 paused
while (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 instanceof TomcatEmbeddedContext) {
((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
微信公众号:
Spring Boot启动过程(七):Connector初始化的更多相关文章
- Spring Boot启动过程(四):Spring Boot内嵌Tomcat启动
之前在Spring Boot启动过程(二)提到过createEmbeddedServletContainer创建了内嵌的Servlet容器,我用的是默认的Tomcat. private void cr ...
- Spring Boot启动过程及回调接口汇总
Spring Boot启动过程及回调接口汇总 链接: https://www.itcodemonkey.com/article/1431.html 来自:chanjarster (Daniel Qia ...
- Spring Boot启动过程(三)
我已经很精简了,两篇(Spring Boot启动过程(一).pring Boot启动过程(二))依然没写完,接着来. refreshContext之后的方法是afterRefresh,这名字起的真.. ...
- Spring Boot 启动过程
一切从SpringApplication.run()开始,最终返回一个ConfigurableApplicationContext 构造了一个SpringApplication对象,然后调用它的run ...
- Spring Boot启动过程(二)
书接上篇 该说refreshContext(context)了,首先是判断context是否是AbstractApplicationContext派生类的实例,之后调用了强转为AbstractAppl ...
- Spring Boot启动过程(一)
之前在排查一个线上问题时,不得不仔细跑了很多遍Spring Boot的代码,于是整理一下,我用的是1.4.3.RELEASE. 首先,普通的入口,这没什么好说的,我就随便贴贴代码了: SpringAp ...
- 转:Spring Boot启动过程
之前在排查一个线上问题时,不得不仔细跑了很多遍Spring Boot的代码,于是整理一下,我用的是1.4.3.RELEASE. 首先,普通的入口,这没什么好说的,我就随便贴贴代码了: SpringAp ...
- Spring Boot 启动过程及 自定义 Listener等组件
一.启动过程 二.自定义组件 package com.example.jdbc.listener; import org.springframework.context.ApplicationCont ...
- Spring boot 启动过程解析 logback
使用 Spring Boot 默认的日志框架 Logback. 所有这些 POM 依赖的好处在于为开发 Spring 应用提供了一个良好的基础.Spring Boot 所选择的第三方库是经过考虑的,是 ...
随机推荐
- SEO-站外优化规范
站外优化规范 新站 前期(提高网站曝光率<信息发布平台>) 一. 分类目录信息发布 二. 黄页网信息发布 三. 友链平台信息发布 四. 各大论坛引蜘蛛区信息发布 五. 网址提交 六. 社区 ...
- Omi应用md2site-0.5.0发布-支持动态markdown拉取解析
写在前面 Md2site是基于Omi的一款Markdown转网站工具,使用简单,生成的文件轻巧,功能强大. 官网:http://alloyteam.github.io/omi/md2site/ Git ...
- 解决WebStorm无法连接到Chrome
问题: 点击 中的chrome时,出现了错误,如下: 解决办法: 找到 File>setting>Web Browser 修改为 C:\Program Files (x86)\Google ...
- 手动的写一个structs
为了更好的学习框架的运行机制,这里开始学习框架之前,介绍一个简单的自定义的框架. 需求: 登录:id:aaa,pwd:888登录成功之后,跳转到,index.jsp页面并显示,欢迎你,aaa 注册,页 ...
- 百度推出 MIP Shell 链接
在站长将站点 MIP 化时,需要关注 URL 的一共有三个:MIP URL, MIP-Cache URL 以及 MIP-Shell URL. 从 URL 说起 在互联网中,URL 定义页面的地址,每个 ...
- ISO c++ 14 重点介绍[译]
原文链接 http://marknelson.us/2014/09/11/highlights-of-iso-c14/ 下面是对你的日常开发有重大影响的C++14新变动,列出了一些示例代码,并讨论何时 ...
- JSON对象转换成字符串【JSON2.JS】
下载地址 https://github.com/douglascrockford/JSON-js JSON.JS和JSON2.JS的区别 JSON.JS使用的方法名称不同,用的是toJSONStrin ...
- cuda编程学习2——add
cudaMalloc()分配的指针有使用限制,设备指针的使用限制总结如下: 1.可以将其传递给在设备上执行的函数 2.可以在设备代码中使用其进行内存的读写操作 3.可以将其传递给在主机上执行的函数 4 ...
- VAO VBO IBO大乱炖
最近对程序中绘制卡顿的问题忍无可忍,终于决定下手处理了.程序涉及的绘制比较多,除了点.线.三角形.多边形.圆柱体之外,还有自组格式模型.开始想全部采用显示列表优化,毕竟效率最高,虽然显示列表存在编译之 ...
- NIO(四、Selector)
目录 NIO(一.概述) NIO(二.Buffer) NIO(三.Channel) NIO(四.Selector) Selector 前面两个章节都描述了Buffer和Channel,那这个章节就描述 ...