Tomcat是Servlet最受欢迎的容器之一,他们之间彼此依存,为了解耦,通过标准化接口相互协作。
Tomcat的核心组件是Connector和Container.其中,Connector组件是可以被替换的,这样就给设计者提供了比较灵活的设计模式,一个Container可以对应多个Connector,它们一起组成了一个Servcie,就可以对外提供服务了。Servcie还要一个生存环境,那就是Server。整个Tomcat的生命周期是由Server控制的。
Tomcat容器分为4个等级:
Container 容器 ==> Engine容器 ==> Host ==> Context
以Servcie作为“婚姻”
Container:小仙女 Connector:男生
他们是一对快乐的情侣,Connector作为男生,平时主要负责对外交流,Container小仙女主要负责内部的事物,他们两彼此精诚合作,生活也是井井有条。
他们两有了Service这个结婚证呢,就是受外界承认的小夫妻了,无论是在法律上还是生活形式上,他们都是一体的了。
为了让生活更加丰富多彩,他们更加细致的规划了自己的生活,就是StandardService,同时实现了Service接口,还实现了Lifecycle接口。
setContainer方法的源码如下:
@Override
public void setContainer(Engine engine) {
Engine oldEngine = this.engine;
if (oldEngine != null) {
oldEngine.setService(null);
}
this.engine = engine;
if (this.engine != null) {
this.engine.setService(this);
}
if (getState().isAvailable()) {
if (this.engine != null) {
try {
this.engine.start();
} catch (LifecycleException e) {
log.warn(sm.getString("standardService.engine.startFailed"), e);
}
}
// Restart MapperListener to pick up new engine.
try {
mapperListener.stop();
} catch (LifecycleException e) {
log.warn(sm.getString("standardService.mapperListener.stopFailed"), e);
}
try {
mapperListener.start();
} catch (LifecycleException e) {
log.warn(sm.getString("standardService.mapperListener.startFailed"), e);
}
if (oldEngine != null) {
try {
oldEngine.stop();
} catch (LifecycleException e) {
log.warn(sm.getString("standardService.engine.stopFailed"), e);
}
}
}
// Report this property change to interested listeners
support.firePropertyChange("container", oldEngine, this.engine);
}
这段代码逻辑很简单,首先判断当前这个Service有没有关联Container,如果已经有关联,就去掉——>oldEngine.setService(null);如果这个Container已经启动了,则结束它的生命周期——>oldEngine.stop();然后再启动新的关联、初始化并开始这个新的Container得生命周期。
addContainer方法的源码如下:
@Override
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);
}
}
以Server为“居”
既然Container小仙女和Connector有了合法的夫妻关系,那么他们也需要一个基本的条件去维系这段“婚姻”,他们需要一个实体的家,Server.
Server要完成的任务很简答,就是提高一个接口让其它程序可以访问这个Service集合,同时也要维护他所包含的Service的生命周期。
StandardServer是他的标准实现类,同时也继承了LifecycleMBeanBase。
addService方法源码如下:
@Override
public void addService(Service service) {
service.setServer(this); //由此可以看出Service和Server是相互关联的
synchronized (servicesLock) {
Service results[] = new Service[services.length + 1];
System.arraycopy(services, 0, results, 0, services.length);
results[services.length] = service;
services = results;
if (getState().isAvailable()) {
try {
service.start();
} catch (LifecycleException e) {
// Ignore
}
}
// Report this property change to interested listeners
support.firePropertyChange("service", null, service);
}
}
组件的生命线“Lifecycle”
Tomcat中组件的生命周期都是由Lifecycle接口来控制的,组件只要继承这个接口并实现其中的方法就可以统一被拥有他的组件控制了。这样一层一层到最高级的组件就可以控制Tomcat中所有组件的生命周期了,这个最高级的组件就是Server,控制Server的是Startup,也就是启动和关闭Tomcat.
除了控制生命周期的start和stop方法外,还有一个监听机制,在生命周期开始和结束时做一些额外的操作。
Lifecycle接口实现都在其它组件中。
负责外部交流的Connector
接受浏览器发过来的TCP请求,创建一个Request和Response对象分别用于请求端交换数据,然后将产生一个线程来处理这个请求并把产生的Request和Response对象传给处理这个请求的线程,之后处理这个线程就是Container的事情了。
Servlet容器Container
Container是容器的父接口,所有容器必须实现这个接口,设计是典型的责任链的设计模式,他有4个容器组成,分别是Engine、Context和Wrapper,这四个组件不是平行的,而是父子关系。通常一个Servlet class对应一个Wrapper.
Engine容器
标准实现类时StandardEngine,Engine没有父容器了,调用setParent就会出错
@Override
public void setParent(Container container) {
throw new IllegalArgumentException
(sm.getString("standardEngine.notParent"));
}
添加子容器也只能是Host类型
@Override
public void addChild(Container child) {
if (!(child instanceof Host))
throw new IllegalArgumentException
(sm.getString("standardEngine.notHost"));
super.addChild(child);
}
Host容器
Host是Engine的子容器,一个Host在Engine中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,而且进行标识以便于区分。他的子容器是Context,它除了关联子容器外,还有就是保存一个主机应该有的信息。
Context容器
他具备Servlet运行的基本环境,是在管理Servlet实例,Servlet实例在Context容器中是以Wrapper出现的。
Wrapper容器
Wrapper代表一个Servlet,包括Servlet的装载、初始化、执行和资源回收。是最底层的,没有再底层的容器了。
loadServlet是个很重要的方法,代码片段如下:
public synchronized Servlet loadServlet() throws ServletException {
······
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", servletClass), e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.instantiate", servletClass), e);
}
if (multipartConfigElement == null) {
MultipartConfig annotation =
servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement =
new MultipartConfigElement(annotation);
}
}
processServletSecurityAnnotation(servlet.getClass());
// Special handling for ContainerServlet instances
if ((servlet instanceof ContainerServlet) &&
(isContainerProvidedServlet(servletClass) ||
((Context) getParent()).getPrivileged() )) {
((ContainerServlet) servlet).setWrapper(this);
}
classLoadTime=(int) (System.currentTimeMillis() -t1);
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
instancePool = new Stack<>();
}
singleThreadModel = true;
}
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
Servlet容器的工作原理
由上图可知,Servlet规范就是基于上面的几个类运转的,与Servlet主动关联的是三个类:ServletConfig、ServletRequest、ServletResponse,这3个类都是通过容器传递给Servlet的。
用户从浏览器向服务器发起一个请求通常会包含如下信息:
http://hostname:port/contextpath/servletpath
hostname:port用来与服务器建立TCP连接,而后面的URL才用来选择服务器的哪个子容器服务用户的请求。
这种映射关系专门由一个类来完成,这个类就是org.apache.servlet.util.http.mapper,这个类保存了Tomcat的Container所有子容器的信息。
- 创建一个Context容器,很重要的一个配置是ContextConfig,负责整个Web应用的配置文件解析工作。
- Context init(一个Context对应一个web),Context容器的Listener将被调用,ContextConfig调用了LifecycleListener接口。
- Context执行startInternal方法
- Web应用的初始化工作,ContextConfig中的configureStart方法实现。
- 创建Servlet对象
- 初始化Servlet
Servlet中的url-pattern
匹配顺序:精确匹配 路径匹配 后缀匹配
程序媛小白一枚,如有错误,烦请批评指正!(#.#)