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

1. Servlet官方文档:
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

猜你喜欢

转载自angelbill3.iteye.com/blog/2374280