Java web之Servlet技术详解
write:2022-4-14
详细学习Servlet之前,需要明白java web的入门知识即对Servlet容器和对Servlet有大概认识,回顾:java web应用入门
文章目录
1. Servlet简介
(1)Java Servlet是和平台无关的服务器端组件,它运行在Servlet容器中。
(2)Servlet容器负责Servlet和客户的通信以及调用Servlet的方法,Servlet和客户的通信采用“请求/响应”的模式。
(3)运行在Web服务器中的Servlet可完成如下功能:
动态生成HTML文档。
把请求转发给其他Servlet组件。
读取客户端的Cookie,以及向客户端写入Cookie。
访问其他服务器资源(如数据库服务器或基于Java的应用服务器)。
2. Servlet API
Servlet的框架是由两个Java包组成:
javax.servlet包:定义了所有的Servlet类都必须实现或扩展的通用接口和类。
javax.servlet.http包:定义了采用HTTP协议通信的HttpServlet类。
2.1 Servlet API类框图
servlet之所以功能众多,一是servlet由java语言编写,只要编写者能力深厚,能写出完成各种功能的servlet,二是由于Servlet容器为servlet提供了许多资源;
Servlet容器为Servlet提供了十八般武器:
1)请求对象(ServletRequest和HttpServletRequest):Servlet从该对象中获取来自客户端的请求信息。
2)响应对象(ServletResponse和HttpServletResponse):Servlet通过该对象来生成响应结果。
3)Servlet配置对象(ServletConfig):当容器初始化一个Servlet对象时,会向Servlet提供一个ServletConfig对象,Servlet 通过该对象来获取初始化参数信息以及ServletContext对象。
4)Servlet上下文对象(ServletContext):Servlet通过该对象来访问容器为当前Web应用提供的各种资源。
2.2 Servlet接口
1)Servlet的框架的核心是javax.servlet.Servlet接口,所有的Servlet都必须实现这一接口。
2)在Servlet接口中定义了五个方法,其中有三个方法在Servlet的生命周期的不同阶段被Servlet容器调用(Servlet规范规定):
init()方法:负责初始化Servlet对象;
service()方法:负责响应客户的请求;
destroy()方法:当Servlet对象退出生命周期时,负责释放占用的资源。
3)Servlet接口还定义了以下两个返回Servlet的相关信息的方法。JavaWeb应用中的程序代码可以访问Servlet的这两个方法,从而获得Servlet的配置信息以及其他相关信息:
getServletConfig():返回一个ServletConfig对象,该对象中包含了Servlet的初始化参数信息。
getServletInfo():返回一个字符串,该字符串中包含了Servlet的创建者、版本和版权等信息。
2.3 Servlet接口及实现类
2.3.1 GenericServlet抽象类
GenericServlet抽象类为Servlet接口提供了通用实现(只要是实现init()方法),它与任何网络应用层协议无关。
public abstract class GenericServlet
implements Servlet, ServletConfig, java.io.Serializable{
private transient ServletConfig config;
public void destroy(){}
public void init(ServletConfig config) throws ServletException {
this.config = config; //使当前Servlet对象与Servlet容器传入的ServletConfig对象关联
this.init();
}
public void init() throws ServletException {} //子类可以重新实现该方法
public abstract void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
...
public ServletContext getServletContext() {
return getServletConfig().getServletContext();
}
}
2.3.2 HttpServlet抽象类
如果你的Servlet类扩展了HttpServlet类,你通常不必实现service方法,因为HttpServlet类已经实现了service方法,该方法的声明形式如下:
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException;
在HttpServlet的service方法中,首先从HttpServletRequest对象中获取HTTP请求方式的信息,然后再根据请求方式调用相应的方法。例如:如果请求方式为GET,那么调用doGet方法;如果请求方式为POST,那么调用doPost方法。
2.3.3 ServletRequest接口
ServletRequest接口中封装了客户请求信息,如客户请求方式、参数名和参数值、客户端正在使用的协议, 以及发出客户请求的远程主机信息等。ServletRequest接口还为Servlet提供了直接以二进制方式读取客户请求数据流的ServletInputStream。
ServletRequest的子类可以为Servlet提供更多的和特定协议相关的数据. 例如: HttpServletRequest 提供了读取HTTP Head信息的方法。
ServletRequest接口的主要方法:
getContentType 返回客户请求数据MIME类型
getInputStream 返回以二进制方式直接读取客户请求数据的输入流
getParameter 根据给定的参数名返回参数值
getRemoteAddr 返回远程客户主机的IP地址
getRemoteHost 返回远程客户主机名
getRemotePort 返回远程客户主机的端口
2.3.4 ServletResponse 接口
ServletResponse 接口为Servlet提供了生成响应结果的方法。它允许Servlet设置返回数据的长度和MIME类型, 并且提供输出流ServletOutputStream。
ServletResponse子类可以提供更多和特定协议相关的方法。例如: HttpServletResponse 提供设定HTTP HEAD信息的方法。
ServletResponse 接口的主要方法:
getOutputStream 返回可以向客户端发送二进制数据的输出流对象ServletOutputStream
getWriter 返回可以向客户端发送字符数据的PrintWriter对象
setCharacterEncoding 设置Servlet发送的响应数据的字符编码
setContentType 设置Servlet发送的响应数据的MIME类型
getCharacterEncoding 返回Servlet发送的响应数据的字符编码
getContentType 返回Servlet发送的响应数据的MIME类型
3. Java Web应用的生命周期
在一个Servlet容器中,可以同时运行多个java web应用,每个java web应用的生命周期都是独立的。
java web的生命周期由Servlet容器控制,每个java web的生命周期分为3个阶段:
启动阶段:加载Web应用的有关数据,创建ServletContext对象,对一些Servlet进行初始化。
运行时阶段:为客户端提供服务。
终止阶段:释放Web应用所占用的各种资源。
3.1 Java Web应用的启动阶段
Servlet容器在启动JavaWeb应用时,会完成以下操作:
(1)把web.xml文件中的数据加载到内存中。
(2)为JavaWeb应用创建一个ServletContext对象(每个java web应用都有唯一的一个)。
(3)对所有的Filter(过滤器)进行初始化。
(4)对那些需要在Web应用启动时就初始化的Servlet进行初始化。
3.1.1 JavaWeb应用被启动的时机
当Servlet容器启动时,会启动已经发布其中的所有Web应用。
以Tomcat作为Servlet容器为例:当web应用发布到Tomcat中,那么web有多种被启动的时机:
一:随着Tomcat的启动而被启动;
二:通过Tomcat的管理平台(手动控制)启动Web应用;
三:如果Web应用发布到Tomcat中,可以为Web应用配置一个元素,该元素有一个reloadable属性。如果这个属性设为true,Tomcat在运行时会监视在Web应用的WEB-INF/classes 和WEB-INF/lib目录下的类文件的改动,以及监视Web应用的WEB-INF/web.xml文件的改动。如果监测到有类文件或web.xml文件被更新,Tomcat会自动重新启动Web应用。
3.1.2 如何登入Tomcat的管理平台
1)从Tomcat的主页( http://localhost:8080 )上选择Tomcat Manager链接,就会登入Tomcat的管理平台http://localhost:8080/manager/html,会弹出安全验证窗口
2)为了通过安全验证,必须先修改Tomcat根目录下conf/tomcat-users.xml文件:
<user username="tomcat" password="tomcat" roles="admin,manager"/>
3)重启Tomcat服务器,然后就能以用户名tomcat,口令tomcat进行
注意:
\webapps\manager\META-INF\目录下的context.xml文件,新增方框内容,注释原来标签,否则是默认主机访问
Tomcat管理平台:
已发布的java web应用
3.2 JavaWeb应用的运行时阶段
在这个阶段,它的所有Servlet都处于待命状态,随时可以响应客户端的特定请求,提供相应的服务。
假如客户端请求的Servlet还没有被初始化,Servlet容器会先初始化Servlet,然后再调用它的service()服务方法。
3.3 JavaWeb应用的终止阶段
(1)销毁JavaWeb应用中所有处于运行时状态的Servlet。
(2)销毁JavaWeb应用中所有处于运行时状态的Filter。
(3)销毁所有与JavaWeb应用相关的对象,如ServletContext对象等,并且释放Web应用所占用的相关资源。
4. Servlet的生命周期
Servlet 的生命周期(由Servlet容器管理)可以分为三个阶段:
初始化阶段
运行时阶段,可以响应客户请求
终止阶段
在javax.servlet.Servlet接口中定义了三个方法init(), service(), destroy(),它们将分别在Servlet的不同阶段被调用。
4.1 Servlet的初始化阶段
1)Servlet容器加载Servlet类,把它的.class文件中的数据读入到内存中。
2)Servlet容器创建ServletConfig对象。ServletConfig对象包含了特定Servlet的初始化配置信息,如Servlet的初始参数。此外,Servlet容器还会使得ServletConfig对象与当前Web应用的ServletContext对象关联。
每一个web应用有一个ServletContext对象,对于其中的每一个Servlet,都有一个ServletConfig对象:
3)Servlet容器创建Servlet对象。
4)Servlet容器调用Servlet对象的init(ServletConfig config)方法。在Servlet接口的GenericServlet实现类的init(ServletConfig config)方法中,会建立Servlet对象与ServletConfig对象的关联关系。
Servlet–>ServletConfig–>ServletContext
4.1.1 Servlet被初始化的时机
在下列情况之一,Servlet会进入初始化阶段:
(1)当前Web应用处于运行时阶段,特定Servlet被客户端首次请求访问。多数Servlet都会在这种情况下被Servlet容器初始化。
(2)如果在web.xml文件中为一个Servlet设置了load-on-startup元素,那么当Servlet容器启动Servlet所属的Web应用时,就会初始化这个Servlet。
<servlet>
<servlet-name>myservlet</servlet-name>
<servlet-class> MyServlet</servlet-class>
<load-on-startup> 1 </load-on-startup> //设置为1,当web应用启动时第一个被初始化的Servlet
</servlet>
eg:
<servlet>
<servlet-name>servlet1</servlet-name>
<servlet-class> Servlet1</servlet-class>
<load-on-startup> 1 </load-on-startup> //设置为1,当web应用启动时第一个初始化的是Servlet1
</servlet>
<servlet>
<servlet-name>servlet2</servlet-name>
<servlet-class> Servlet2</servlet-class>
<load-on-startup> 2 </load-on-startup> //设置为2,当web应用启动时初始化完Servlet1,Servlet2初始化
</servlet>
<servlet>
<servlet-name>servletX</servlet-name>
<servlet-class> ServletX</servlet-class> //没有设置,只有当ServletX被客户端首次请求访问才初始化
</servlet>
4.2 Servlet的运行时阶段
在这个阶段,Servlet可以随时响应客户端的请求。
对于到达Servlet容器的客户请求,Servlet容器创建特定于这个请求的ServletRequest对象(此时创建的ServletRequest对象就包含客户请求信息)和ServletResponse对象,然后调用 Servlet 的 service方法。service方法从ServletRequest对象获得客户请求信息、处理该请求,并通过ServletResponse对象来生成响应结果。
4.3 Servlet的终止阶段
当Web应用被终止,或Servlet容器终止运行时,Servlet容器会先调用 Servlet的destroy方法,终止所有在这个容器中处于运行时状态的Servlet。在destroy方法中,可以释放Servlet所占用的资源(例如关闭文件输入流和输出流,关闭与数据库的连接等) 。
5. 创建Servlet
使用HTTP协议通信,创建用户自己的HttpServlet类的步骤:
(1) 扩展 HttpServlet 抽象类。
(2) 覆盖HttpServlet的doGet()或doPost()等方法。
(3) 获取HTTP 请求信息,例如通过HttpServletRequest 对象来检索 HTML 表单所提交的数据或 URL 上的查询字符串。无论是HTML表单数据还是URL 上的查询字符串,在HttpServletRequest 对象中都以参数名/参数值的形式存放,你可以通过getParameter(String name)方法检索参数信息。
(4) 生成 HTTP 响应结果。通过HttpServletResponse 对象可以生成响应结果。HttpServletResponse 对象有一个 getWriter()方法,该方法返回一个 PrintWriter 对象。使用 PrintWriter 的 print()或 println()方法可以向客户端输出字符串数据。
5.1 创建HelloServlet类
第一步: 扩展 HttpServlet 抽象类。
public class HelloServlet extends HttpServlet
第二步:覆盖doGet()方法(此处假定客户请求是以get方式)
public void doGet(HttpServletRequest request,HttpServletResponse response) throws IOException ,ServletException
第三步:获取HTTP 请求中的请求参数信息
//用户请求:http://localhost:8080/helloapp/hello?clientName=小芳
// 请求参数名 =请求参数值
String clientName=request.getParameter("clientName");
if(clientName!=null)
//字符编码转换
clientName=new String(clientName.getBytes("ISO-8859-1"),"GB2312");
else
clientName="我的朋友";
第四步:生成 HTTP响应结果
PrintWriter out;
String title="HelloServlet";
//设置响应正文的类型
response.setContentType("text/html;charset=GB2312");
//生成HTML正文
out = response.getWriter();
out.print("<HTML><HEAD><TITLE>"+title+"</TITLE>");
out.print("</HEAD><BODY>");
out.println("<h1><P> "+clientName+" : 您好</h1>");
out.print("</BODY></HTML>");
//关闭输出流
out.close();
5.2 发布HelloServlet类
HelloServlet的.class文件位于helloapp/WEB-INF/classes/mypack目录下。
在web.xml中为HelloServlet类加上如下和元素:
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>mypack.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
5.3 运行HelloServlet类
通过如下URL访问HelloServlet:
http://localhost:8080/helloapp/hello?clientName=小芳
6. ServletContext类的用法
6.1 ServletContext和Web应用关系
Servlet容器为java web应用提供的一个得力助手即ServletContext,下图说明ServletContext的作用:
1)当Servlet容器启动Web应用时,并为每个Web应用创建惟一的ServletContext对象。
2)在ServletContext中可以存放共享数据,它提供了读取或设置共享数据的方法:
setAttribute(String name,Object object)把一个对象和一个属性名绑定,将这个对象存储在ServletContext中。
getAttribute(String name)根据给定的属性名返回所绑定的对象
eg:
Apple o=new Apple( );
servletContext.setAttribute(“苹果",0); //存放共享数据
Apple o1=(Apple )servletContext.getAttribute(“苹果"); //读取共享数据
3)可以把ServletContext看成是一个Web应用的服务器端组件的共享内存。
一个Servlet类如何获得当前web应用的ServletContext对象:GenericServlet抽象类(所有Servlet类的父类)中有一个getServletContext()方法,就用于返回与当前web应用关联的ServletContext对象。
6.2 CounterServlet例
- 创建CounterServlet
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.util.*;
public class CounterServlet extends HttpServlet {
private static final String CONTENT_TYPE = "text/html";
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
//获得ServletContext的引用
ServletContext context = getServletContext();
// 从ServletContext读取count属性
Integer count = (Integer)context.getAttribute("count");
// 如果count属性还没有设置, 那么创建count属性,初始值为0
// one and add it to the ServletContext
if ( count == null ) {
count = Integer.valueOf(0);
context.setAttribute("count", count);
}
response.setContentType(CONTENT_TYPE);
PrintWriter out = response.getWriter();
out.println("<html>");
out.println("<head><title>WebCounter</title></head>");
out.println("<body>");
// 输出当前的count属性值
out.println("<p><h1>The current COUNT is : " + count + ".</h1></p>");
out.println("</body></html>");
// 创建新的count对象,其值增1
count =Integer.valueOf(count.intValue() + 1);
// 将新的count属性存储到ServletContext中
context.setAttribute("count", count);
}
public void destroy() {
}
}
- 测试CounterServlet:
(1)通过如下URL访问CounterServlet:
http://localhost:8080/helloapp/counter
当你第一次访问该Servlet,你会在浏览器上看到count值为0。
(2)刷新该页面,你会看到每刷新一次count值增加1,假定最后一次刷新后count值为5。
(3)再打开一个新的浏览器,访问CounterServlet。此时count值为6。
(4)重新启动Tomcat服务器,然后再访问CounterServlet,你会看到count值又被初始化为0。
7. 多线程运行Servlet的并发问题
Servlet容器允许多个客户同时访问同一个Servlet
1)Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。
2)由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的并发问题。
3)如果在编写Servlet/JSP程序时不注意到多线程并发的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,就会经常出现一些莫明其妙的问题,对于这类随机性的问题调试难度也很大。
7.1 并发问题
- 会导致并发问题的HelloServlet1
public class HelloServlet1 extends HttpServlet {
String clientName=null; //clientName为实例变量,实例变量属于类的成员变量
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException ,ServletException
{
clientName=request.getParameter("clientName");
if(clientName!=null)
clientName=new String(clientName.getBytes("ISO-8859-1"),"GB2312");
else
clientName="我的朋友";
System.out.println(Thread.currentThread().getName());
try{Thread.sleep(10000); }catch(Exception e){}
// 第四步:生成 HTTP 响应结果。
PrintWriter out;
……
out.println("<h1><P> "+clientName+" : 您好</h1>");
out.print("</BODY></HTML>");
//close out.
out.close();
}
}
通过两个浏览器同时访问HelloServlet1
两个线程同时操纵HelloServlet1对象的clientName实例变量
导致并发问题的操作时序
两个线程各自操纵HelloServlet对象的clientName局部变量
7.2 解决并发问题的方案
(1)根据实际应用需求,合理决定在Servlet中定义的变量的作用域,变量到底为实例变量,还是局部变量,是由实际应用需求决定的。
(2)对于多个线程同时访问共享数据而导致并发问题的场合,使用Java同步机制对线程进行同步。
synchronized{…}
8. 练习题
- 问题:HttpServletRequest对象是由谁创建的?
选项:
(A)由Servlet容器负责创建,对于每个要求访问HttpServlet的HTTP请求, Servlet容器都会创建一个HttpServletRequest对象
(B)由JavaWeb应用的Servlet或JSP组件负责创建,当Servlet或JSP组件响应HTTP请求时,先创建HttpServletRequest对象
答案: A - 问题:HttpServlet从HTTP请求中获得请求参数,应该调用哪个方法?
选项:
(A)调用HttpServletRequest对象的getAttribute()方法
(B)调用ServletContext对象的getAttribute()方法
©调用HttpServletRequest对象的getParameter()方法
答案: C - 问题:ServletContext对象是由谁创建的?
选项:
(A)由Servlet容器负责创建,对于每个HTTP请求, Servlet容器都会创建一个ServletContext对象
(B)由JavaWeb应用本身负责为自己创建一个ServletContext对象
©由Servlet容器负责创建,对于每个JavaWeb应用,在启动时,Servlet容器都会创建一个ServletContext对象
答案: C - 问题:当Servlet容器销毁一个Servlet时,会销毁哪些对象?(多选)
选项:
(A)Servlet对象
(B)与Servlet对象关联的ServletConfig对象
(C)ServletContext对象
(D)ServletRequest和ServletResponse对象
答案: A,B
(B)与Servlet对象关联的ServletConfig对象(当Servlet容器初始化一个Servlet时,会先创建一个包含了Servlet配置信息的ServletConfig对象,然后创建Servlet对象,Servlet与ServletConfig关联)
(C)ServletContext对象(是与整个java web应用关联的,只有当终止应用才会销毁与它关联ServletContext对象)
(D)ServletRequest和ServletResponse对象 (当响应结果到达客户端,这两个对象就会结束生命周期) - 分析ServletRequest、ServletResponse、Servlet、ServletContext等对象的生命周期,何时被创建,何时被销毁。
ServletRequest、ServletResponse:Servlet容器创建,当响应结果到达客户端,这两个对象就会结束生命周期
Servlet、ServletConfig:Servlet容器创建,当Servlet容器初始化一个Servlet时,会先创建一个包含了Servlet配置信息的 ServletConfig对象,然后创建Servlet对象,Servlet与ServletConfig关联
ServletContext:Servlet容器创建,与整个java web应用关联的,只有当终止应用才会销毁与它关联ServletContext对象