浅析Servlet

Servlet是Java Web中一个非常重要的概念,而且它的定义很模糊,在此总结一下

Servlet(Server Applet),全称Java Servlet,暂无中文译文。是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。

一、Servlet容器与Tomcat

Servlet没有main()方法,他们受控于另外一个Java应用,这个Java应用称为容器(Container)。最著名的Servlet容器一个就是Tomcat了。

1、Tomcat定义

Tomcat是Servlet容器,同时也是轻量级的Web服务器。这是它的两个身份!

Apache Server、Microsoft IIS、Apache Tomcat都是Web服务器。

Tomcat作为Web服务器时,主要负责实现HTTP传输等工作。

Tomcat作为Servlet容器时,主要负责解析Request,生成ServletRequest、ServletResponse,将其传给相应的Servlet(调用service( )方法),再将Servlet的相应结果返回。

2、Tomcat的工作过程

Tomcat实现了servlet API。也就是说它的内部,定义了Servlet中接口的实现类,并且在它运行时,将一些实现类自动地实例化。

Tomcat 是Web应用服务器,是一个Servlet/JSP容器。Tomcat 作为Servlet容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。

而Servlet是一种运行在支持Java语言的服务器上的组件。Servlet最常见的用途是扩展Java Web服务器功能,提供非常安全的,可移植的,易于使用的CGI替代品。 它的处理流程如下:

  1. Web客户向Servlet容器(Tomcat)发出Http请求
  2. Servlet容器分析客户的请求信息
  3. Servlet容器创建一个HttpRequest对象,将客户请求的信息封装到这个对象中
  4. Servlet容器创建一个HttpResponse对象
  5. Servlet容器调用HttpServlet对象的service方法,把HttpRequest对象与HttpResponse对象作为参数传给 HttpServlet对象
  6. HttpServlet调用HttpRequest对象的有关方法,获取Http请求信息
  7. HttpServlet调用HttpResponse对象的有关方法,生成响应数据
  8. Servlet容器把HttpServlet的响应结果传给Web客户

3、Tomcat结构组成

  • Server,代表整个Servlet容器组件,是Tomcat的顶层元素。其中可以包含一到多个Service;

  • Service,包含一个Engine,以及一到多个Connector;

  • Connector,代表和客户端程序实际交互的组件,负责接收客户请求,以及向客户返回响应结果;

  • Engine,处理同一个Service中所有Connector接收到的客户请求;

  • Host,在Engine中可以包含多个Host,每个Host定义了一个虚拟主机,它可以包含一个到多个Web应用;

  • Context,一个Host中可以包含多个Context,每个Context代表了运行在虚拟主机上的单个Web应用。

4、Tomcat的工作模式

  1. 独立的Servlet容器:Tomcat作为独立的Web服务器单独运行,Servlet容器作为Web服务器的一部分,这也是默认工作模式。
  2. 其他Web服务器进程内的Servlet容器:Tomcat作为进程内的Servlet容器时,Servlet容器是作为Web服务器的插件和Java容器的实现。
  3. 其他Web服务器进程外的Servlet容器:Tomcat作为进程外的Servlet容器时,Servlet容器运行于Web服务器之外的地址空间,并且作为Web服务器的插件和Java容器的实现的结合。

二、Servlet的生命周期

1、生命周期

Servlet的生命周期是由Servlet容器控制的。Servlet一生中只有一个实例出现,但是有多个线程出现。

  1. 加载类:Servlet .class文件
  2. 实例化:构造函数运行
  3. 初始化:容器调用 init() 方法(一生只调一次,并且是在第一次调用Servlet的时候执行)
  4. Service方法:Servlet一生主要在这里度过
  5. 销毁:销毁实例之前调用 destroy() 方法
  6. 可回收:等待垃圾回收等待垃圾回收

注意:当我们配置文件中指定load-on-startup 属性时,若他的值是一个大于等于0的整数,容器会在启动的时候加载这个servlet类并调用他的init方法。

2、关键方法

  1. init()方法:在servlet实例被创建后调用,可以覆盖此方法做一些初始化的工作,比方说得到一个数据库连接。
  2. service()方法:当客户请求到来时,容器会创建一个新的线程,调用Servlet的service()方法。Servlet的一生基本都在这里度过。我们一般不要覆盖此方法。
  3. doGet或者doPost()方法:service方法根据请求的HTTP方法,来调用doGet()或者doPost()方法。我们在开发Servlet时肯定要覆盖此方法。每次运行doGet()或者doPost()方法,它都在一个单独的线程中。

3、init方法与构造函数

一直有个疑问:构造函数用来初始化类,可是servlet初始化却是init方法,可servlet本质上也是Java类,那它的构造方法和init函数到底是什么关系?

  • 首先,构造函数是有的,虽然我们通常不写Servlet的构造函数,但是就像任何一个普通的Java类一样,编译器会自动给你生成一个默认构造函数;
  • 其次,构造函数和init方法都会被web容器调用,而且是先调用构造函数,然后调用init方法;
  • 最后,貌似容器只会调用默认构造函数,所以如果你自己写了带参数的构造函数(系统就不会自动生成默认构造函数),容器初始化servlet就会出错……P.S.:任何时候都不推荐自己写构造函数来初始化servlet类,哪怕你自己提供不带参数的构造函数……

