第4讲 Servlet的特点及运行过程

来源:互联网 发布:farfetch是正品吗 知乎 编辑:程序博客网 时间:2024/05/16 14:59

Servlet的特点及运行过程
·Servlet的特点
·Servlet的运行过程
·Servlet的线程安全问题


Servlet的特点
·Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
·Servlet引擎是一种容器程序,它负责管理和维护所有Servlet对象的生命周期。Servlet的加载、执行流程、以及如何接收客户端发送的数据和如何将数据传输到客户端等具体的底层事务,都是由Servlet引擎来实现的。Servlet引擎负责将客户端的请求信息转交给Servlet和将Servlet生成的响应信息返回给客户端。
·Servlet属于一种插件,它是一个提供了一些约定方法供容器去调用的类,它只负责在自身的方法中接受并处理容器传递进来的数据,以及生成并返回给容器去使用的数据和状态信息。
·Servlet的最常见应用在于读取WEB浏览器传递给WEB服务器的参数和生产WEB服务器返回给WEB浏览器的动态网页文档内容;Servlet也能获取WEB浏览器发送的HTTP请求消息中的各个请求头和请求行信息,已经生成用于WEB服务器发送的HTTP响应消息中的状态行和响应头信息;Servlet还能获取WEB服务器和Servlet引擎自身的一些环境和状态信息。
·Servlet程序的运行过程就是它与Servlet引擎交互过程,Servlet程序只与Servlet引擎打交道,它并不与Web服务器和客户端进行任何直接的交互。
·Servlet本身就是Java语言的一个应用,具有Java语言的所有优势,能完成普通Java程序所能完成的所有功能。
·Web服务器上可以布置多个功能各不相同的Servlet,每个Servlet都应宣称它可以处理何种样式的URL,当符合样式的URL请求到达时,Servlet引擎用相应的Servlet进行处理。

Servlet程序查询数据库的工作过程 图

Servlet的运行过程
1.Servlet引擎检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第4步,否则,执行第2步。
2.装载并创建该Servlet的一个实例对象。
3.调用Servlet实例对象的init()方法。
4.创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
5.WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。

Servlet的运行过程->特殊说明
·在<servlet>元素中嵌套一个<load-on-startup>子元素,WEB应用程序在启动时就可以装载并创建Servlet的实例对象、以及调用Servlet实例对象的init()方法。
举例:
    <servlet>
        <servlet-name>invoker</servlet-name>
        <servlet-class>
          org.apache.catalina.servlets.InvokerServlet
        </servlet-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>0</param-value>
        </init-param>
        <load-on-startup>2</load-on-startup>
    </servlet>
·在Servlet的整个生命周期内,它的init方法只被调用一次,而对一个Servlet的每次访问请求都导致Servlet的引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法。
·Tomcat也提供了是否自动重新装载被修改的Servlet的配置选项。在<Tomcat安装目录>/conf/server.xml文件中,可以将<Context>元素的reloadable属性设置为true,这样,Tomcat将监视该WEB应用程序的/WEB-INF/classes和/WEB-INF/lib目录下的类是否发生了改变,然后自动重新装载那些发生了改变的类。
举例:
<Context path="/it315" docBase="d:/myweb" debug="0" reloadable="true">
----下面是为就这个问题在课堂上请教的张老师得到的答复,heima开学第一天上课2011.2.28下午
<Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true"
            xmlValidation="false" xmlNamespaceAware="false">
 <Context path="" docBase="" reloadable="true" />
</Host>
老师Tomcat的conf/server.xml文件中上面配置 context的reloadable属性的时候,设置了path和docBase,
如果说path是Tomcat的wabapps目录下的项目名称,那么docBase是设置的那个路径呢?

答疑:
path对应的是我们每次在访问一个网站的时候在浏览器上输入的虚拟目录路径,而服务器上的具体的对应的目录就是docBase。
实际上二者是一个映射过程。
浏览器访问Servlet的过程示意图

 

Servlet、Servlet容器与Servlet API的关系
Servlet API是中定义的是接口作为引用来使用的,而Servlet容器和Servlet都是具体的实现类。Servlet容器通过Servlet API中预先约定好的规则方法来调度管理Servlet。

注意:虽然Servlet源程序中引用的是Servlet API,但Servlet运行时真正调用的对象是由Servlet容器中的实现类创建的,所以,将Servlet API的jar包增加到CLASSPATH环境变量中,只能保证Servlet程序可以被成功编译,但不能让Servlet程序离开Servlet容器运行。

Servlet的线程安全问题
·Servlet引擎采用多线程模式运行,它为并发的每个访问请求都使用一个独立的线程来进行响应,但带来了线程安全的问题。
·如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
·singleThreadModel接口中没有过定义任何方法,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。
·对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
·实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。

