Tomcat简介
Tomcat是由Apache软件基金会属下Jakarta项目开发的Servlet容器,实现了对Servlet和JavaServer Page(JSP)的支持。由于Tomcat本身也内含了HTTP服务器,因此也可以视作单独的Web服务器。
简单来说,Tomcat可以看成是Web服务器+Servlet容器,如下图
Tomcat能够通过Connector组件接收并解析HTTP请求,然后将一个ServletRequest
对象发送给Container处理。容器处理完之后会将响应封装成ServletRespone
返回给Connector,然后Connector再将ServletRespone
解析为HTTP响应文本格式发送给客户端,至此Tomcat就完成了一次网络通信。
Tomcat架构
可以看到Tomcat Server大致可以分为三个组件,Service、Connector、Container
Service
其中一个Tomcat Server可以包含多个Service,比如Tomcat默认的Service服务Catalina。每一个Service都是独立的,他们共享一个JVM以及系统类库,并且一个Service负责维护多个Connector和一个Container。
Connector
Connector用于连接Service和Container,解析客户端的请求并转发到Container,以及转发来自Container的响应。每一种不同的Connector都可以处理不同的请求协议,包括HTTP/1.1、HTTP/2、AJP等等。
Container
Tomcat的Container包含四种子容器:Engine
、Host
、Context
和Wrapper
,在Tomcat源码中我们可以清晰地看到各容器之间的继承关系
其中,一个Container对应一个Engine,一个Engine可以包含多个Host,一个Host可以包含多个Context,Context又包含多个Wrapper,各子容器的功能如下
Engine
可以看成是容器对外提供功能的入口,每个Engine是Host的集合,用于管理各个Host。
Host
可以看成一个虚拟主机
,一个Tomcat可以支持多个虚拟主机。
Context
又叫做上下文容器,我们可以将其看成一个Web应用,每个Host里面可以运行多个Web应用。同一个Host里面不同的Context,其contextPath必须不同,默认Context的contextPath为空格(“”)或斜杠(/)。
Wrapper
是对Servlet的抽象和包装,每个Context可以有多个Wrapper,用于支持不同的Servlet每个Wrapper实例表示一个具体的Servlet定义,Wrapper主要负责管理 Servlet ,包括的 Servlet 的装载、初始化、执行以及资源回收。
可以用一张图来表示请求在Container中的解析过程
以上的映射信息通过通过Mapper组件来关联。Mapper组件保存了Web应用的配置信息,容器组件与访问路径的映射关系等。
JavaWeb三大组件
Servlet
Servlet是用来处理客户端请求的动态资源,当Tomcat接收到来自客户端的请求时,会将其解析成RequestServlet
对象并发送到对应的Servlet上进行处理。
Servlet的生命周期
Servlet的生命周期分为如下五个阶段
- 加载:当Tomcat第一次访问Servlet的时候,Tomcat会负责创建Servlet的实例
- 初始化:当Servlet被实例化后,Tomcat会调用
init()
方法初始化这个对象 - 处理服务:当浏览器访问Servlet的时候,Servlet 会调用
service()
方法处理请求 - 销毁:当Tomcat关闭时或者检测到Servlet要从Tomcat删除的时候会自动调用
destroy()
方法,让该实例释放掉所占的资源。一个Servlet如果长时间不被使用的话,也会被Tomcat自动销毁 - 卸载:当Servlet调用完
destroy()
方法后,等待垃圾回收。如果有需要再次使用这个Servlet,会重新调用init()
方法进行初始化操作
只要访问Servlet,service()
就会被调用。init()
只有第一次访问Servlet的时候才会被调用。 destroy()
只有在Tomcat关闭的时候才会被调用。因此我们主要的业务逻辑代码是写在service()
函数中的。
Servlet使用示例
想要编写一个自己的Servlet,就必须要继承Servlet接口并实现如下五个方法
但每次需要编写Servlet的时候都要重写五个方法,这样未免太繁琐。因此Tomcat已经帮我们封装好了两个类,分别是GenericServlet
类和HttpServlet
类。
GenericServlet抽象类实现了 Servlet 接口,并对 Servlet 接口中除service()方法外的其它四个方法进行了简单实现。如果我们通过继承GenericServlet类创建来Servlet,只需要重写service()
方法即可。但正如其名,GenericServlet抽象类是一个通用的Servlet类,并不是针对某种应用场景而设计的,因此我们在处理HTTP请求的时候需要手动实现对HTTP请求的解析和封装。
HttpServlet是GenericServlet的子类,它在GenericServlet的基础上专门针对HTTP协议进行了处理。其针对每一种HTTP请求都设置了一种处理方法。当我们在使用HttpServlet类的时候,只需要根据HTTP请求类型重写相应的处理方法即可
下面我就以HttpServlet为例,编写一个自己的Servlet
package Servlet;
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("/hello")
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String parameter = req.getParameter("name");
PrintWriter writer = resp.getWriter();
writer.write("Hello "+parameter+"!");
writer.close();
}
}
这里我们使用注解来注册Servlet
...
@WebServlet("/hello")
public class MyServlet extends HttpServlet {
...
}
当然我们也可以通过手动配置web.xml文件来注册Servlet,这种方式虽然繁琐,但是便于管理各Servlet。
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>Servlet.MyServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
配置Tomcat,添加好相应的war包
访问http://localhost:8081/Tomcat_Servlet_war/hello?name=Feng
,结果如下
ServletConfig和ServletContext
ServletConfig
当Servlet容器初始化一个Servlet时,会为这个Servlet创建一个ServletConfig对象,并将 ServletConfig 对象作为参数传递给Servlet。ServletConfig对象封装了Servlet的一些独有参数信息,因此一个Servlet只能对应一个ServletConfig。
下面是ServletConfig的使用示例
package Servlet;
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("/config")
public class Config_Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置响应编码
resp.setContentType("text/html; charset=UTF-8");
//获取ServletConfig
ServletConfig servletConfig = getServletConfig();
//获取Servlet名称
String name = servletConfig.getServletName();
PrintWriter writer = resp.getWriter();
writer.write("Servlet名称为:"+name);
writer.close();
}
}
ServletContext
Servlet 容器启动时,会为每个 Web 应用(webapps 下的每个目录都是一个 Web 应用)创建一个唯一的 ServletContext 对象,该对象一般被称为“Servlet 上下文”。
由于一个Web应用可以包含多个Servlet,因此ServletContext可以看作是一个Web应用中各Servlet的共享资源。不同 Servlet 之间可以通过ServletContext对象实现数据通讯,因此ServletContext对象也被称为Context域对象。
ServletContext 对象的生命周期从 Servlet 容器启动时开始,到容器关闭或应用被卸载时结束。
通过ServletContext可以获取Web应用中一些共享的资源。下面我们使用ServletContext来获取上下文初始化参数
首先在web.xml中配置一个上下文初始化参数
<web-app>
<display-name>Archetype Created Web Application</display-name>
//配置上下文初始化参数name
<context-param>
<param-name>name</param-name>
<param-value>Feng</param-value>
</context-param>
<context-param>
<param-name>age</param-name>
<param-value>18</param-value>
</context-param>
</web-app>
package Servlet;
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;
import java.util.Enumeration;
@WebServlet("/context")
public class Context_Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=UTF-8");
PrintWriter writer = resp.getWriter();
ServletContext servletContext = getServletContext();
Enumeration<String> initParamerNames = servletContext.getInitParameterNames();
while(initParamerNames.hasMoreElements()){
String ParamerName = initParamerNames.nextElement();
String Paramer = servletContext.getInitParameter(ParamerName);
writer.write(ParamerName+"的值为:"+Paramer+"<br/>");
}
writer.close();
}
}
Filter
Filter用于拦截用户请求以及服务端的响应,能够在拦截之后对请求和响应做出相应的修改。Filter不是Servlet,不能直接访问,它能够对于Web应用中的资源(Servlet、JSP、静态页面等)做出拦截,从而实现一些相应的功能。下面是Filter在Server中的调用流程图
这种调用流程类似于设计模式中的“责任链模式”,对于不符合要求的资源进行拦截,而符合要求的资源使用FilterChain.doFilter()
放行。下面是一个简单的Filter Servlet示例,这里我们同样使用注解方式进行配置
Hello_Servlet.java
package Filter;
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("/hello")
public class Hello_Servlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
PrintWriter writer = resp.getWriter();
writer.write("Hello World!");
writer.close();
}
}
Hello_Filter1.java
package Filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;
@WebFilter("/hello")
public class Hello_Filter1 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
response.setContentType("text/html; charset=UTF-8");
PrintWriter writer = response.getWriter();
writer.write("调用Filter1!</br>");
chain.doFilter(request,response);
}
}
Hello_Filter2.java
package Filter;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;
import java.io.PrintWriter;
@WebFilter("/hello")
public class Hello_Filter2 implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
PrintWriter writer = response.getWriter();
writer.write("调用Filter2!</br>");
chain.doFilter(request,response);
}
}
运行结果如下
Filter的生命周期
Filter的生命周期和Servlet一样,Filter的创建和销毁也是由WEB服务器负责。
- 初始化阶段:init(FilterConfig),初始化方法,只会在web应用程序启动时调用一次。
- 拦截和过滤阶段:doFilter(ServletRequest, ServletResponse, FilterChain),完成实际的过滤操作。当客户请求访问与过滤器关联的URL的时候,Servlet过滤器将先执行doFilter方法。FilterChain参数用于访问后续过滤器
- 销毁阶段:destory(),销毁Filter,只会在当web应用移除或服务器停止时才调用一次来卸载Filter对象
FilterChain
我们知道,一个Servlet可以注册多个Filter,Web容器会将注册的多个Filter组合成一个“Filter链”,并按照一定的顺序依次执行各Filter的doFilter()方法。
FilterChain就是这样一个接口,其doFIiter()方法用于将本Filter处理完的Servlet资源交给下一个Filter处理。
Filter执行顺序
Filter的注册方式不同,Filter的执行顺序也有所不同
- 基于注解配置:按照类名的字符串比较规则比较,值小的先执行
- 使用web.xml配置:根据对应的Mapping的顺序组织,谁定义在上边谁就在前
FilterConfig
和Servlet类似,由于Filter也有可能访问Servlet,所以Servlet 规范将代表 ServletContext 对象和 Filter 的配置参数信息都封装到一个称为 FilterConfig 的对象中。
FilterConfig接口则用于定义FilterConfig对象应该对外提供的方法,以便在 Filter的doFilter()方法中可以调用这些方法来获取 ServletContext 对象,以及获取在 web.xml 文件中的一些初始化参数。
Listener
Listener是一个实现了特定接口的Java程序,用于监听一个方法或者属性,当被监听的方法被调用或者属性改变时,就会自动执行某个方法。
相关概念
下面有几个与Listener相关的概念
- 事件:某个方法被调用,或者属性的改变
- 事件源:被监听的对象(如ServletContext、requset、方法等)
- 监听器:用于监听事件源,当发生事件时会触发监听器
监听器的分类
监听器一共有如下8种
事件源 | 监听器 | 描述 |
---|---|---|
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使用示例
这里我们以ServletContextListener为例,创建一个用于监听ServletContext对象的Listener,这里我仍使用注解配置
package Listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class Hello_Listener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext对象创建了!");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("ServletContext对象销毁了!");
}
}
在启动Tomcat服务器的时候,ServletContext对象被创建,同时触发我们设置的ServletContextListener.contextInitialized()
方法
当停止Tomcat服务器时,ServletContext对象被销毁,会触发ServletContextListener.contextDestroyed()
方法
三者的加载顺序
三者的加载顺序为Listener->Filter->Servlet
。
在org.apache.catalina.core.StandardContext
类的startInternal()
方法中,首先调用了listenerStart()
,接着是filterStart()
,最后是loadOnStartup()
。这三处调用触发了Listener、Filter、Servlet的构造加载。
...
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// Check constraints for uncovered HTTP methods
// Needs to be after SCIs and listeners as they may programmatically
// change constraints
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
}
try {
// Start manager
Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
}
// Configure and call application filters
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
}if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
// Check constraints for uncovered HTTP methods
// Needs to be after SCIs and listeners as they may programmatically
// change constraints
if (ok) {
checkConstraintsForUncoveredMethods(findConstraints());
}
try {
// Start manager
Manager manager = getManager();
if (manager instanceof Lifecycle) {
((Lifecycle) manager).start();
}
} catch(Exception e) {
log.error(sm.getString("standardContext.managerFail"), e);
ok = false;
}
// Configure and call application filters
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" servlets
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
// Start ContainerBackgroundProcessor thread
super.threadStart();
}
...