4、单例多线程

关于Servlet接触到一个新的名词:单例多线程。就是只有一个实例,但是有多个线程来执行它

4.1单实例多线程

再来看一下Servlet容器默认是采用单实例多线程的方式处理多个请求的:

  1. 当web服务器启动的时候(或客户端发送请求到服务器时),Servlet就被加载并实例化(只存在一个Servlet实例);
  1. 容器初始化Servlet。主要就是读取配置文件(例如tomcat,可以通过servlet.xml的\设置线程池中线程数目,初始化线程池;通过web.xml,初始化每个参数值等等);
  1. 当请求到达时,Servlet容器通过调度线程(Dispatchaer Thread)调度它管理下的线程池中等待执行的线程(Worker Thread)给请求者;
  1. 线程执行Servlet的service方法;
  1. 请求结束,放回线程池,等到被调用;

由此可以看出:

  1. Servlet单实例,减少了产生servlet的开销;
  2. 通过线程池来响应多个请求,提高了请求的响应时间;
  3. Servlet容器并不关心到达的Servlet请求访问的是否是同一个Servlet还是另一个Servlet,直接分配给它一个新的线程;如果是同一个Servlet的多个请求,那么Servlet的service方法将在多线程中并发的执行;
  4. 每一个请求由ServletRequest对象来接受请求,由ServletResponse对象来响应该请求;

4.2、原因

为什么只有一个实例:

出于性能的考虑:特别的对于门户网站而言,每一个Servlet在每一秒内的并发访问量都可以是成千上万的。在一个面向模块化开发的现在,常常一个点击操作就被定义为一个Servlet的实现,而如果Servlet的每一次被访问,都创建一个新的实例的话,服务器的可用资源消耗量将是一个相当重要的问题。退一步,一般Servlet的访问是很快的,每一个实例被快速的创建,又被快速的回收,GC的回收速度也跟不上,频繁的内存操作也将可能带来次生的问题。

所以,Servlet的“单一实例化”是一个很重要的策略。另外一点,JSP实质上也是一个Servlet文件。一个页面被请求时,应该如何返回被请求页面的内容?是把一个“页面对象”返回?一个页面既有的框架内容大都基本固定(当然现在ajax横行,但其所基于的也是另一个页面的访问),其所可复用,重用的部分是相当的。

那么,它如何解决并发的问题呢?

服务器容器会为每一个Servlet维护一个连接池,是c3p0或dbcp,都实现了DataSource,是其实现类。在编程实现上可理解为一个Application级别的Vector(自身线程安全的集合类),里面装载相当数量的Connection。每一个HTTP请求到来时,将分配一个Connection去响应请求。并在结束响应后,把Connection放回线程池。而如果当前可用Connection小于一个标准数量值,便自动添加新的Connection到Vector中。

所以,除了Servlet文件的编写及相关的部署外,所有的其他操作其实都交付给了服务器容器来进行管理。

另外,在Tomcat上,Container的实现形式有Wrapper、Context、Host、Engine,其级别递增。而每一个Servlet就代表了一个Wrapper容器,那么很自然的,也可以“证明”上面的说法。

4.3、Servlet的成员变量

因为是单例的,又有多线程访问,所以自然要设计到线程安全问题。

同一个Servlet得到多个请求到来时,如果该Servlet中存在成员变量,可能发生多线程同时访问该资源时,都来操作它,造成数据的不一致,因此产生线程安全问题。

解决方法:

  1. 实现SingleThreadModel接口:
    • 如果一个Servlet被这个接口指定,那么在这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题;
    • 如果一个Servlet实现了SingleThreadModel接口,Servlet引擎将为每个新的请求创建一个单独的Servlet实例,这将引起大量的系统开销。SingleThreadModel在Servlet2.4中已不再提倡使用;
  2. 对共享互数据同步:
    • 使用synchronized关键字能保证一次只有一个线程可以访问被保护的区段,Servlet可以通过同步块操作来保证线程的安全。
    • ServletRequest对象是线程安全的,但是ServletContext和HttpSession不是线程安全的;
    • 同样如果在程序中使用同步来保护要使用的共享的数据,也会使系统的性能大大下降。这是因为被同步的代码块在同一时刻只能有一个线程执行它,使得其同时处理客户请求的吞吐量降低,而且很多客户处于阻塞状态。
  3. 避免使用成员变量:
    • 线程安全问题是由实例变量造成的,只要在Servlet里面的任何方法里面都不使用实例变量,那么该Servlet就是线程安全的。(所有建议不要在servlet中定义成员变量,尽量用局部变量代替)
    • 从Java内存模型可以知道,方法中的临时变量是在栈上分配空间,而且每个线程都有自己私有的栈空间,所以它们不会影响线程的安全。
    • 这里可以顺便提一下ThreadLocal,正在学习之中

