Tomcat基础类介绍
一、Server
- service组件的维护:Server 组件的具体实现类是 StandardServer。Server在内部维护了若干 Service 组件,它是以数组来保存的。还管理Service的生命周期(启、停方法)
- 实现Tomcat的关闭:具体实现在StandardServer的await方法中,在await方法中会启动一个Socket来监听8005(停止端口),并在一个死循环里接收Socket上的连接请求,如果有新的连接到来就建立连接,然后从Socket中读取数据,如果读到的数据是停止命令“SHUTDOWN”,就退出循环,进入stop流程。
public void await() {
if (getPortWithOffset() == -1) {
try {
awaitThread = Thread.currentThread();
while(!stopAwait) {
try {
Thread.sleep( 10000 );
}
}
} finally {
awaitThread = null;
}
return;
}
try {
// 创建socket,默认端口8005
awaitSocket = new ServerSocket(getPortWithOffset(), 1, InetAddress.getByName(address));
}
try {
awaitThread = Thread.currentThread();
// 死循环,等待停止命令
while (!stopAwait) {
ServerSocket serverSocket = awaitSocket;
if (serverSocket == null) {
break;
}
// 等待连接,从连接中获取请求命令
Socket socket = null;
StringBuilder command = new StringBuilder();
try {
InputStream stream;
long acceptStartTime = System.currentTimeMillis();
try {
socket = serverSocket.accept(); socket.setSoTimeout(10 * 1000); // Ten seconds
stream = socket.getInputStream();
} catch (SocketTimeoutException ste) {
// log ……
continue;
} catch (AccessControlException ace) {
// log ……
continue;
} catch (IOException e) {
if (stopAwait) {
// Wait was aborted with socket.close()
break;
}
// log ……
break;
}
// Read a set of characters from the socket
int expected = 1024; // Cut off to avoid DoS attack
while (expected < shutdown.length()) {
if (random == null)
random = new Random();
expected += (random.nextInt() % 1024);
}
while (expected > 0) {
int ch = -1;
try {
ch = stream.read();
} catch (IOException e) { log.warn(sm.getString("standardServer.accept.readError"), e);
ch = -1;
}
// Control character or EOF (-1) terminates loop
if (ch < 32 || ch == 127) {
break;
} command.append((char) ch);
expected--;
}
} finally {
// Close the socket now that we are done with it
try {
if (socket != null) { socket.close();
}
}
// Ignore
}
// 如果连接是SHUTDOWN命令,结束循环
boolean match = command.toString().equals(shutdown);
} finally {
ServerSocket serverSocket = awaitSocket;
awaitThread = null;
awaitSocket = null;
// Close the server socket and return
if (serverSocket != null) {
try { serverSocket.close();
}
// Ignore
}
}
}
二、Service
2.1 简介
- Service需要负责管理Connector和Container的生命周期。具体实现StandardService
- Service内部还有映射器Mapper及其监听器MapperListener:映射器及其监听器用于将用户请求的 URL 定位到一个 Servlet。它的工作原理是: Mapper组件里保存了Web应用的配置信息,其实就是容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及 Wrapper容器里Servlet映射的路径;MapperListener监听器用于支持热部署,当Web应用的部署发生变化时,Mapper中的映射信息也要跟着变化,MapperListener就是一个监听器,它监听容器的变化,并把信息更新到Mapper 中,这是典型的观察者模式。
2.2 Sercice组件的功能
- Service组件最重要的功能对下层容器的管理,比如启动时,要负责启动Connector和Container组件。Service的启动方法在startIntel中,如下:
protected void startInternal() throws LifecycleException {
if(log.isInfoEnabled()) log.info(sm.getString("standardService.start.name", this.name));
// 1. 触发启动监听器
setState(LifecycleState.STARTING);
// 2. 先启动Engine,Engine会启动它子容器
if (engine != null) {
synchronized (engine) {
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
// 3. 再启动Mapper监听器
mapperListener.start();
// 4. 最后启动连接器,连接器会启动它子组件,比如Endpoint
synchronized (connectorsLock) {
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) { connector.start();
}
}
}
}
从启动方法可以看到,Service先启动了Engine组件,再启动Mapper监听器,最后才是启动连接器。这很好理解,因为内层组件启动好了才能对外提供服务,才能启动外层的连接器组件。而 Mapper 也依赖容器组件,容器组件启动好了才能监听它们的变化,因此Mapper和MapperListener在容器组件之后启动。组件停止的顺序跟启动顺序相反。
三、Connector连接器
3.1 简介
- 处理 Socket 连接:将网络字节流转化为Request和Response对象。也就是说连接器对Servlet容器屏蔽了协议及I/O模型等的区别。
- Tomcat设计者将上述功能及实现进一步细分为以下三部分:
- 网络通信—EndPoint
- 应用层协议解析—Processor处理器
- Tomcat Request/Response 与 ServletRequest/ServletResponse 的转化—Adaper适配器
3.2 ProtocolHandler
- 处理网络连接和应用层协议,包含2个重要部件:EndPoint、Processor
- 初始化在Connector构造函数中完成,默认为Http/1.1NIO类型,即HttpNioProtocol
3.2.1 网络通讯与应用层协议简介
- 网络通信的I/O模型是变化的,可能是非阻塞 I/O、异步 I/O 或者 APR。
- 应用层协议也是变化的,可能是 HTTP、HTTPS、AJP。浏览器端发送的请求信息也是变化的。
3.2.2 ProtocolHandler架构
- Tomcat使用抽象基类AbstractProtocol实现ProtocolHandler接口,将一些协议通用功能进行抽取。
- 每一种应用协议对应自己的协议抽象基类,如AbstractHttp11Protocol
- 具体协议的实现扩展协议抽象基类,如Http11NioProtocol
3.2.3 Endpoint容器
3.2.3.1 EndPoint简介
- EndPoint是通信端点:即通信监听的接口,是具体的Socket接收和发送处理器,是对传输层的抽象,因此EndPoint是用来实现TCP/IP协议的。
- EndPoint抽象实现类是AbstractEndpoint,而 具体子类是NioEndpoint和Nio2Endpoint,有两个重要的子组件:Acceptor和SocketProcessor。
3.2.3.2 EndPoint子组件
- Acceptor:用于监听Socket连接请求。
- SocketProcessor:用于处理接收到的 Socket请求,它实现Runnable接口,在run()方法里调用协议处理组件Processor 进行处理。为了提高处理能力,SocketProcessor被提交到线程池来执行。
3.2.4 Processor处理器
- Processor:用来实现应用层协议的(HTTP协议、AJP协议等),负责接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理。
- Processor抽象实现类 AbstractProcessor:对一些协议共有的属性进行封装,没有对方法进行实现。具体的实现有AJPProcessor、HTTP11Processor等,这些具体实现类实现了特定协议的解析方法和请求处理方式。
3.2.5 代码处理流程
上图的流程代码逻辑大致如下:
NioEndPoint.startIntel ->
Poller -> run()
-> processKey(SelectionKey sk, NioSocketWrapper attachment)
-> processSocket(attachment, SocketEvent.OPEN_READ, true)
-> createSocketProcessor(socketWrapper, event)
-> SocketProcessor run
-> getHandler().process
-> processor.process(wrapper, status)
-> service(socketWrapper)
-> getAdapter().service(request, response)
3.3 Adapter适配器
Tomcat设计者的解决方案是引入CoyoteAdapter,这是适配器模式的经典运用,连接器调用CoyoteAdapter 的 Sevice方法,传入的是Tomcat Request对象,CoyoteAdapter负责将Tomcat Request转成ServletRequest,再调用Engine容器的pipline方法,实现对servlet的调用。
四 Container容器
- 定义容器基本功能:子级child、父级Parent容器和Cluster集群、Pipeline管道、Threads线程池、Container容器、ContainerListener容器监听等CRUD功能
- 抽象基类为ContainerBase
- Toncat设计了4中容器,分别为Engine、Host、Context、Wrapper
4.1 ContainerBase简介
- 抽象类中相关功能得到实现。
protected final HashMap<String,Container> children = new HashMap<>();
- 使用HashMap存储保存子容器。
protected synchronized void startInternal(){
Container children[]=findChildren();
List<Future<Void>> results=new ArrayList<>();
for(Container child:children){
results.add(startStopExecutor.submit(new StartChild(child)));
}
}
- ChntainerBase实现了子容器的CRUD,以及子容器的启动和停止提供了默认实现,使用专门的线程池来启动子容器。
4.2 Engine容器
- 作用:把请求转发给某一个Host子容器来处理
继承了ContainerBase基类,具体实现类是StandardEngine。
4.2.1 StandardEngineValue
final class StandardEngineValve extends ValveBase {
public StandardEngineValve() {
super(true);
}
@Override
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// 拿到请求中的host容器
Host host = request.getHost();
if (host == null) {
// is defined. This is handled by the CoyoteAdapter.
return;
}
if (request.isAsyncSupported()) {
request.setAsyncSupported(host.getPipeline().isAsyncSupported());
}
// 调用host容器pipline中第一个valve,将请求提交给host处理
host.getPipeline().getFirst().invoke(request, response);
}
}
4.4 Host容器
- Host是跟虚拟域名绑定的,同时负责维护其子容器Context的生命周期。Host容器在Tomcat中的实现类是StandardHost,它也继承了ContainerBase基类同时实现了Host接口。
4.4 Context容器
- Context对应一个web应用,是用于存储管理Servlet的容器,在Tomcat中的默认实现是StandardContext。
- StandardContext也继承了ContainerBase类,子容器也是通过ContainerBase类的成员变量children维护的,但是StandardContext中重写了startInternal方法,在该方法中,完成WebAppClassLoader的初始化,子容器Wrapper的初始化及启动,web.xml配置文件解析,Context监听器维护与触发等。
4.4.1 基础属性
比如我们在配置文件中配置的Context的path属性,用于指定Host下(域名下)web应用的访问路径:
<Host appBase="webapps" autoDeploy="true" name="www.lidol.top" unpackWARs="true">
<!--访问路径:www.lidol.top/demo1-->
<Context docBase="/Users/zhuoli/Documents/demo/demo1" path="/demo1" reloadable="true"/>
<!--访问路径:www.lidol.top/demo2-->
<Context docBase="/Users/zhuoli/Documents/demo/demo2" path="/demo2" reloadable="true"/>
</Host>
- 在StandardContext中,我们发现存在这样两个成员变量:
/**
* The document root for this web application.
*/
private String docBase = null;
private String path = null;
- Context是所有的Servlet的父容器,我们知道Servlet中一个重要的组件就是ServletContext(Servlet上下文),该组件就是在StandardContext中初始化的:
/**
* The ServletContext implementation associated with this Context.
*/
protected ApplicationContext context = null;
@Override
public ServletContext getServletContext() {
if (context == null) {
context = new ApplicationContext(this);
if (altDDName != null) context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
}
return (context.getFacade());
}
4.4.2 Context和Java Web三大组件
- Servlet其实就是Context的子容器Wrapper,用继承的children成员变量来维护。
- Filter是一种Servlet的扩展机制,在web.xml中配置,在Context容器中进行管理的:
private Map<String, FilterDef> filterDefs = new HashMap<>();
需要注意的是StandardContext中只是将我们配置的Filter解析成FilterDef,并不是每个请求都需要将所有的Filter走一遍。在web.xml中,我们通过来配置servlet生效的Filter集合,Tomcat容器会为每个请求生成一个FilterChain,用于表示该请求相关联的Filter,在每个Filter执行结束后,才会调用Servlet的service方法。
-
Listener跟Filter一样,也是一种扩展机制,你可以监听容器内部发生的事件,主要有两类事件:
- 生命状态的变化,比如Context容器启动和停止、Session的创建和销毁
- 属性的变化,比如Context容器某个属性值变了、Session的某个属性值变了以及新的请求来了等,在web.xml配置了监听器,在监听器里实现我们的业务逻辑。对于Tomcat来说,它需要读取配置文件,拿到监听器类的名字,实例化这些类,并且在合适的时机调用这些监听器的方法。
-
Context容器将两类事件分开来管理,分别用不同的集合来存放不同类型事件的监听器:
// 监听属性值变化的监听器
private List<Object> applicationEventListenersList = new CopyOnWriteArrayList<>();
// 监听生命事件的监听器
private Object applicationLifecycleListenersObjects[] = new Object[0];
剩下的事情就是触发监听器了,比如在Context容器的启动方法里,就触发了所有的ServletContextListener:
for (int i = 0; i < instances.length; i++) {
if (!(instances[i] instanceof ServletContextListener))
continue;
ServletContextListener listener =
(ServletContextListener) instances[i];
try { fireContainerEvent("beforeContextInitialized", listener);
if (noPluggabilityListeners.contains(listener)) { listener.contextInitialized(tldEvent);
} else { listener.contextInitialized(event);
} fireContainerEvent("afterContextInitialized", listener);
} catch (Throwable t) { ExceptionUtils.handleThrowable(t); fireContainerEvent("afterContextInitialized", listener);
getLogger().error (sm.getString("standardContext.listenerStart", instances[i].getClass().getName()), t);
ok = false;
}
}
4.5 Wrapper容器
- 用来管理Servlet,每一个Servlet都对应一个Wrapper,在Tomcat中的实现是StandardWrapper。所以StandardWrapper中有一个比较重要的成员变量:
protected volatile Servlet instance = null;
- StandardWrapper:提供了allocate()方法,用于实例化并初始化Servlet。而allocate方法最终调用了StandardWrapper中的loadServlet()方法来实例化并初始化Servlet。
public synchronized Servlet loadServlet() throws ServletException {
// Nothing to do if we already have an instance or an instance pool
if (!singleThreadModel && (instance != null))
return instance;
// 1. 创建Servlet实例
servlet = (Servlet) instanceManager.newInstance(servletClass);
// 2. 调用Servlet的init方法,初始化Servlet
initServlet(servlet);
return servlet;
}
- StandardWrapperValve:完成Servlet的初始化和实例化。如下:
public final void invoke(Request request, Response response)
throws IOException, ServletException {
// 1. 实例化Servlet
servlet = wrapper.allocate();
// 2. 为当前请求创建一个FilterChain
ApplicationFilterChain filterChain = ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
// 3. 调用这个FilterChain的doFilter方法,FilterChain中最后一个Filter执行结束后,会调用Servlet的service方法 filterChain.doFilter(request.getRequest(), response.getResponse());
}
到这里,我们也可以解答一个疑问,为什么Servlet会在第一次被调用时实例化。因为在上述loadServlet方法中,只有Wrapper容器中的Servlet实例为null时(第一次调用时),才会创建Servlet实例。