一、Servlet基础
1.运行在Servlet容器中的Java类,可接受请求并产生响应。
2.Servlet API
a.Servlet接口
b.ServletConfig接口
c.GenericServlet / HttpServlet抽象类
d.ServletContext接口
f.ServletRequest / HttpServletRequest接口
g.ServletResponse / HttpServletResponse接口
3.开发Servlet
a.继承HttpServlet
b.重写init()方法,doGet()方法,doPost()方法,destroy()方法
4.Servlet生命周期
a.加载和实例化
Servlet容器负责加载和实例化Servlet。当Servlet容器启动时,或者在容器检测到需要这个Servlet来响应第一个请求时,创建Servlet实例。当Servlet容器启动后,它必须要知道所需的Servlet类在什么位置,Servlet容器可以从本地文件系统、远程文件系统或者其他的网络服务中通过类加载器加载Servlet类,成功加载后,容器创建Servlet的实例。因为容器是通过Java的反射API来创建Servlet实例,调用的是Servlet的默认构造方法(即不带参数的构造方法),所以我们在编写Servlet类的时候,不应该提供带参数的构造方法。
b.初始化
(1)init()方法
(2)只执行一次
在Servlet实例化之后,容器将调用Servlet的init()方法初始化这个对象。初始化的目的是为了让Servlet对象在处理客户端请求前完成一些初始化的工作,如建立数据库的连接,获取配置信息等。对于每一个Servlet实例,init()方法只被调用一次。在初始化期间,Servlet实例可以使用容器为它准备的ServletConfig对象从Web应用程序的配置信息(在web.xml中配置)中获取初始化的参数信息。在初始化期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常来通知容器。ServletException异常用于指明一般的初始化失败,例如没有找到初始化参数;而UnavailableException异常用于通知容器该Servlet实例不可用。例如,数据库服务器没有启动,数据库连接无法建立,Servlet就可以抛出UnavailableException异常向容器指出它暂时或永久不可用。
c.服务
(1)service()方法
(2)反复执行
Servlet容器调用Servlet的service()方法对请求进行处理。要注意的是,在service()方法调用之前,init()方法必须成功执行。在service()方法中,Servlet实例通过ServletRequest对象得到客户端的相关信息和请求信息,在对请求进行处理后,调用ServletResponse对象的方法设置响应信息。在service()方法执行期间,如果发生错误,Servlet实例可以抛出ServletException异常或者UnavailableException异常。如果UnavailableException异常指示了该实例永久不可用,Servlet容器将调用实例的destroy()方法,释放该实例。此后对该实例的任何请求,都将收到容器发送的HTTP 404(请求的资源不可用)响应。如果UnavailableException异常指示了该实例暂时不可用,那么在暂时不可用的时间段内,对该实例的任何请求,都将收到容器发送的HTTP 503(服务器暂时忙,不能处理请求)响应。
d.销毁
(1)destroy()方法
(2)只执行一次
当容器检测到一个Servlet实例应该从服务中被移除的时候,容器就会调用实例的destroy()方法,以便让该实例可以释放它所使用的资源,保存数据到持久存储设备中。当需要释放内存或者容器关闭时,容器就会调用Servlet实例的destroy()方法。在destroy()方法调用之后,容器会释放这个Servlet实例,该实例随后会被Java的垃圾收集器所回收。如果再次需要这个Servlet处理请求,Servlet容器会创建一个新的Servlet实例。
以上是Servlet生命周期的几个阶段,下面我们来用图示说明Servlet生命周期
5.jsp本质上是一个Servlet,jsp适合表示层开发,Servlet适合封装控制逻辑
二、Servlet线程
Servlet线程不安全,Servlet是单实例多线程的,当多个线程同时访问同一个Servlet时,web
服务器会为每一个客户的访问请求创建一个线程,并且在这个线程上调用Servlet的service方法,
因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。
@WebServlet("/lizi")
public class lizi extends HttpServlet {
private static final long serialVersionUID = 1L;
public lizi() {
super();
// TODO Auto-generated constructor stub
}
private int count;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
for(int i=0;i<100;i++) {
try {
Thread.sleep(50);
count++;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("This is the No. " + count + " request" + ", Current Thread is :" + Thread.currentThread().getName());
}
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
this.doGet(request, response);
}
}
从运行结果来看,我们能看到有多个14请求运行,所以说明线程不安全。
This is the No. 1 request, Current Thread is :http-nio-8080-exec-1
This is the No. 2 request, Current Thread is :http-nio-8080-exec-2
This is the No. 3 request, Current Thread is :http-nio-8080-exec-3
This is the No. 5 request, Current Thread is :http-nio-8080-exec-4
This is the No. 6 request, Current Thread is :http-nio-8080-exec-5
This is the No. 6 request, Current Thread is :http-nio-8080-exec-6
This is the No. 9 request, Current Thread is :http-nio-8080-exec-7
This is the No. 9 request, Current Thread is :http-nio-8080-exec-8
This is the No. 10 request, Current Thread is :http-nio-8080-exec-9
This is the No. 12 request, Current Thread is :http-nio-8080-exec-10
This is the No. 14 request, Current Thread is :http-nio-8080-exec-1
This is the No. 14 request, Current Thread is :http-nio-8080-exec-2
三、那么如何解决线程安全问题呢?有以下几个方法:
1.实现SingleThreadModel接口
该接口指定了系统如何处理对同一个Servlet的调用。如果一个Servlet被这个接口指定,那么在
这个Servlet中的service方法将不会有两个线程被同时执行,当然也就不存在线程安全的问题。
2.同步对共享数据的操作
使用synchronized关键字,通过对象的锁机制保证同一时间只有一个线程访问变量。当然,这
个被用作“锁机制”的变量是多个线程共享的。提供一份变量,让不同的线程排队访问。
3.避免使用实例化变量
只要在Servlet里面的任何方法里面都不使用实例化变量,那么该Servlet就是线程安全的。
4.使用ThreadLocal模式
java.lang.ThreadLocal,提供了一种解决多线程并发问题的方案,该类在维护变量时,实际
使用了当前线程中的一个叫做ThreadLocalMap的独立副本,每个线程可以独立修改属于自己的副
本而不会互相影响,从而隔离了线程和线程,避免了线程访问实例变量发生冲突的问题。
ThreadLocal本身并不是一个变量,而是通过操作当前线程的一个内部变量来达到与其他线程隔离
的目的。