三、Servlet相关的类

写网站的时候确实会用到ServletContext、HttpServletRequest 、HttpServletResponse和HttpSession这些类,但是对于他们的关系还是一知半解。

1、HttpServletRequest and HttpServletResponse

Servlets容器依附于在特定端口监听http请求的web服务器,我们在开发的时候通常使用的是8080端口。当一个客户端(使用浏览器的用户)发送一个http请求的时候,Servlets容器会创建一个新的HttpServletRequest和HttpServletResponse对象,在一个线程中,通过在filter和servlet中已经创建了的方法匹配URL来传递。

这个request对象提供了http请求的所有信息,比如请求的头和请求的主体,这些response对象提供了你灵活控制发送http response的方式,比如设置头和体(通常包含jsp中HTML的内容)。当一个http response提交并完成的时候,request和response对象都会被销毁。

2、HttpSession

一个客户端第一次访问web应用的时候或者httpSession第一次被request.getSession()获取的时候,servlets容器会创建它,产生一个long并且是唯一的一个ID(可以通过session.getId()获取)并且存储在服务器的内存中,servlets容器同样会在http response头中的Set-Cookie设置cookie,使用JESSIONID作为cookie的名字,唯一的session ID作为cookie的值。

因为每一个http cookie的详细说明(一个良好的web浏览器和web服务器间的契约必须要附加传递),只要cookie有效,客户端会在后续的request中在cookie的头中发送回cookie回去。使用浏览器内部的http流量监控你可以检查他们,servlets容器将决定每个http request中名字为JSESSRIONID和值为sessionID的cookie和服务器内存中哪一个httpSession相关联。

httpSession存活的时间是由<session-timeout>设置的时间决定的,你可以在web.xml中进行设定,默认情况下是30分钟。所以当客户端不访问服务器超过30分钟的时候,Servlet容器会销毁这个session。每一个后续的请求,即使已经指定了cookie,也不会访问同一个session了,servlets容器会创建一个新的session。

另一方面,session cookie在客户端也有一个默认的时间,只要这个浏览器是运行的。所以当客户端关闭浏览器的之后,客户端的session会被销毁,再次打开浏览器同一个session不会被发送,一个新的request.getSession()会返回一个新的httpSession并用新的sessionID设置cookie。

3、ServletContext

一个 WEB 运用程序只有一个 ServletContext 实例, 它是在容器(包括 JBoss, Tomcat 等)完全启动 WEB 项目之前被创建, 生命周期伴随整个 WEB 运用。

利用ServletContext 能够获得 WEB 运用的配置信息, 实现在多个 Servlet 之间共享数据等。比如通过ServletContext 类的getRealPath()方法获取服务器的真实地址

获取方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RequestMapping("/psxt/login") 
@ResponseBody
public ResponseMessage login(HttpSession session) {
ServletContext servletContext=session.getServletContext();
String path=servletContext.getRealPath("");
return loginHandler.tryLogin(session,userName, password);
}


public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
ServletContext servletContext=request.getSession().getServletContext();
String path=servletContext.getRealPath("");

WebApplicationContext webApplicationContext = ContextLoader.getCurrentWebApplicationContext();
ServletContext servletContext = webApplicationContext.getServletContext();
return true;
}

可以通过当前的Session或者request来获取。或者直接通过Spring的WebApplicationContext来获取。

4、WebApplicationContext

ApplicationContext是Spring的核心,在介绍Spring的时候知道它是一个Bean容器,在SpringMVC中,会用到的是WebApplicationContext,它继承自ApplicationContext。

WebApplicationContext的初始化方式和以前介绍的BeanFactory、ApplicationContext容器有所区别,因为WebApplicationContext需要ServletContext实例,也就是说它必须在拥有Web容器的前提下才能完成启动的工作。

在web.xml中配置自启动的Servlet或定义Web容器监听器(ServletContextListener),借助着两者中的任何一个,我们就可以启动Spring Web应用上下文的工作。

作用:

  • 因为它是SpringMVC容器,所以得到它后就可以直接获取Bean了。
  • 还可以像上面说的获取ServletContext

5、生命周期

  1. HttpServletRequest 和HttpServletResponse的生命周期是request发送和response完成,不能被共享。
  2. HttpSession的生命周期和客户端服务器运行的时间一样长,只要服务器端没有超时,在同一个session中共享所有请求。
  3. ServletContext的生命周期和web应用的生命周期一样长,被所有session中的所有请求所共享。

四、参考地址

http://blog.csdn.net/truong/article/details/21044595

http://blog.csdn.net/pirateleo/article/details/8574973

http://blog.csdn.net/cnctloveyu/article/details/4303636

http://blog.csdn.net/firstests/article/details/8121229

http://blog.csdn.net/zheng0518/article/details/44948455

http://greenyouyou.blog.163.com/blog/static/13838814720112143022834/

http://blog.csdn.net/maoyeqiu/article/details/49718173

http://blog.csdn.net/zhulinu/article/details/7305247