Tomcat架构学习
文章目录
Tomcat简介
Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器,实现了对Servlet和JavaServer Page(JSP)的支持。由于Tomcat本身也内含了HTTP服务器,因此也可以视作单独的Web服务器。
Tomcat架构
Tomcat架构采用类似于俄罗斯套娃的设计方式。换句话说就是一个容器包含一个容器,而这个被包含的容器反过来再包含别的实体。Tomcat将Engine,Host,Context,Wrapper统一抽象成容器(Container)。一个抽象的容器模块可以包含各种服务。
容器组件
- Server组件:Server是最顶级的组件,一个Tomcat对应一个Server,Server代表tomcat的运行实例,其中包含Listener组件用以监听生命周期中的各种事件;包含Global Naming Resources组件用以集成JNDI;包含Service组件用以提供服务。
- Service组件:Service是服务的抽象,代表请求从接受到处理的所有组件的集合;Server组件可以包含多个Service组件;包含Connector组件用以接收客户端的信息;包含Engine组件用以处理请求;包含Executor用以提供线程池执行任务。一个Service包含多个connector(接受请求的协议),和一个container(容器),多个connector共享一个container容器。
- Connector组件:负责接受客户端连接并接受信息报文,解析不同协议及io方式。包含Mapper组件对请求地址进行路由;包含CoyoteAdaptor组件用以将Connector组件和Engine等容器组件适配;
- Executor组件:线程池
- Engine组件:Servlet引擎,container容器中顶层的容器对象,用来管理多克虚拟站点,包含Listener组件用以在生命周期中对Engine相关的事件进行监听;包含AccessLog组件以记录访问日志;包含Cluster组件以提供集群功能,将需要共享的数据同步到集群中的其他Tomcat实例中;包含Pipeline组件用以处理请求;包含Realm组件用以提供安全权限功能。一个Service最多只有一个Engine,但一个engine可以包含多个host主机。
- Host组件:代表一个虚拟主机,或者说一个站点,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可包含多个Context。Host包含Listener组件用以在生命周期中对Host相关的事件进行监听;包含AccessLog组件以记录访问日志;包含Cluster组件以提供集群功能,将需要共享的数据同步到集群中的其他Tomcat实例中;包含Pipeline组件用以处理请求;包含Realm组件用以提供安全权限功能。一个host对应一个网络域名,一个host包含多个context。
- Context组件:Web应用抽象,Web应用部署tomcat后会转换为Context对象;包含了各种静态资源、若干Servlet(Wrapper容器)以及各种其他动态资源。contest表示一个Web应用。
- Mapper组件用以作为路由映射Servlet。
- Wrapper组件:对应的是Servlet;包含Web应用开发常用的Servlet组件;包含ServletPool组件用以存放Servlet对象。web应用中的servlet会被包装成一个wrapper。
可以用一张图来表示请求在Container中的解析过程:
Tomcat将请求路由到具体的Serclet的过程是交给Mapper组件来完成的。Mapper组件里面会保存着Tomcat应用的各种配置信息,例如Host域名、Context路径等。
1.根据请求协议各端口号路由到相应的Service,找到Service对应唯一的Engine容器。
2.更与域名地址找到相应的Host容器。
3.根据URL路径匹配某一个Context组件。
4.最后根据URL匹配到某一个Wrapper,也就是Servlet。
JavaWeb三大组件
Servlet组件
Servlet是用来处理客户端请求的动态资源,当Tomcat接收到来自客户端的请求时,会将其解析成RequestServlet对象并发送到对应的Servlet上处理。
Servlet组件有javax.servlet.Servlet
类实现,由init
、getServletConfig
、service
、getServletInfo
,destory
方法构成。
Servlet的生命周期
Servlet的生命周期分如下五个阶段
- 加载:当Tomcat第一次访问Servlet时,Tomcat会负责创建Servlet的实例
- 初始化:Tomcat创建Servlet后,调用
init()
方法初始化对象 - 服务:当浏览器访问Servlet时,Servlet会调用
service()
处理请求 - 销毁:当Tomcat关闭时或检测到Servlet从Tomcat删除时会调用
destory()
方法。 - 卸载:当Servlet调用完
destroy()
方法后,等待垃圾回收。如果有需要再次使用这个Servlet,会重新调用init()
方法进行初始化操作
为了简化操作,Tomcat帮我们封装了javax.servlet.http.HttpServlet
类,其中包括doGet
、doPost
等方法。对于简单的HTTP请求,我们只需要重载响应的方法即可,例如get方法重载doGet()
。
Servlet样例
// src/main/java/example/demo/Login.java
package example.demo;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/password")
public class Login extends HttpServlet {
private String message;
public void init() {
message = "欢迎登陆";
}
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// String getMethod():获取请求方式:GET
resp.setContentType("text/html;charset=UTF-8");
PrintWriter printWriter = resp.getWriter();
printWriter.write("<pre><h3>");
String method = req.getMethod();
printWriter.write("method:" + method);
printWriter.write("\n");
// String getContextPath():获取虚拟目录(项目访问路径):/webapp
String contextPath = req.getContextPath();
printWriter.write("contextPath:" + contextPath);
printWriter.write("\n");
// StringBuffer
// getRequestURL():获取URL(统一资源定位符):http://localhost:8080/webapp/Login
StringBuffer url = req.getRequestURL();
printWriter.write("url:" + url.toString());
printWriter.write("\n");
// String getRequestURI():获取URI(统一资源标识符): /webapp/Login
String uri = req.getRequestURI();
printWriter.write("uri:" + uri);
printWriter.write("\n");
// String getQueryString():获取请求参数(GET方式): username=admin
String queryString = req.getQueryString();
printWriter.write("queryString:" + queryString);
printWriter.write("\n");
printWriter.write(message);
printWriter.write("</h3></pre>");
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("Post方法" + message);
}
}
上述代码在/password
路径中创建了名为Login
的Servlet,项目名称为webapp,访问http://localhost:8080/webapp/password
,成功调用doGet
方法。
值得注意的是,在代码中我们使用了@WebServlet("/password")
注解进行Servlet注册,对Servlet进行路由绑定,任何访问password
的请求都将传递给Login
类进行处理。当然@WebServlet
是servlet3.0以后支持的方式,如果servlet低于3.0,我们仍然需要使用web.html进行配置,为了与上面区分,下面的web.html
将/Login
的路径绑定至Login类上。
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<!--这里是给我们的servlet取个别名,一般类名就行-->
<servlet-name>Login</servlet-name>
<!--这里是全类名,也就是让系统知道你上面那个名字是取给谁的-->
<servlet-class>example.demo.Login</servlet-class>
</servlet>
<!--给上面的东西添加映射,可以理解为,上面创建了一个门,这里写门通向哪里-->
<servlet-mapping>
<!--和上面的大门名对应-->
<servlet-name>Login</servlet-name>
<!--大门名的接口, 后面运行后的地址加上/ser01,就可以访问到我们的servlet类了-->
<url-pattern>/Login</url-pattern>
</servlet-mapping>
</web-app>
tomcaturl访问http://localhost:8080/webapp/password
过程如下图所示:
ServletConfig
ServletConfig就是Servlet的配置参数对象,每个Servlet都有一个专属的ServletConfig,负责在初始化时将当前Servlet的配置传递给Servlet,简单来说,ServletConfig就是当前Servlet在web.xml中的配置信息,可以使用getServletConfig()
获得当前Servlet的ServletConfig对象。
主要方法
getServletName() 获取Servlet的别名servlet-name的值
getInitParameter("") 获取当前Servlet的初始化值init-param
getServletContext() 获取ServletContext对象
测试样例
// src/main/java/example/demo/Login.java
package example.demo;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/password")
public class Login extends HttpServlet {
private String message;
public void init() {
message = "欢迎登陆";
}
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter printWriter = resp.getWriter();
printWriter.write("<pre><h3>");
//获取ServletConfig
ServletConfig servletConfig = getServletConfig();
String name = servletConfig.getServletName();
printWriter.write("Servlet的名称是"+name);
printWriter.write("\n");
String arg1 = servletConfig.getInitParameter("arg1");
printWriter.write("arg1:的值为"+arg1);
printWriter.write("</h3></pre>");
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("Post方法" + message);
}
}
<!--/src/main/webapp/WEB-INF/web.xml-->
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>Login</servlet-name>
<servlet-class>example.demo.Login</servlet-class>
<!-- 配置当前Servlet上下文参数-->
<init-param>
<param-name>arg1</param-name>
<param-value>Servlet_login</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Login</servlet-name>
<url-pattern>/Login</url-pattern>
</servlet-mapping>
<!-- 配置ServletContext上下文参数-->
<context-param>
<param-name>arg2</param-name>
<param-value>Servlet-Global</param-value>
</context-param>
</web-app>
ServletContext
ServletContext是一个全局的存储信息的空间,可以理解为全局变量,一个Web工程内只有一个ServletContext对象实例,是一个域对象。任何ServletConfig都可以通过servletConfig.getServletContext()
获得ServletContext对象。同样的ServletContext对象也可以在web.xml设置上下文初始化参数。
主要方法
getContextPath() 获得当前工程路径,/工程路径
getRealPath("/") 获得工程部署后在服务器硬盘上的绝对路径
getInitParameter("") 获得web.xml配置的全局上下文参数context-param
测试样例
// src/main/java/example/demo/Login.java
package example.demo;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebServlet("/password")
public class Login extends HttpServlet {
private String message;
public void init() {
message = "欢迎登陆";
}
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter printWriter = resp.getWriter();
printWriter.write("<pre><h3>");
//获取ServletConfig
ServletConfig servletConfig = getServletConfig();
String name = servletConfig.getServletName();
printWriter.write("Servlet的名称是"+name);
printWriter.write("\n");
String arg1 = servletConfig.getInitParameter("arg1");
printWriter.write("arg1:的值为"+arg1);
printWriter.write("\n");
// 获得ServletContext
ServletContext servletContext = servletConfig.getServletContext();
String arg2 = servletContext.getInitParameter("arg2");
printWriter.write("arg2:的值为"+arg2);
printWriter.write("</h3></pre>");
}
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
resp.getWriter().write("Post方法" + message);
}
}
<!--/src/main/webapp/WEB-INF/web.xml-->
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<servlet-name>Login</servlet-name>
<servlet-class>example.demo.Login</servlet-class>
<!-- 配置当前Servlet上下文参数-->
<init-param>
<param-name>arg1</param-name>
<param-value>Servlet_login</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>Login</servlet-name>
<url-pattern>/Login</url-pattern>
</servlet-mapping>
<!-- 配置ServletContext上下文参数-->
<context-param>
<param-name>arg2</param-name>
<param-value>ServletContext</param-value>
</context-param>
</web-app>
Filter组件
Filter组件是运行在服务端的组件,主要功能是对客户端访问资源的过滤,不符合条件的拦截,符合要求的资源使用FilterChain.doFilter()
放行,并且可以对访问的目标资源访问前后进行逻辑处理。
每个过滤器在客户端向服务器发送请求时进行一次过滤,在服务器向客户端响应时进行一次过滤。一个Servlet可以设置多个Filter,Filter之间按照一定顺序构成调用链,优先级高的先进行调用,每个Filter使用FilterChain接口将处理后的资源传递,其doFIiter()方法用于将本Filter处理完的Servlet资源交给下一个Filter处理,最终交付给Servlet进行响应,Filter调用过程如下图所示:
Filter生命周期
Filter生命周期与Servlet一样,Filter的创建和销毁也是由WEB服务器负责。
- 初始化阶段:init(FilterConfig),只会在web应用启动时调用
- 拦截和过滤阶段:doFilter(ServletRequest, ServletResponse, FilterChain),完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器。
- 销毁阶段:destory(),销毁Filter,只会在当web应用移除或服务器停止时才调用一次来卸载Filter对象
Filter样例
Filter_memshell.java
// src/main/java/Filter_memshell.java
package example.demo;
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class Filter_memshell implements Filter {
private String message;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
message = "调用 Filter_mem";
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter printWriter = response.getWriter();
printWriter.write("<pre><h3>");
printWriter.write(message);
printWriter.write("\n");
// 放行请求
chain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
Filter_2.java
// src/main/java/Filter_2.java
package example.demo;
import javax.servlet.*;
import java.io.IOException;
import java.io.PrintWriter;
public class Filter_2 implements Filter {
private String message;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
message = "调用 Filter_2";
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter printWriter = response.getWriter();
// printWriter.write("<pre><h3>");
printWriter.write(message);
// 放行请求
chain.doFilter(request,response);
}
@Override
public void destroy() {
}
}
web.xml
我们在配置url-pattern配置时,有三种写法:
- 精确匹配:/Login
- 目录匹配:/aaa/bbb/*
- 扩展名匹配:*.abc,*.jsp
值得注意的是,url-pattern也可以使用servlet-name代替,此时Filter会与Servlet绑定。
<!--/src/main/webapp/WEB-INF/web.xml-->
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<servlet>
<!--这里是给我们的servlet取个别名,一般类名就行-->a
<servlet-name>Login</servlet-name>
<!--这里是全类名,也就是让系统知道你上面那个名字是取给谁的-->
<servlet-class>example.demo.Login</servlet-class>
<init-param>
<param-name>arg1</param-name>
<param-value>Servlet_login</param-value>
</init-param>
</servlet>
<!--给上面的东西添加映射,可以理解为,上面创建了一个门,这里写门通向哪里-->
<servlet-mapping>
<!--和上面的大门名对应-->
<servlet-name>Login</servlet-name>
<!--大门名的接口, 后面运行后的地址加上/ser01,就可以访问到我们的servlet类了-->
<url-pattern>/Login</url-pattern>
</servlet-mapping>
<!-- 配置ServletContext上下文参数-->
<context-param>
<param-name>arg2</param-name>
<param-value>ServletContext</param-value>
</context-param>
<filter>
<filter-name>Filter1</filter-name>
<filter-class>example.demo.Filter_memshell</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter1</filter-name>
<!--对该web应用下的所有资源进行过滤-->
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>Filter2</filter-name>
<filter-class>example.demo.Filter_2</filter-class>
</filter>
<filter-mapping>
<filter-name>Filter2</filter-name>
<url-pattern>/*</url-pattern>
<!--<servlet-name>Login</servlet-name>-->
</filter-mapping>
</web-app>
当然,对于注册Filter,我们也可以使用@WebFilter()
注解实现,上述web.xml可以简写为:
@WebFilter(filterName = "Filter_memshell",
urlPatterns = "/Login"
)
Filter执行顺序
filter执行顺序是由filter注册时优先级决定,与Servlert类似,Filter也存在两种注册方式。
- 基于注解配置:按照类名的字符串比较规则比较,值小的先执行
- 基于web.xml配置:根据对应的Mapping的顺序组织,定义在上面先执行。
FilterConfig
与Servlet类似,Filte同样存在FilterConfig存储基本配置信息,不同的是Filer只能在init
方法参数中获取。
主要方法
getFilterName() 获得Filter的名字filter-name的内容
getServletContext() 获得ServletContext对象
getInitParameter("") 获取在Filter中配置的init-param初始化参数
getInitParameterNames() 获取在Filter中配置的初始化参数的名称
Listener组件
Listener是Listener监听器,用于监听一个方法或属性,当被监听的方法被调用或者属性改变时,通过回调函数,反馈给客户(程序)。
监听器的分类
事件源 | 监听器 | 描述 |
---|---|---|
ServletContext | ServletContextListener | 用于监听 ServletContext 对象的创建与销毁过程 |
HttpSession | HttpSessionListener | 用于监听 HttpSession 对象的创建和销毁过程 |
ServletRequest | ServletRequestListener | 用于监听 ServletRequest 对象的创建和销毁过程 |
ServletContext | ServletContextAttributeListener | 用于监听 ServletContext 对象的属性新增、移除和替换 |
HttpSession | HttpSessionAttributeListener | 用于监听 HttpSession 对象的属性新增、移除和替换 |
ServletRequest | ServletRequestAttributeListener | 用于监听 HttpServletRequest 对象的属性新增、移除和替换 |
HttpSession | HttpSessionBindingListener | 用于监听 JavaBean 对象绑定到 HttpSession 对象和从 HttpSession 对象解绑的事件 |
HttpSession | HttpSessionActivationListener | 用于监听 HttpSession 中对象活化和钝化的过程 |
根据监听对象的不同可以大致划分为三类:
- ServletContextListener
- HttpSessionListener
- ServletRequestListener
ServletContextListener使用示例
// src/main/java/Listener_memshell.java
package example.demo;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebListener
public class Listener_memshell implements ServletContextListener, HttpSessionListener, HttpSessionAttributeListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
/* This method is called when the servlet context is initialized(when the Web application is deployed). */
System.out.println("ServletContext对象创建了!");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
/* This method is called when the servlet Context is undeployed or Application Server shuts down. */
System.out.println("ServletContext对象销毁了!");
}
}
当启动Tomcat时,Servlet被创建;Tomcat结束时,Servlet被销毁。
三个组件启动顺序
tomcat启动时,三大组件的启动顺序为Listener->Filter->Servlet
,在org.apache.catalina.core.StandardContext
类的startInternal()
方法中,依次调用了listenerStart()
,filterStart()
,loadOnStartUp()
分别对应Listener,Filter,Servlet。