结合源码谈谈Servlet的实例化、变量以及多线程

[b]1. Servlet官方文档:[/b]
[url]http://docs.oracle.com/javaee/7/api/javax/servlet/Servlet.html[/url]

javax.servlet包包含了一系列的接口和类,它们共同描述和定义了servlet容器(如Apache tomcat)与servlet类的规范,以及servlet容器运行时的环境。

为什么要学习servlet,因为我们平常熟悉的Tomcat和Spring都与servlet有分不开的关系。Tomcat是著名的servlet容器,Spring的DispatcherServlet就实现了Servlet接口。


[b]2. Servlet版本[/b]
[table]
|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|--|--|--|
[/table]


[b]3. 类图[/b]
用StarUML画的
[b]3.1 javax.servlet.Servlet相关类图:[/b]
[img]http://dl.iteye.com/upload/picture/pic/137205/322880b3-b72a-357f-b5ba-fe0c76b9771b.png[/img]

[b]3.2 javax.servlet.ServletRequest类图:(ServletResponse略)[/b]
[img]http://dl.iteye.com/upload/picture/pic/137207/26a30109-50cd-3e17-9ab6-47cdc66a8dd8.png[/img]


[b]4. Servlet生命周期[/b]
面试经常被问的问题之一。从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";



[b]5. 重要的类[/b]
[b]5.1 javax.servlet.ServletContext[/b]
当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等。

[b]5.2 HttpServletRequest和HttpServletResponse[/b]
javax.servlet中只定义了接口,具体的实现是在Servlet容器中。如运行在Tomcat中的话,具体的装饰者类为:
[list]
[*]org.apache.catalina.connector.RequestFacada
[*]org.apache.catalina.connector.ResponseFacada
[/list]
当一个用户访问web项目,servlet容器会生成HttpServletResponse、HttpServletResponse,并将它们传递给定义过的Filter链,最终会传给Servlet实例。


[b]6. 多线程[/b]
Servlet是线程不安全的。Server在处理用户的request请求通常是以多线程的模式处理,如Tomcat的默认线程池最大数为150。

总结:
[list]
[*]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完全返回给用户(网页内容),它们的数据是不会被共享的。
[/list]
既然容器是以多线程的模式处理request的,那么在Servlet中,在doGet(或doPost等)方法外定义的私有变量可能会有同步问题。在Servlet官方文档也明确提示,并发访问共享资源时要特别小心。共享资源包括内存数据(如类的私有变量,文件,数据库连接等)。

引用官方API:
[quote]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.[/quote]

具体示例:

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.
}
}



[b]7. 分析Tomcat源码来看Servlet特性[/b]
[b]7.1 org.apache.catalina.core.StandardWrapper[/b]
Servlet的wrapper类,每个Servlet都有自己的wrapper,负责Servlet的初始化等。
API:[url]http://tomcat.apache.org/tomcat-8.0-doc/api/org/apache/catalina/core/StandardWrapper.html[/url]

部分源码:

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生命周期的奥义。

[b]7.2 org.apache.tomcat.util.net.NioEndpoint[/b]
如果要看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]


[b]参考:[/b]
Servlet维基百科:[url]https://en.wikipedia.org/wiki/Java_servlet[/url]
Tomcat官方API:[url]http://tomcat.apache.org/tomcat-8.0-doc/api/overview-summary.html[/url]
Tomcat在线源码(用Maven下载下来更方便):[url]http://grepcode.com/snapshot/repo1.maven.org/maven2/org.apache.tomcat/tomcat-catalina/8.0.24/[/url]
文章:[url]https://blogs.oracle.com/arungupta/entry/what_s_new_in_servlet[/url]
文章:[url]http://www.tuicool.com/articles/AZb2ai[/url]
在线讨论:[url]http://stackoverflow.com/questions/3106452/how-do-servlets-work-instantiation-sessions-shared-variables-and-multithreadi[/url]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值