动手体验:体验并发访问与线程安全问题
示例代码:
package lqq.heima;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ThreadServlet extends HttpServlet
{
 private int count = 0;
 //从Httpservlet类的帮助文档中复制service方法的声明部分可避免书写错误
 public void service(HttpServletRequest request,
        HttpServletResponse response) throws ServletException,IOException
 {
  count++;
  try{Thread.sleep(500);}catch(InterruptedException e){e.printStackTrace();}
  System.out.println("第" + count + "次访问,当前访问的线程名称是:" + Thread.currentThread().getName());
 }
}
运行结果:
第1次访问,当前访问的线程名称是:http-8080-Processor24
第2次访问,当前访问的线程名称是:http-8080-Processor24
第3次访问,当前访问的线程名称是:http-8080-Processor24
第4次访问,当前访问的线程名称是:http-8080-Processor24
第5次访问,当前访问的线程名称是:http-8080-Processor24
第6次访问,当前访问的线程名称是:http-8080-Processor24
第9次访问,当前访问的线程名称是:http-8080-Processor24
第10次访问,当前访问的线程名称是:http-8080-Processor22
第11次访问,当前访问的线程名称是:http-8080-Processor25
第12次访问,当前访问的线程名称是:http-8080-Processor23
第13次访问,当前访问的线程名称是:http-8080-Processor24
第13次访问,当前访问的线程名称是:http-8080-Processor22
第14次访问,当前访问的线程名称是:http-8080-Processor25
第16次访问,当前访问的线程名称是:http-8080-Processor23
第17次访问,当前访问的线程名称是:http-8080-Processor22
第17次访问,当前访问的线程名称是:http-8080-Processor25
第17次访问,当前访问的线程名称是:http-8080-Processor24

解决同步问题实现同步的代码如下:
package lqq.heima;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ThreadServlet extends HttpServlet
{
 private int count = 0;
 //从Httpservlet类的帮助文档中复制service方法的声明部分可避免书写错误
 public void service(HttpServletRequest request,
        HttpServletResponse response) throws ServletException,IOException
 {
  synchronized(this){
   count++;
   try{Thread.sleep(500);}catch(InterruptedException e){e.printStackTrace();}
   System.out.println("第" + count + "次访问,当前访问的线程名称是:" + Thread.currentThread().getName());
  }
 }
}
运行结果:
第1次访问,当前访问的线程名称是:http-8080-Processor25
第2次访问,当前访问的线程名称是:http-8080-Processor24
第3次访问,当前访问的线程名称是:http-8080-Processor25
第4次访问,当前访问的线程名称是:http-8080-Processor23
第5次访问,当前访问的线程名称是:http-8080-Processor24
第6次访问,当前访问的线程名称是:http-8080-Processor22
第7次访问,当前访问的线程名称是:http-8080-Processor24
第8次访问,当前访问的线程名称是:http-8080-Processor22
第9次访问,当前访问的线程名称是:http-8080-Processor25

以下代码演示实现了SingleThreadModel接口是解决不了线程并发问题的,当多个线程要访问同一个Servlet的对象中的共享数据时,实现了SingleThreadModel接口后,Servlet引擎在同一时刻接到多个线程访问同一个Servlet的时候,引擎会产生一个有多个Servlet实例对象池,然后随机调取一个空闲的Servlet实例对象来处理这个线程,这样就没法实现数据的共享访问了,这个池子中的Servlet是完全不同的各自独立的实例对象。
package lqq.heima;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class ThreadServlet extends HttpServlet implements SingleThreadModel
{
 private int count = 0;
 //从Httpservlet类的帮助文档中复制service方法的声明部分可避免书写错误
 public void service(HttpServletRequest request,
        HttpServletResponse response) throws ServletException,IOException
 {
  synchronized(this){
   count++;
   try{Thread.sleep(500);}catch(InterruptedException e){e.printStackTrace();}
   System.out.println("第" + count + "次访问,当前访问的线程名称是:" + Thread.currentThread().getName());
  }
 }
}
运行结果:
第1次访问,当前访问的线程名称是:http-8080-Processor25
第1次访问,当前访问的线程名称是:http-8080-Processor24
第1次访问,当前访问的线程名称是:http-8080-Processor23
第2次访问,当前访问的线程名称是:http-8080-Processor22
第2次访问,当前访问的线程名称是:http-8080-Processor25
第2次访问,当前访问的线程名称是:http-8080-Processor24
第3次访问,当前访问的线程名称是:http-8080-Processor23
第3次访问,当前访问的线程名称是:http-8080-Processor22
第3次访问,当前访问的线程名称是:http-8080-Processor25
第1次访问,当前访问的线程名称是:http-8080-Processor24
第4次访问,当前访问的线程名称是:http-8080-Processor21
第4次访问,当前访问的线程名称是:http-8080-Processor23

