1.1 Servlet概述
Servlet是SUN公司提供的一套规范,名称就叫Servlet规范,它也是JavaEE规范之一。
第一:Servlet是一个运行在web服务端的java小程序
第二:它可以用于接收和响应客户端的请求
第三:要想实现Servlet功能,可以实现Servlet接口,继承GenericServlet或者HttpServlet
第四:每次请求都会执行service方法
第五:Servlet还支持配置
1.2 Servlet入门
1.2.1 Servlet编码步骤
1)编码步骤
第一步:前期准备-创建JavaWeb工程
第二步:编写一个普通类继承GenericServlet并重写service方法
第三步:在web.xml配置Servlet
2)测试
在Tomcat中部署项目
在浏览器访问Servlet
1.2.2 Servlet执行过程分析
我们通过浏览器发送请求,请求首先到达Tomcat服务器,由服务器解析请求URL,然后在部署的应用列表中找到我们的应用。接下来,在我们的应用中找应用里的web.xml配置文件,在web.xml中找到FirstServlet的配置,找到后执行service方法,最后由FirstServlet响应客户浏览器。
1.2.3 Servlet编写方式
我们在实现Servlet功能时,可以选择以下三种方式:
第一种:实现Servlet接口,接口中的方法必须全部实现。
使用此种方式,表示接口中的所有方法在需求方面都有重写的必要。此种方式支持最大程度的自定义。
第二种:继承GenericServlet,service方法必须重写,其他方可根据需求,选择性重写。
使用此种方式,表示只在接收和响应客户端请求这方面有重写的需求,而其他方法可根据实际需求选择性重写,使我们的开发Servlet变得简单。但是,此种方式是和HTTP协议无关的。
第三种:继承HttpServlet,它是javax.servlet.http包下的一个抽象类,是GenericServlet的子类。如果我们选择继承HttpServlet时,只需要重写doGet和doPost方法,不要覆盖service方法。
使用此种方式,表示我们的请求和响应需要和HTTP协议相关。也就是说,我们是通过HTTP协议来访问的。那么每次请求和响应都符合HTTP协议的规范。请求的方式就是HTTP协议所支持的方式(目前我们只知道GET和POST,而实际HTTP协议支持7种请求方式,GET POST PUT DELETE TRACE OPTIONS HEAD )。
1.3 Servlet使用细节
1.3.1 Servlet的生命周期
对象的生命周期,就是对象从生到死的过程,即:出生——活着——死亡。用更偏向 于开发的官方说法就是对象创建到销毁的过程。
出生:请求第一次到达Servlet时,对象就创建出来,并且初始化成功。只出生一次,就放到内存中。
活着:服务器提供服务的整个过程中,该对象一直存在,每次只是执行service方法。
死亡:当服务停止时,或者服务器宕机时,对象消亡。
通过分析Servlet的生命周期我们发现,它的实例化和初始化只会在请求第一次到达Servlet时执行,而销毁只会在Tomcat服务器停止时执行,由此我们得出一个结论,Servlet对象只会创建一次,销毁一次。所以,Servlet对象只有一个实例。如果一个对象实例在应用中是唯一的存在,那么我们就说它是单实例的,即运用了单例模式。
1.3.2 Servlet的线程安全
由于Servlet运用了单例模式,即整个应用中只有一个实例对象,所以我们需要分析这个唯一的实例中的类成员是否线程安全。接下来,我们来看下面的的示例:
/*
Servlet线程安全
*/
public class ServletDemo04 extends HttpServlet{
//1.定义用户名成员变量
//private String username = null;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = null;
//synchronized (this) {
//2.获取用户名
username = req.getParameter("username");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.获取输出流对象
PrintWriter pw = resp.getWriter();
//4.响应给客户端浏览器
pw.print("welcome:" + username);
//5.关流
pw.close();
//}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
启动两个浏览器,输入不同的参数,访问之后发现输出的结果都是一样,所以出现线程安全问题
通过上面的测试我们发现,在Servlet中定义了类成员之后,多个浏览器都会共享类成员的数据。其实每一个浏览器端发送请求,就代表是一个线程,那么多个浏览器就是多个线程,所以测试的结果说明了多个线程会共享Servlet类成员中的数据,其中任何一个线程修改了数据,都会影响其他线程。因此,我们可以认为Servlet它不是线程安全的。
分析产生这个问题的根本原因,其实就是因为Servlet是单例,单例对象的类成员只会随类实例化时初始化一次,之后的操作都是改变,而不会重新初始化。
解决这个问题也非常简单,就是在Servlet中定义类成员要慎重。如果类成员是共用的,并且只会在初始化时赋值,其余时间都是获取的话,那么是没问题。如果类成员并非共用,或者每次使用都有可能对其赋值,那么就要考虑线程安全问题了,把它定义到doGet或者doPost方法里面去就可以了。
1.3.3 Servlet的注意事项
1)映射Servlet的细节
Servlet支持三种映射方式,以达到灵活配置的目的。
首先编写一个Servlet,代码如下
public class ServletDemo5 extends HttpServlet {
/**
* doGet方法输出一句话
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("ServletDemo5接收到了请求");
}
/**
* 调用doGet方法
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req,resp);
}
}
第一种:指名道姓的方式
此种方式,只有和映射配置一模一样时,Servlet才会接收和响应来自客户端的请求。
例如:映射为:/servletDemo5
第二种:/开头+通配符的方式
此种方式,只要符合目录结构即可,不用考虑结尾是什么。
例如:映射为:/servlet/*
第三种:通配符+固定格式结尾
此种方式,只要符合固定结尾格式即可,其前面的访问URI无须关心(注意协议,主机和端口必须正确)
例如:映射为:*.do
指名道姓的方式优先级最高,带有通配符的映射方式,有/的比没/的优先级高
所以,我们前面讲解的三种映射方式的优先级为:第一种>第二种>第三种。
2)启动时创建Servlet
我们前面讲解了Servlet的生命周期,Servlet的创建默认情况下是请求第一次到达Servlet时创建的。但是我们都知道,Servlet是单例的,也就是说在应用中只有唯一的一个实例,所以在Tomcat启动加载应用的时候就创建也是一个很好的选择。那么两者有什么区别呢?
-
第一种:应用加载时创建Servlet,它的优势是在服务器启动时,就把需要的对象都创建完成了,从而在使用的时候减少了创建对象的时间,提高了首次执行的效率。它的弊端也同样明显,因为在应用加载时就创建了Servlet对象,因此,导致内存中充斥着大量用不上的Servlet对象,造成了内存的浪费。
-
第二种:请求第一次访问是创建Servlet,它的优势就是减少了对服务器内存的浪费,因为那些一直没有被访问过的Servlet对象都没有创建,因此也提高了服务器的启动时间。而它的弊端就是,如果有一些要在应用加载时就做的初始化操作,它都没法完成,从而要考虑其他技术实现。