http://docs.oracle.com/javaee/7/api/javax/servlet/Servlet.html
javax.servlet包包含了一系列的接口和类,它们共同描述和定义了servlet容器(如Apache tomcat)与servlet类的规范,以及servlet容器运行时的环境。
为什么要学习servlet,因为我们平常熟悉的Tomcat和Spring都与servlet有分不开的关系。Tomcat是著名的servlet容器,Spring的DispatcherServlet就实现了Servlet接口。
2. Servlet版本
Version | 版本时间 | JSR Number | 平台 | 主要改进 |
Servlet4.0 | 开发中 | 369 | Java EE 8 | HTTP/2 |
Servlet3.1 | 2013.05 | 340 | Java EE 7 | NIO, WebSocket等。 |
Servlet3.0 | 2009.12 | 315 | Java EE 6, Java SE 6 | 可插性,加入异步Servlet、安全以及文件上传。 |
Servlet2.5 | 2005.09 | 154 | Java EE 5, Java SE 5 | 依赖Java SE 5,支持annotation。 |
Servlet2.4 | 2003.11 | 154 | J2EE1.4, J2SE1.3 | web.xml使用了XML规范格式。 |
Servlet2.3 | 2001.08 | 53 | J2EE1.3, J2SE1.2 | 加了Filter相关。 |
Servlet2.2 | 1999.08 | 902, 903 | J2EE1.3, J2SE1.2 | 成为J2EE的一部分,引入war。 |
Servlet2.1 | 1998.11 | -- | -- | 第一个官方版本,加入RequestDispatcher, ServletContext。 |
Servlet2.0 | -- | -- | JDK 1.1 | 成为Java Servlet开发工具2.0的一部分。 |
Servlet1.0 | 1997.06 | -- | -- | -- |
3. 类图
用StarUML画的
3.1 javax.servlet.Servlet相关类图:
3.2 javax.servlet.ServletRequest类图:(ServletResponse略)
4. Servlet生命周期
面试经常被问的问题之一。从3.1的Servlet接口方法中可以看到,Servlet接口定义的方法主要有init(cfg)、service(req, res)、destroy()。
init()、destory()方法只会被调用一次,即在初始化时以及销毁时。
service(req, res)会被调用很多很多次,客户访问web网页,Servlet容器(如Apache Tomcat)会生成一个线程来处理这个用户的请求。这样的设计的优点是节省资源,试想如果客户每请求一次都生成一个新的Servlet,经历init()、service()、destory()生命周期,实在是太浪费资源了。
此外,HttpServlet中的doGet(req, res)或是doPost(req, res)只是对不同的req.getMethod()进行了分类处理,常见的request method有以下几种(摘自HttpServlet类):
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";
5. 重要的类
5.1 javax.servlet.ServletContext
当servlet容器(如Apache Tomcat)运行时,它会布署、加载所有的web应用。当一个应用被加载时,servlet容器会创建一个ServletContext,然后将它放在内存中(即只有一个实例,不会过期)。接着读取web应用中的web.xml的<servlet>、<filter> (Since Servlet 2.3)以及<listener>,(或是annotation@WebServlet, @WebFilter, @WebListener)配置并实例化它们,将他们放在server的内存中。在创建的时候,init()方法会被调用。
也就是说,
在一个Servlet容器中(single node模式下的话,就是只有一个JVM),可能会有多个Servlet,但只会有一个ServletContext。ServletContext接口定义了一系列的方法,用来给Servlet与容器之间进行“交流”,如返回文件的MIME类型,dispatch requests等。
5.2 HttpServletRequest和HttpServletResponse
javax.servlet中只定义了接口,具体的实现是在Servlet容器中。如运行在Tomcat中的话,具体的装饰者类为:
- org.apache.catalina.connector.RequestFacada
- org.apache.catalina.connector.ResponseFacada
当一个用户访问web项目,servlet容器会生成HttpServletResponse、HttpServletResponse,并将它们传递给定义过的Filter链,最终会传给Servlet实例。
6. 多线程
Servlet是线程不安全的。Server在处理用户的request请求通常是以多线程的模式处理,如Tomcat的默认线程池最大数为150。
总结:
- a. ServletContext实例在web app启动时创建,在web app关闭时销毁。所有的request,session共享同一个ServletContext。
- b. Servlet, Filter, Listener实例在web app启动时创建,在web app关闭时销毁。所有的request,session共享同一个ServletContext。
- c. HttpServletRequest和HttpServletResponse起于servlet接收用户的HTTP request,止于response完全返回给用户(网页内容),它们的数据是不会被共享的。
既然容器是以多线程的模式处理request的,那么在Servlet中,在doGet(或doPost等)方法外定义的私有变量可能会有同步问题。在Servlet官方文档也明确提示,并发访问共享资源时要特别小心。共享资源包括内存数据(如类的私有变量,文件,数据库连接等)。
引用官方API:
引用
Servlets typically run on multithreaded servers,so be aware that a servlet must handle concurrent requests and be careful to synchronize access to shared resources. Shared resources include in-memory data such as instance or class variables and external objects such as files, database connections, and network connections.
具体示例:
public class ExampleServlet extends HttpServlet { private Object thisIsNOTThreadSafe; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Object thisIsThreadSafe; thisIsNOTThreadSafe = request.getParameter("foo"); // BAD!! Shared among all requests! thisIsThreadSafe = request.getParameter("foo"); // OK, this is thread safe. } }
7. 分析Tomcat源码来看Servlet特性
7.1 org.apache.catalina.core.StandardWrapper
Servlet的wrapper类,每个Servlet都有自己的wrapper,负责Servlet的初始化等。
API: http://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/core/StandardWrapper.html
部分源码:
public class StandardWrapper extends ContainerBase implements ServletConfig, Wrapper, NotificationEmitter { protected volatile Servlet instance = null; protected volatile boolean singleThreadModel = false; @Override public synchronized void load() throws ServletException { instance = loadServlet(); if (!instanceInitialized) { initServlet(instance); } // 略 } private synchronized void initServlet(Servlet servlet) throws ServletException { if (instanceInitialized && !singleThreadModel) return; // 略 servlet.init(facade); } }
变量singleThreadModel默认值为false,即默认情况下是多线程模式。load()方法中也能看到先是对instance进行实例化,接着判断是否是多线程模式,如果是,那么进行initServlet,调用initServiet(servlet),再进行一系列的操作后,最后调用servlet.init(cfg)方法,也就是3.1类图中标出的(也是第4节生命周期中说到的)init方法。
由此可见,若已经被实例化过(instanceInitialized),那么代码就直接return了,并不会进行servlet.init(cfg),也就是说这个方法只会被调用一次。即servlet生命周期的奥义。
7.2 org.apache.tomcat.util.net.NioEndpoint
如果要看Tomcat的线程池实现,可以看NioEndpoint类。此类继承了抽象类AbstractEndpoint<S>,其它的实现还有AprEndpoint, JIoEndpoint, Nio2Endpoint。
具体API戳:[url]http://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/tomcat/util/net/AbstractEndpoint.html [/url]
参考:
Servlet维基百科: https://en.wikipedia.org/wiki/Java_servlet
Tomcat官方API: http://tomcat.apache.org/tomcat-8.0-doc/api/overview-summary.html
Tomcat在线源码(用Maven下载下来更方便): http://grepcode.com/snapshot/repo1.maven.org/maven2/org.apache.tomcat/tomcat-catalina/8.0.24/
文章: https://blogs.oracle.com/arungupta/entry/what_s_new_in_servlet
文章: http://www.tuicool.com/articles/AZb2ai
在线讨论: http://stackoverflow.com/questions/3106452/how-do-servlets-work-instantiation-sessions-shared-variables-and-multithreadi