指点迷津:哪些情况下要考虑线程安全问题
在Servlet程序中除了访问成员变量的时候要注意线程安全问题,在访问其他一些共享资源的时候也要注意安全问题,例如:访问代表整个Servlet容器的web应用程对象,这个Servlet容器是被所有的Servlet共享的,所以Servlet程序在访问Servlet容器对象的时候会涉及到多个线程同时访问的问题,在Servlet程序访问数据库的时候也会涉及到多线程问题,如果在Servlet中没有定义成员变量或者Servlet的service方法中没有访问无同步访问控制的共享数据的时候,即使多个线程并发的去调用service方法也不会发生任何线程安全问题,当我们访问共享资源如果这个共享资源自己已经实现了同步控制,当然是不会出现线程安全问题的。

ServletConfig接口
·Servlet在有些情况下可能需要访问Servlet容器或借助Servlet容器访问外部的资源,所以,Servlet引擎需要将表示Servlet容器的对象传递给Servlet。另外,在web.xml文件中为某个Servlet设置的友好名称和初始化参数等信息也需要传递给该Servlet。
为Servlet设置参数的例子:
<servlet>
 <servlet-name>ConfigTest</servlet-name>
 <servlet-class>ConfigTestServlet</servlet-class>
 <init-param>
  <param-name>Corporation</param-name>
  <param-value>北京传智播客公司</param-value>
 </init-param>
</servlet>


·Servlet引擎将代表Servlet容器的对象和Servlet的配置参数信息一并封装到一个称为ServletConfig的对象中,并在初始化Servlet实例对象时传递给该Servlet。ServletConfig接口则用于定义ServletConfig对象需要对外提供的方法,以便于在Servlet程序中可以调用这些方法来获取有关信息。
·Servlet引擎调用Servlet的实例对象的init(ServletConfig config)方法将ServletConfig对象传递给Servlet。Servlet.getServletConfig()方法必须返回init(ServletConfig config)方法传递进来的这个ServletConfig对象的引用。

SeveletConfig接口的方法
·getInitParameterNames
·getInitParameter
·getServletName
·getServletContext


GenericServlet类实现ServletConfig接口的目的
·Servlet接口中定义了一个getServletConfig方法,该方法必须返回Servlet容器调用Servlet.init(ServletConfig config)方法时传递进来的那个ServletConfig对象的引用,GenericServlet类已经按此要求实现了getServletConfig方法。
·在Servlet程序中如何调用ServletConfig对象的方法
 举例:String servletName = getServletConfig().getServletName();
·GenericServlet类如何实现ServletConfig接口中的方法
举例:
public String getServletName()
{
 return getServletConfig().getServletName();
}
·在Servlet程序中调用ServletConfig对象的方法的简单方式
举例:String servletName = getServletName();

动手体验:了解ServletConfig对象的应用
第一步编写Servlet代码:
package lqq.heima;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.Enumeration;
public class ConfigTestServlet extends HttpServlet
{

 //从Httpservlet类的帮助文档中复制service方法的声明部分可避免书写错误
 public void service(HttpServletRequest request,
        HttpServletResponse response) throws ServletException,IOException
 {
  response.setContentType("text/html;charset=GB2312");
  PrintWriter out = response.getWriter();
  
  out.println("<html>");
  out.println("Servlet名称是:" + /*this.*/getServletConfig().getServletName() +"<br>");
  
  Enumeration e = getServletConfig().getInitParameterNames();
  out.println("下面是为Servlet设置的参数:<br>");
  while(e.hasMoreElements()){
   String key = (String)e.nextElement();
   String value = /*this.*/getInitParameter(key);
   out.println("&nbsp;&nbsp;" + key + "=" + value + "<br>");

  }
  
  ServletContext context = getServletContext();
  String path = context.getRealPath("/");
  out.println("当前WEB应用程序的本地目录:" + path + "<br>");
  out.println("</html>");
 }
}
第二步:配置该web程序项目下的web.xml文件注册该Servlet
 <servlet>
  <servlet-name>ConfigTest</servlet-name>
  <servlet-class>lqq.heima.ConfigTestServlet</servlet-class>
  <init-param>
   <param-name>firstname</param-name>
   <param-value>zhang</param-value>
  </init-param>
  <init-param>
   <param-name>lastname</param-name>
   <param-value>san</param-value>
  </init-param>
 </servlet>
 <servlet-mapping>
  <servlet-name>ConfigTest</servlet-name>
  <url-pattern>/demo/config.html</url-pattern>
 </servlet-mapping>
第三步:配置conf/server.xml文件的<Host>标签中的<Context>标签的reloadable属性为true
<Context path ="/servlet" docBase="servlet" reloadable="true"/>
注意:配置Servlet的初始化参数的时候是有多少个参数就需要配置多少对下面的标签
  <init-param>
   <param-name>firstname</param-name>
   <param-value>zhang</param-value>
  </init-param>
  <init-param>
   <param-name>lastname</param-name>
   <param-value>san</param-value>
  </init-param>
使用如下语句在Servlet的输出的网页文件中解决中文乱码问题:设置响应头的ContentType头字段的值为:text/html;charset=GB2312
response.setContentType("text/html;charset=GB2312");