本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到个人仓库里查看前端
https://github.com/h2pl/Java-Tutorialjava
喜欢的话麻烦点下Star哈python
文章首发于个人我的博客:git
www.how2playlife.com程序员
本文是微信公众号【Java技术江湖】的《走进JavaWeb技术世界》其中一篇,本文部份内容来源于网络,为了把本文主题讲得清晰透彻,也整合了不少我认为不错的技术博客内容,引用其中了一些比较好的博客文章,若有侵权,请联系做者。github
该系列博文会告诉你如何从入门到进阶,从servlet到框架,从ssm再到SpringBoot,一步步地学习JavaWeb基础知识,并上手进行实战,接着了解JavaWeb项目中常常要使用的技术和组件,包括日志组件、Maven、Junit,等等内容,以便让你更完整地了解整个Java Web技术体系,造成本身的知识框架。web
为了更好地总结和检验你的学习成果,本系列文章也会提供每一个知识点对应的面试题以及参考答案。面试
若是对本系列文章有什么建议,或者是有什么疑问的话,也能够关注公众号【Java技术江湖】联系做者,欢迎你参与本系列博文的创做和修订。数据库
文末赠送8000G的Java架构师学习资料,须要的朋友能够到文末了解领取方式,资料包括Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源) apache
Tomcat 的结构很复杂,可是 Tomcat 也很是的模块化,找到了 Tomcat 最核心的模块,您就抓住了 Tomcat 的“七寸”。下面是 Tomcat 的整体结构图:
从上图中能够看出 Tomcat 的心脏是两个组件:Connector 和 Container,关于这两个组件将在后面详细介绍。Connector 组件是能够被替换,这样能够提供给服务器设计者更多的选择,由于这个组件是如此重要,不只跟服务器的设计的自己,并且和不一样的应用场景也十分相关,因此一个 Container 能够选择对应多个 Connector。多个 Connector 和一个 Container 就造成了一个 Service,Service 的概念你们都很熟悉了,有了 Service 就能够对外提供服务了,可是 Service 还要一个生存的环境,必需要有人可以给她生命、掌握其生死大权,那就非 Server 莫属了。因此整个 Tomcat 的生命周期由 Server 控制。
咱们将 Tomcat 中 Connector、Container 做为一个总体比做一对情侣的话,Connector 主要负责对外交流,能够比做为 Boy,Container 主要处理 Connector 接受的请求,主要是处理内部事务,能够比做为 Girl。那么这个 Service 就是链接这对男女的结婚证了。是 Service 将它们链接在一块儿,共同组成一个家庭。固然要组成一个家庭还要不少其它的元素。
说白了,Service 只是在 Connector 和 Container 外面多包一层,把它们组装在一块儿,向外面提供服务,一个 Service 能够设置多个 Connector,可是只能有一个 Container 容器。这个 Service 接口的方法列表以下:
从 Service 接口中定义的方法中能够看出,它主要是为了关联 Connector 和 Container,同时会初始化它下面的其它组件,注意接口中它并无规定必定要控制它下面的组件的生命周期。全部组件的生命周期在一个 Lifecycle 的接口中控制,这里用到了一个重要的设计模式,关于这个接口将在后面介绍。
Tomcat 中 Service 接口的标准实现类是 StandardService 它不只实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就能够控制它下面的组件的生命周期了。StandardService 类结构图以下:
从上图中能够看出除了 Service 接口的方法的实现以及控制组件生命周期的 Lifecycle 接口的实现,还有几个方法是用于在事件监听的方法的实现,不只是这个 Service 组件,Tomcat 中其它组件也一样有这几个方法,这也是一个典型的设计模式,将在后面介绍。
下面看一下 StandardService 中主要的几个方法实现的代码,下面是 setContainer 和 addConnector 方法的源码:
public void setContainer(Container container) {复制代码
Container oldContainer = this.container;复制代码
if ((oldContainer != null) && (oldContainer instanceof Engine))复制代码
((Engine) oldContainer).setService(null);复制代码
this.container = container;复制代码
if ((this.container != null) && (this.container instanceof Engine))复制代码
((Engine) this.container).setService(this);复制代码
if (started && (this.container != null) && (this.container instanceof Lifecycle)) {复制代码
try {复制代码
((Lifecycle) this.container).start();复制代码
} catch (LifecycleException e) {复制代码
;复制代码
}复制代码
}复制代码
synchronized (connectors) {复制代码
for (int i = 0; i < connectors.length; i++)复制代码
connectors[i].setContainer(this.container);复制代码
}复制代码
if (started && (oldContainer != null) && (oldContainer instanceof Lifecycle)) {复制代码
try {复制代码
((Lifecycle) oldContainer).stop();复制代码
} catch (LifecycleException e) {复制代码
;复制代码
}复制代码
}复制代码
support.firePropertyChange("container", oldContainer, this.container);复制代码
}
复制代码
这段代码很简单,其实就是先判断当前的这个 Service 有没有已经关联了 Container,若是已经关联了,那么去掉这个关联关系—— oldContainer.setService(null)。若是这个 oldContainer 已经被启动了,结束它的生命周期。而后再替换新的关联、再初始化并开始这个新的 Container
的生命周期。最后将这个过程通知感兴趣的事件监听程序。这里值得注意的地方就是,修改 Container 时要将新的 Container 关联到每一个 Connector,还好 Container 和 Connector 没有双向关联,否则这个关联关系将会很难维护。
public void addConnector(Connector connector) {复制代码
synchronized (connectors) {复制代码
connector.setContainer(this.container);复制代码
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 (initialized) {复制代码
try {复制代码
connector.initialize();复制代码
} catch (LifecycleException e) {复制代码
e.printStackTrace(System.err);复制代码
}复制代码
}复制代码
if (started && (connector instanceof Lifecycle)) {复制代码
try {复制代码
((Lifecycle) connector).start();复制代码
} catch (LifecycleException e) {复制代码
;复制代码
}复制代码
}复制代码
support.firePropertyChange("connector", null, connector);复制代码
}复制代码
}
复制代码
上面是 addConnector 方法,这个方法也很简单,首先是设置关联关系,而后是初始化工做,开始新的生命周期。这里值得一提的是,注意 Connector 用的是数组而不是 List 集合,这个从性能角度考虑能够理解,有趣的是这里用了数组可是并无向咱们日常那样,一开始就分配一个固定大小的数组,它这里的实现机制是:从新建立一个当前大小的数组对象,而后将原来的数组对象 copy 到新的数组中,这种方式实现了相似的动态数组的功能,这种实现方式,值得咱们之后拿来借鉴。
最新的 Tomcat6 中 StandardService 也基本没有变化,可是从 Tomcat5 开始 Service、Server 和容器类都继承了 MBeanRegistration 接口,Mbeans 的管理更加合理。
前面说一对情侣由于 Service 而成为一对夫妻,有了可以组成一个家庭的基本条件,可是它们还要有个实体的家,这是它们在社会上生存之本,有了家它们就能够安心的为人民服务了,一块儿为社会创造财富。
Server 要完成的任务很简单,就是要可以提供一个接口让其它程序可以访问到这个 Service 集合、同时要维护它所包含的全部 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。还有其它的一些次要的任务,如您住在这个地方要向当地政府去登记啊、可能还有要配合当地公安机关平常的安全检查什么的。
Server 的类结构图以下:
它的标准实现类 StandardServer 实现了上面这些方法,同时也实现了 Lifecycle、MbeanRegistration 两个接口的全部方法,下面主要看一下 StandardServer 重要的一个方法 addService 的实现:
public void addService(Service service) {复制代码
service.setServer(this);复制代码
synchronized (services) {复制代码
Service results[] = new Service[services.length + 1];复制代码
System.arraycopy(services, 0, results, 0, services.length);复制代码
results[services.length] = service;复制代码
services = results;复制代码
if (initialized) {复制代码
try {复制代码
service.initialize();复制代码
} catch (LifecycleException e) {复制代码
e.printStackTrace(System.err);复制代码
}复制代码
}复制代码
if (started && (service instanceof Lifecycle)) {复制代码
try {复制代码
((Lifecycle) service).start();复制代码
} catch (LifecycleException e) {复制代码
;复制代码
}复制代码
}复制代码
support.firePropertyChange("service", null, service);复制代码
}复制代码
}
复制代码
从上面第一句就知道了 Service 和 Server 是相互关联的,Server 也是和 Service 管理 Connector 同样管理它,也是将 Service 放在一个数组中,后面部分的代码也是管理这个新加进来的 Service 的生命周期。Tomcat6 中也是没有什么变化的。
前面一直在说 Service 和 Server 管理它下面组件的生命周期,那它们是如何管理的呢?
Tomcat 中组件的生命周期是经过 Lifecycle 接口来控制的,组件只要继承这个接口并实现其中的方法就能够统一被拥有它的组件控制了,这样一层一层的直到一个最高级的组件就能够控制 Tomcat 中全部组件的生命周期,这个最高的组件就是 Server,而控制 Server 的是 Startup,也就是您启动和关闭 Tomcat。
下面是 Lifecycle 接口的类结构图:
除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生命周期开始和结束的时候作一些额外的操做。这个机制在其它的框架中也被使用,如在 Spring 中。关于这个设计模式会在后面介绍。
Lifecycle 接口的方法的实现都在其它组件中,就像前面中说的,组件的生命周期由包含它的父组件控制,因此它的 Start 方法天然就是调用它下面的组件的 Start 方法,Stop 方法也是同样。如在 Server 中 Start 方法就会调用 Service 组件的 Start 方法,Server 的 Start 方法代码以下:
public void start() throws LifecycleException {复制代码
if (started) {复制代码
log.debug(sm.getString("standardServer.start.started"));复制代码
return;复制代码
}复制代码
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);复制代码
lifecycle.fireLifecycleEvent(START_EVENT, null);复制代码
started = true;复制代码
synchronized (services) {复制代码
for (int i = 0; i < services.length; i++) {复制代码
if (services[i] instanceof Lifecycle)复制代码
((Lifecycle) services[i]).start();复制代码
}复制代码
}复制代码
lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);复制代码
}
复制代码
监听的代码会包围 Service 组件的启动过程,就是简单的循环启动全部 Service 组件的 Start 方法,可是全部 Service 必需要实现 Lifecycle 接口,这样作会更加灵活。
Server 的 Stop 方法代码以下:
public void stop() throws LifecycleException {复制代码
if (!started)复制代码
return;复制代码
lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);复制代码
lifecycle.fireLifecycleEvent(STOP_EVENT, null);复制代码
started = false;复制代码
for (int i = 0; i < services.length; i++) {复制代码
if (services[i] instanceof Lifecycle)复制代码
((Lifecycle) services[i]).stop();复制代码
}复制代码
lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);复制代码
}
复制代码
它所要作的事情也和 Start 方法差很少。
Connector 组件是 Tomcat 中两个核心组件之一,它的主要任务是负责接收浏览器的发过来的 tcp 链接请求,建立一个 Request 和 Response 对象分别用于和请求端交换数据,而后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程,处理这个请求的线程就是 Container 组件要作的事了。
因为这个过程比较复杂,大致的流程能够用下面的顺序图来解释:
(查看清晰大图)
Tomcat5 中默认的 Connector 是 Coyote,这个 Connector 是能够选择替换的。Connector 最重要的功能就是接收链接请求而后分配线程让 Container 来处理这个请求,因此这必然是多线程的,多线程的处理是 Connector 设计的核心。Tomcat5 将这个过程更加细化,它将 Connector 划分红 Connector、Processor、Protocol, 另外 Coyote 也定义本身的 Request 和 Response 对象。
下面主要看一下 Tomcat 中如何处理多线程的链接请求,先看一下 Connector 的主要类图:
(查看清晰大图)
看一下 HttpConnector 的 Start 方法:
public void start() throws LifecycleException {复制代码
if (started)复制代码
throw new LifecycleException复制代码
(sm.getString("httpConnector.alreadyStarted"));复制代码
threadName = "HttpConnector[" + port + "]";复制代码
lifecycle.fireLifecycleEvent(START_EVENT, null);复制代码
started = true;复制代码
threadStart();复制代码
while (curProcessors < minProcessors) {复制代码
if ((maxProcessors > 0) && (curProcessors >= maxProcessors))复制代码
break;复制代码
HttpProcessor processor = newProcessor();复制代码
recycle(processor);复制代码
}复制代码
}
复制代码
threadStart() 执行就会进入等待请求的状态,直到一个新的请求到来才会激活它继续执行,这个激活是在 HttpProcessor 的 assign 方法中,这个方法是代码以下 :
synchronized void assign(Socket socket) {复制代码
while (available) {复制代码
try {复制代码
wait();复制代码
} catch (InterruptedException e) {复制代码
}复制代码
}复制代码
this.socket = socket;复制代码
available = true;复制代码
notifyAll();复制代码
if ((debug >= 1) && (socket != null))复制代码
log(" An incoming request is being assigned");复制代码
}
复制代码
建立 HttpProcessor 对象是会把 available 设为 false,因此当请求到来时不会进入 while 循环,将请求的 socket 赋给当期处理的 socket,并将 available 设为 true,当 available 设为 true 是 HttpProcessor 的 run 方法将被激活,接下去将会处理此次请求。
Run 方法代码以下:
public void run() {复制代码
while (!stopped) {复制代码
Socket socket = await();复制代码
if (socket == null)复制代码
continue;复制代码
try {复制代码
process(socket);复制代码
} catch (Throwable t) {复制代码
log("process.invoke", t);复制代码
}复制代码
connector.recycle(this);复制代码
}复制代码
synchronized (threadSync) {复制代码
threadSync.notifyAll();复制代码
}复制代码
}
复制代码
解析 socket 的过程在 process 方法中,process 方法的代码片断以下:
private void process(Socket socket) {复制代码
boolean ok = true;复制代码
boolean finishResponse = true;复制代码
SocketInputStream input = null;复制代码
OutputStream output = null;复制代码
try {复制代码
input = new SocketInputStream(socket.getInputStream(),connector.getBufferSize());复制代码
} catch (Exception e) {复制代码
log("process.create", e);复制代码
ok = false;复制代码
}复制代码
keepAlive = true;复制代码
while (!stopped && ok && keepAlive) {复制代码
finishResponse = true;复制代码
try {复制代码
request.setStream(input);复制代码
request.setResponse(response);复制代码
output = socket.getOutputStream();复制代码
response.setStream(output);复制代码
response.setRequest(request);复制代码
((HttpServletResponse) response.getResponse())复制代码
.setHeader("Server", SERVER_INFO);复制代码
} catch (Exception e) {复制代码
log("process.create", e);复制代码
ok = false;复制代码
}复制代码
try {复制代码
if (ok) {复制代码
parseConnection(socket);复制代码
parseRequest(input, output);复制代码
if (!request.getRequest().getProtocol().startsWith("HTTP/0"))复制代码
parseHeaders(input);复制代码
if (http11) {复制代码
ackRequest(output);复制代码
if (connector.isChunkingAllowed())复制代码
response.setAllowChunking(true);复制代码
}复制代码
}复制代码
。。。。。。复制代码
try {复制代码
((HttpServletResponse) response).setHeader复制代码
("Date", FastHttpDateFormat.getCurrentDate());复制代码
if (ok) {复制代码
connector.getContainer().invoke(request, response);复制代码
}复制代码
。。。。。。复制代码
}复制代码
try {复制代码
shutdownInput(input);复制代码
socket.close();复制代码
} catch (IOException e) {复制代码
;复制代码
} catch (Throwable e) {复制代码
log("process.invoke", e);复制代码
}复制代码
socket = null;复制代码
}
复制代码
当 Connector 将 socket 链接封装成 request 和 response 对象后接下来的事情就交给 Container 来处理了。
Container 是容器的父接口,全部子容器都必须实现这个接口,Container 容器的设计用的是典型的责任链的设计模式,它有四个子容器组件构成,分别是:Engine、Host、Context、Wrapper,这四个组件不是平行的,而是父子关系,Engine 包含 Host,Host 包含 Context,Context 包含 Wrapper。一般一个 Servlet class 对应一个 Wrapper,若是有多个 Servlet 就能够定义多个 Wrapper,若是有多个 Wrapper 就要定义一个更高的 Container 了,如 Context,Context 一般就是对应下面这个配置:
<Context复制代码
path="/library"复制代码
docBase="D:\projects\library\deploy\target\library.war"复制代码
reloadable="true"复制代码
/>
复制代码
Context 还能够定义在父容器 Host 中,Host 不是必须的,可是要运行 war 程序,就必需要 Host,由于 war 中必有 web.xml 文件,这个文件的解析就须要 Host 了,若是要有多个 Host 就要定义一个 top 容器 Engine 了。而 Engine 没有父容器了,一个 Engine 表明一个完整的 Servlet 引擎。
那么这些容器是如何协同工做的呢?先看一下它们之间的关系图:
(查看清晰大图)
当 Connector 接受到一个链接请求时,将请求交给 Container,Container 是如何处理这个请求的?这四个组件是怎么分工的,怎么把请求传给特定的子容器的呢?又是如何将最终的请求交给 Servlet 处理。下面是这个过程的时序图:
(查看清晰大图)
这里看到了 Valve 是否是很熟悉,没错 Valve 的设计在其余框架中也有用的,一样 Pipeline 的原理也基本是类似的,它是一个管道,Engine 和 Host 都会执行这个 Pipeline,您能够在这个管道上增长任意的 Valve,Tomcat 会挨个执行这些 Valve,并且四个组件都会有本身的一套 Valve 集合。您怎么才能定义本身的 Valve 呢?在 server.xml 文件中能够添加,如给 Engine 和 Host 增长一个 Valve 以下:
<Engine defaultHost="localhost" name="Catalina">
复制代码
<Valve className="org.apache.catalina.valves.RequestDumperValve"/>复制代码
………复制代码
<Host appBase="webapps" autoDeploy="true" name="localhost" unpackWARs="true"复制代码
xmlNamespaceAware="false" xmlValidation="false">
复制代码
<Valve className="org.apache.catalina.valves.FastCommonAccessLogValve"复制代码
directory="logs" prefix="localhost_access_log." suffix=".txt"复制代码
pattern="common" resolveHosts="false"/> 复制代码
…………复制代码
</Host>复制代码
</Engine>
复制代码
StandardEngineValve 和 StandardHostValve 是 Engine 和 Host 的默认的 Valve,它们是最后一个 Valve 负责将请求传给它们的子容器,以继续往下执行。
前面是 Engine 和 Host 容器的请求过程,下面看 Context 和 Wrapper 容器时如何处理请求的。下面是处理请求的时序图:
(查看清晰大图)
从 Tomcat5 开始,子容器的路由放在了 request 中,request 中保存了当前请求正在处理的 Host、Context 和 wrapper。
Engine 容器比较简单,它只定义了一些基本的关联关系,接口类图以下:
它的标准实现类是 StandardEngine,这个类注意一点就是 Engine 没有父容器了,若是调用 setParent 方法时将会报错。添加子容器也只能是 Host 类型的,代码以下:
public void addChild(Container child) {复制代码
if (!(child instanceof Host))复制代码
throw new IllegalArgumentException复制代码
(sm.getString("standardEngine.notHost"));复制代码
super.addChild(child);复制代码
}
复制代码
public void setParent(Container container) {复制代码
throw new IllegalArgumentException复制代码
(sm.getString("standardEngine.notParent"));复制代码
}
复制代码
它的初始化方法也就是初始化和它相关联的组件,以及一些事件的监听。
Host 是 Engine 的字容器,一个 Host 在 Engine 中表明一个虚拟主机,这个虚拟主机的做用就是运行多个应用,它负责安装和展开这些应用,而且标识这个应用以便可以区分它们。它的子容器一般是 Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。
下面是和 Host 相关的类关联图:
(查看清晰大图)
从上图中能够看出除了全部容器都继承的 ContainerBase 外,StandardHost 还实现了 Deployer 接口,上图清楚的列出了这个接口的主要方法,这些方法都是安装、展开、启动和结束每一个 web application。
Deployer 接口的实现是 StandardHostDeployer,这个类实现了的最要的几个方法,Host 能够调用这些方法完成应用的部署等。
Context 表明 Servlet 的 Context,它具有了 Servlet 运行的基本环境,理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat 能够没有 Engine 和 Host。
Context 最重要的功能就是管理它里面的 Servlet 实例,Servlet 实例在 Context 中是以 Wrapper 出现的,还有一点就是 Context 如何才能找到正确的 Servlet 来执行它呢? Tomcat5 之前是经过一个 Mapper 类来管理的,Tomcat5 之后这个功能被移到了 request 中,在前面的时序图中就能够发现获取子容器都是经过 request 来分配的。
Context 准备 Servlet 的运行环境是在 Start 方法开始的,这个方法的代码片断以下:
synchronized void start() throws LifecycleException {复制代码
………复制代码
if( !initialized ) {复制代码
try {复制代码
init();复制代码
} catch( Exception ex ) {复制代码
throw new LifecycleException("Error initializaing ", ex);复制代码
}复制代码
}
复制代码
………复制代码
lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);复制代码
setAvailable(false);复制代码
setConfigured(false);复制代码
boolean ok = true;复制代码
File configBase = getConfigBase();复制代码
if (configBase != null) {复制代码
if (getConfigFile() == null) {复制代码
File file = new File(configBase, getDefaultConfigFile());复制代码
setConfigFile(file.getPath());复制代码
try {复制代码
File appBaseFile = new File(getAppBase());复制代码
if (!appBaseFile.isAbsolute()) {复制代码
appBaseFile = new File(engineBase(), getAppBase());复制代码
}复制代码
String appBase = appBaseFile.getCanonicalPath();复制代码
String basePath =复制代码
(new File(getBasePath())).getCanonicalPath();复制代码
if (!basePath.startsWith(appBase)) {复制代码
Server server = ServerFactory.getServer();复制代码
((StandardServer) server).storeContext(this);复制代码
}复制代码
} catch (Exception e) {复制代码
log.warn("Error storing config file", e);复制代码
}复制代码
} else {复制代码
try {复制代码
String canConfigFile = (new File(getConfigFile())).getCanonicalPath();复制代码
if (!canConfigFile.startsWith (configBase.getCanonicalPath())) {复制代码
File file = new File(configBase, getDefaultConfigFile());复制代码
if (copy(new File(canConfigFile), file)) {复制代码
setConfigFile(file.getPath());复制代码
}复制代码
}复制代码
} catch (Exception e) {复制代码
log.warn("Error setting config file", e);复制代码
}复制代码
}复制代码
}
复制代码
………复制代码
Container children[] = findChildren();复制代码
for (int i = 0; i < children.length; i++) {复制代码
if (children[i] instanceof Lifecycle)复制代码
((Lifecycle) children[i]).start();复制代码
}
复制代码
if (pipeline instanceof Lifecycle)复制代码
((Lifecycle) pipeline).start();复制代码
………
复制代码
}
复制代码
它主要是设置各类资源属性和管理组件,还有很是重要的就是启动子容器和 Pipeline。
咱们知道 Context 的配置文件中有个 reloadable 属性,以下面配置:
复制代码
<Context复制代码
path="/library"复制代码
docBase="D:\projects\library\deploy\target\library.war"复制代码
reloadable="true"复制代码
/>
复制代码
当这个 reloadable 设为 true 时,war 被修改后 Tomcat 会自动的从新加载这个应用。如何作到这点的呢 ? 这个功能是在 StandardContext 的 backgroundProcess 方法中实现的,这个方法的代码以下:
public void backgroundProcess() {复制代码
if (!started) return;复制代码
count = (count + 1) % managerChecksFrequency;复制代码
if ((getManager() != null) && (count == 0)) {复制代码
try {复制代码
getManager().backgroundProcess();复制代码
} catch ( Exception x ) {复制代码
log.warn("Unable to perform background process on manager",x);复制代码
}复制代码
}复制代码
if (getLoader() != null) {复制代码
if (reloadable && (getLoader().modified())) {复制代码
try {复制代码
Thread.currentThread().setContextClassLoader复制代码
(StandardContext.class.getClassLoader());复制代码
reload();复制代码
} finally {复制代码
if (getLoader() != null) {复制代码
Thread.currentThread().setContextClassLoader复制代码
(getLoader().getClassLoader());复制代码
}复制代码
}复制代码
}复制代码
if (getLoader() instanceof WebappLoader) {复制代码
((WebappLoader) getLoader()).closeJARs(false);复制代码
}复制代码
}复制代码
}
复制代码
它会调用 reload 方法,而 reload 方法会先调用 stop 方法而后再调用 Start 方法,完成 Context 的一次从新加载。能够看出执行 reload 方法的条件是 reloadable 为 true 和应用被修改,那么这个 backgroundProcess 方法是怎么被调用的呢?
这个方法是在 ContainerBase 类中定义的内部类 ContainerBackgroundProcessor 被周期调用的,这个类是运行在一个后台线程中,它会周期的执行 run 方法,它的 run 方法会周期调用全部容器的 backgroundProcess 方法,由于全部容器都会继承 ContainerBase 类,因此全部容器都可以在 backgroundProcess 方法中定义周期执行的事件。
Wrapper 表明一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。Wrapper 是最底层的容器,它没有子容器了,因此调用它的 addChild 将会报错。
Wrapper 的实现类是 StandardWrapper,StandardWrapper 还实现了拥有一个 Servlet 初始化信息的 ServletConfig,由此看出 StandardWrapper 将直接和 Servlet 的各类信息打交道。
下面看一下很是重要的一个方法 loadServlet,代码片断以下:
public synchronized Servlet loadServlet() throws ServletException {复制代码
………复制代码
Servlet servlet;复制代码
try {复制代码
………复制代码
ClassLoader classLoader = loader.getClassLoader();复制代码
………复制代码
Class classClass = null;复制代码
………复制代码
servlet = (Servlet) classClass.newInstance();复制代码
if ((servlet instanceof ContainerServlet) &&复制代码
(isContainerProvidedServlet(actualClass) ||复制代码
((Context)getParent()).getPrivileged() )) {复制代码
((ContainerServlet) servlet).setWrapper(this);复制代码
}复制代码
classLoadTime=(int) (System.currentTimeMillis() -t1);复制代码
try {复制代码
instanceSupport.fireInstanceEvent(InstanceEvent.BEFORE_INIT_EVENT,servlet);复制代码
if( System.getSecurityManager() != null) {复制代码
Class[] classType = new Class[]{ServletConfig.class};复制代码
Object[] args = new Object[]{((ServletConfig)facade)};复制代码
SecurityUtil.doAsPrivilege("init",servlet,classType,args);复制代码
} else {复制代码
servlet.init(facade);复制代码
}复制代码
if ((loadOnStartup >= 0) && (jspFile != null)) {复制代码
………复制代码
if( System.getSecurityManager() != null) {复制代码
Class[] classType = new Class[]{ServletRequest.class,复制代码
ServletResponse.class};复制代码
Object[] args = new Object[]{req, res};复制代码
SecurityUtil.doAsPrivilege("service",servlet,classType,args);复制代码
} else {复制代码
servlet.service(req, res);复制代码
}复制代码
}复制代码
instanceSupport.fireInstanceEvent(InstanceEvent.AFTER_INIT_EVENT,servlet);复制代码
………
复制代码
return servlet;复制代码
}
复制代码
它基本上描述了对 Servlet 的操做,当装载了 Servlet 后就会调用 Servlet 的 init 方法,同时会传一个 StandardWrapperFacade 对象给 Servlet,这个对象包装了 StandardWrapper,ServletConfig 与它们的关系图以下:
Servlet 能够得到的信息都在 StandardWrapperFacade 封装,这些信息又是在 StandardWrapper 对象中拿到的。因此 Servlet 能够经过 ServletConfig 拿到有限的容器的信息。
当 Servlet 被初始化完成后,就等着 StandardWrapperValve 去调用它的 service 方法了,调用 service 方法以前要调用 Servlet 全部的 filter。
Tomcat 还有其它重要的组件,如安全组件 security、logger 日志组件、session、mbeans、naming 等其它组件。这些组件共同为 Connector 和 Container 提供必要的服务。
黄小斜是 985 硕士,阿里巴巴Java工程师,在自学编程、技术求职、Java学习等方面有丰富经验和独到看法,但愿帮助到更多想要从事互联网行业的程序员们。做者专一于 JAVA 后端技术栈,热衷于分享程序员干货、学习经验、求职心得,以及自学编程和Java技术栈的相关干货。黄小斜是一个斜杠青年,坚持学习和写做,相信终身学习的力量,但愿和更多的程序员交朋友,一块儿进步和成长!
原创电子书:关注微信公众号【程序员黄小斜】后回复【原创电子书】便可领取我原创的电子书《菜鸟程序员修炼手册:从技术小白到阿里巴巴Java工程师》这份电子书总结了我2年的Java学习之路,包括学习方法、技术总结、求职经验和面试技巧等内容,已经帮助不少的程序员拿到了心仪的offer!
程序员3T技术学习资源: 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 “资料” 便可免费无套路获取,包括Java、python、C++、大数据、机器学习、前端、移动端等方向的技术资料。
若是你们想要实时关注我更新的文章以及分享的干货的话,能够关注个人微信公众号【Java技术江湖】
这是一位阿里 Java 工程师的技术小站。做者黄小斜,专一 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!
Java工程师必备学习资源:关注公众号后回复”Java“便可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源
黄小斜是 985 硕士,阿里巴巴Java工程师,在自学编程、技术求职、Java学习等方面有丰富经验和独到看法,但愿帮助到更多想要从事互联网行业的程序员们。做者专一于 JAVA 后端技术栈,热衷于分享程序员干货、学习经验、求职心得,以及自学编程和Java技术栈的相关干货。黄小斜是一个斜杠青年,坚持学习和写做,相信终身学习的力量,但愿和更多的程序员交朋友,一块儿进步和成长!
原创电子书:关注微信公众号【程序员黄小斜】后回复【原创电子书】便可领取我原创的电子书《菜鸟程序员修炼手册:从技术小白到阿里巴巴Java工程师》这份电子书总结了我2年的Java学习之路,包括学习方法、技术总结、求职经验和面试技巧等内容,已经帮助不少的程序员拿到了心仪的offer!
程序员3T技术学习资源: 一些程序员学习技术的资源大礼包,关注公众号后,后台回复关键字 “资料” 便可免费无套路获取,包括Java、python、C++、大数据、机器学习、前端、移动端等方向的技术资料。
若是你们想要实时关注我更新的文章以及分享的干货的话,能够关注个人微信公众号【Java技术江湖】
这是一位阿里 Java 工程师的技术小站。做者黄小斜,专一 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点Docker、ELK,同时也分享技术干货和学习经验,致力于Java全栈开发!
Java工程师必备学习资源:关注公众号后回复”Java“便可领取 Java基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送做者原创的Java学习指南、Java程序员面试指南等干货资源