# 前言
本文适合有过Servlet编程经验的半小白观看~~
# 一、Servlet简介
Servlet 全称是Java Server Applet。从命名来看就知道名字起名来自于Java Applet(曾经也不没有风靡一时的java 小应用程序,主要应用于嵌入HTML),那么Servlet 顾名思义就是应用于服务器端的程序(小服务程序)了。
Servlet 的含义比较多,一句话概括的话我倾向于理解为——Servlet 是Java语言的一套接口和标准。他是目前最主流的实现动态页面的技术之一,开发者可以由此编写java web 服务端应用。而且截止目前,Java 服务端生态圈已经发展处众多的应用框架,如Spring MVC、Struts2……
尽管已经有众多框架可以让开发者免于手动编写Servlet开发程序,但是对于初学者来讲,学习并使用Servlet开发一次Web项目是非常有必要的。
# 二、Servlet容器简介
既然有了这么一套标准,开发者也就无需要自己那么辛苦撸一套基于socket包和http协议的web服务器了。
(P.S 自己撸是不可能的撸的,曾经做计网作业写一个web服务器DEMO,能跑起来很简单,但是实现更多的协议细节和一堆技术的话……)
除了实现这套接口之外,还需要一个“容器”来运行编写好的Servlet,Tomcat就是主流的Servlet 容器之一,将编写好的Servlet 打包成web应用程序就可以在Tomcat上运行了。
# 三、Servlet接口
撇开入门时候就用上的Tomcat,Servlet到底是什么,狭义的Servlet仅仅是一个接口!
// 删掉注释版本,实际上注释很有用
package javax.servlet;
import java.io.IOException;
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}
如图,揭示了Servlet的生命周期和作用。
init(ServletConfig config)
为该Servlet初始化时候被调用的。具体会根据 web.xml(或Java Config)中的
load-on-startup
属性决定初始化时刻。service(ServletRequest req, ServletResponse res)
为业务代码,在这里可以执行业务,读取请求,和修改响应。
destroy()
当Servlet实例将要被销毁时,该方法将会被调用
最基本的Servlet知识仅为如上,然后各公司提供的实现都是从这个接口实现得到,而同包(javax.servlet)下已经有了
GenericServlet
和HttpServlet
的实现。
其中 HttpServlet
继承自 GenericServlet
新增了更多如 doGet
,doPut
等Http操作,进一步方便开发者开发。
另外,Servlet的默认工作方式是单例多线程的,所以需要注意线程安全问题,不好把一些状态保存到一个Servlet实例变量上。
如果需要多例模式,请继承SingleThreadModel,为了性能非常不推荐
public abstract class HttpServlet extends GenericServlet {
private static final long serialVersionUID = 1L;
private static final String METHOD_DELETE = "DELETE";
private static final String METHOD_HEAD = "HEAD";
private static final String METHOD_GET = "GET";
private static final String METHOD_OPTIONS = "OPTIONS";
private static final String METHOD_POST = "POST";
private static final String METHOD_PUT = "PUT";
private static final String METHOD_TRACE = "TRACE";
private static final String HEADER_IFMODSINCE = "If-Modified-Since";
private static final String HEADER_LASTMOD = "Last-Modified";
private static final String LSTRING_FILE =
"javax.servlet.http.LocalStrings";
private static final ResourceBundle lStrings =
ResourceBundle.getBundle(LSTRING_FILE);
// ...Servlet Method...doGet,doPost,doPut....
}
# 四、 围绕Servlet接口的类
围绕着Servet接口有四个类,分别是ServletConfig
、ServletContext
,ServletRequest
、ServletResponse
,依次讲。
# ServletConfig
ServletConfig 是在 Servlet 初始化时就传给 Servlet 了,保存了相关Servlet初始化信息,并且提供了getServletContext()
方法从容器中获得Servlet上下文。
package javax.servlet;
import java.util.Enumeration;
public interface ServletConfig {
public String getServletName();
public ServletContext getServletContext();
public String getInitParameter(String name);
public Enumeration<String> getInitParameterNames();
}
我们看看有哪些类实现了该接口。
getServletContext()
可以追溯到Tomcat源码查看实现
/**
* @return the servlet context for which this Context is a facade.
*/
@Override
public ServletContext getServletContext() {
if (context == null) {
context = new ApplicationContext(this);
if (altDDName != null)
context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
}
return (context.getFacade());
}
如图,获取了 Tomcat 容器中 应用上下文的门面类。
# ServletContext
上文已经讲到, ServletContext
其实就是从 ApplicationContext
中获取其门面类 ApplicationContextFacade
(均实现 ServletContext
接口,屏蔽了部分内部使用的属性)。
每一个独立的Web项目创建时, Tomcat 都会为其新建一个 ServletContext
实例,亦即应用上下文。
public class ApplicationContextFacade implements org.apache.catalina.servlet4preview.ServletContext {
public Object getAttribute(String name) {}
public String getInitParameter(String name) {}
public FilterRegistration.Dynamic addFilter {}
// methods
}
通过该 ServletContext
可以
- 获得Web项目中的共享数据 Attributes...
- 获得初始化参数 InitParameters ,InitParameterNames
- 获得项目资源 Resources
- ……
# ServletRequest
该接口为Servlet对Request请求的封装。
public interface ServletRequest {
public Object getAttribute(String name);
public String getParameter(String name);
public Map<String, String[]> getParameterMap();
public String getRemoteAddr();
public String getRemoteHost();
public void setAttribute(String name, Object o);
public void removeAttribute(String name);
public ServletContext getServletContext();
// ...
}
具体在一次Tomcat Servlet 请求中的实现类为 RequestFacade
包装了 org.apache.catalina.servlet4preview.http.HttpServletRequest
这个类
都是从 容器Tomcat 中传过来的对象,实现了 HttpServletRequest
接口,亦即是在 Servlet 开发中处理 Request 是通常使用的接口。
继续追溯 Request
类 的关系
大概可以得到如下关联,其中最后两个为Tomcat中的Connect 提供的类,Facade为其门面类,均继承自 javax.servlet.ServletRequest
接口, 而另外一个 org.apache.coyote.Request
为 Tomcat Processor 收到请求时候对请求的 初次封装(处理 socket 则由另外的包工具进行,再封装成该 Request
类)。
这样的互相转化,在 Tomcat org.apache.coyote.Adapter
中进行。
public void service(org.apache.coyote.Request req,org.apache.coyote.Response res)throws Exception {
\tRequest request = (Request) req.getNote(ADAPTER_NOTES);
\tResponse response = (Response) res.getNote(ADAPTER_NOTES);
\tif (request == null) {
\t\trequest = connector.createRequest();
\t\trequest.setCoyoteRequest(req);
\t\tresponse = connector.createResponse();
\t\tresponse.setCoyoteResponse(res);
\t\trequest.setResponse(response);
\t\tresponse.setRequest(request);
\t\treq.setNote(ADAPTER_NOTES, request);
\t\tres.setNote(ADAPTER_NOTES, response);
}
// ......
Coyote 为 Tomcat 处理从 Socket
到 Request
的框架,并包装成 Tomcat 自定义的 Request
类,没有继承和实现任何接口,供 Tomcat 自身使用,同时 Tomcat 还讲其包装成实现于 javax.servlet.ServletRequest
接口的类供开发者使用。关于Tomcat 更底层的内容,本篇尚不讨论。
# ServletResponse
Response
类的原理和Request不会差的太远,继承和实现的关系也差不都类似的结构。
# 五、再说 Tomcat 容器
Servlet 容器和 Servlet 互相独立发展又彼此依存,通过标准化的接口互相协作,我们已经把接口的关系差不多了解清楚了,再回到刚开始介绍的 Tomcat 容器的内部结构。
如图,从外到里,整个 Tomcat 生命周期由 Server 层控制, 包含多层 Service ,然后就是 各个 Connector ,再到一个Container 核心组件。Service 可以有多个 Connectors ,但是只能有一个 Container 。
为了更易理解,我们先不从源码阅读,我们从初始配置文件 server.xml
开始阅读。
<Server port="8005" shutdown="SHUTDOWN">
<!-- ... -->
</Server>
最外层 Service 为 Tomcat 关闭服务 使用 8005 端口,所以一种关闭 Tomcat 的方法为 telnet
到 8005 端口执行 SHUTDOWN
命令。然后为各 Listener、GlobalNamingResources 等……不多介绍
<Service name="Catalina">
<!-- ... -->
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"/>
<!-- ... -->
<Engine name="Catalina" defaultHost="localhost">
<!-- ... -->
</Engine>
</Service>
第二层为熟悉的 Catalina Service ,包含了多个 Connector 和 一个 Container 。Tomcat将Engine,Host,Context,Wrapper统一抽象成Container。
Connector 从配置文件看就了解到它负责接收浏览器的发过来的 tcp 连接请求。除此,还将创建一个 Request 和 Response 对象分别用于和请求端交换数据。
再到了 Engine 层
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b"/>
</Host>
</Engine>
一个 Engine 代表一个完整的 Servlet 引擎,并将传过来的请求选择到一个 Host 处理。一个 Engine 可以有多个Host,没个 Host 亦即虚拟主机,初始配置文件只有一个 localhost 的主句, 通常来讲每个 Host 对应一个域名。
Host不是必须的,但是要解析 web.xml
必须要带有HOST
Host 往下为 Context
<Context
path="/library"
docBase="/.../library.war"
reloadable="true"
/>
初始配置文件没有添加 web应用 所以没有 <Context/>
标签, 而一个 Context 对应一个 Web应用,也就是开头提到的 ServletContext
。
Context 以下就是 Wrapper 层,Wrapper 是对 Servlet 的包装,所以一个 Context 可以有多个 Wrapper , Wrapper 已经是 Tomcat 最底层的容器的。
为了管理 Tomcat 组件的生命周期,Tomcat 将其抽象为接口 Lifecycle
package org.apache.catalina;
/**
* <pre>
* start()
* -----------------------------
* | |
* | init() |
* NEW -»-- INITIALIZING |
* | | | | ------------------«-----------------------
* | | |auto | | |
* | | \\\\|/ start() \\\\|/ \\\\|/ auto auto stop() |
* | | INITIALIZED --»-- STARTING_PREP --»- STARTING --»- STARTED --»--- |
* | | | | |
* | |destroy()| | |
* | --»-----«-- ------------------------«-------------------------------- ^
* | | | |
* | | \\\\|/ auto auto start() |
* | | STOPPING_PREP ----»---- STOPPING ------»----- STOPPED -----»-----
* | \\\\|/ ^ | ^
* | | stop() | | |
* | | -------------------------- | |
* | | | | |
* | | | destroy() destroy() | |
* | | FAILED ----»------ DESTROYING ---«----------------- |
* | | ^ | |
* | | destroy() | |auto |
* | --------»----------------- \\\\|/ |
* | DESTROYED |
* | |
* | stop() |
* ----»-----------------------------»------------------------------
*
* @author Craig R. McClanahan
*/
public interface Lifecycle {
public void init() throws LifecycleException;
public void start() throws LifecycleException;
public void stop() throws LifecycleException;
public void destroy() throws LifecycleException;
// ...
}
# 六、 web.xml
web.xml 称为部署描述符文件,在Servlet规范中定义的,是web应用的配置文件,虽然如今已经不是必须的了(基于注解),但是依然有必要提到一下。
该文件定义了web应用的基本配置,例如 servlet、filter、mapping。Tomcat 在 context.xml 中 定义了该文件的寻找路径,寻找到后将会被解析和添加到Context。
如下为一个标准的 spring web.xml 文件实例
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:web="http://java.sun.com/xml/ns/javaee" xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>console</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:spring.xml,classpath:spring-mybatis.xml,classpath:spring-shiro.xml
</param-value>
</context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<description>字符集编码</description>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<description>spring mvc servlet</description>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<description>spring mvc 配置文件</description>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>60</session-timeout>
</session-config>
</web-app>
如图,
- Context 初始化时候加载了 Spring IOC 容器
- 添加了字符集编码的 filter
- 添加了Spring MVC 的 DispatcherServlet 并将 Spring IOC 容器作为其父容器
load-on-startup
的值为1
,该 Servlet 随着项目初始化而初始化init()
# 总结
本文主要目的是,从 Servlet 云里雾里的开发,到对 Servlet 有基本的认识, Tomcat 容器的讲解一直不是重点,只是帮助理解 Servlet 的工作状态。至于从源码分析一切的实现,亦不是本文讨论的重点。
所以本文涉及的内容不会深奥,而且 Tomcat 如此庞大的源码, Servlet 的细节非常多,本人知识能力还不足以一一阐述清楚。
Tomcat 的设计是非常遵守不少设计模式,例如 listener——观察者模式,Engine-->Host-->Context——责任链模式,学习设计模式,会对阅读 Tomcat 源码有很大帮助。
参考链接: