目录
一、什么是Servlet
Servlet是在服务端运行的Java程序,可以接受客户端请求并作出响应。
简单的来说 就是将一个静态页面变为动态页面
Servlet 通过一个定义良好的生命周期来进行管理,该生命周期规定了 Servlet 如何被加载、实例化、初始化、 处理客户端请求,以及何时结束服务(销毁)。
二、Servlet的生命周期
Servlet的生命周期有四个阶段:加载并实例化、初始化、请求处理、销毁。主要涉及到的方法有init
、service
、doGet
、doPost
、destory
等
1、加载并实例化
Servlet容器负责加载和实例化Servelt。当Servlet容器启动时,或者在容器检测到需要这个Servlet来响应第一个请求时,创建Servlet实例。当Servlet容器启动后,Servlet通过类加载器来加载Servlet类,加载完成后再new一个Servlet对象来完成实例化。
2、初始化(执行一次)
在Servlet实例化之后,容器将调用init()
方法,并传递实现ServletConfig接口的对象。在init()
方法中,Servlet可以部署描述符中读取配置参数,或者执行任何其他一次性活动。在Servlet的整个生命周期类,init()
方法只被调用一次。
@WebServlet("/BaseServlet")
public class BaseServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected TemplateEngine templateEngine; // 模板引擎
protected ServletContextTemplateResolver templateResolver; //模板解析器
@Override
public void init() throws ServletException {
super.init();
templateEngine = new TemplateEngine();
templateResolver = new ServletContextTemplateResolver(getServletContext());
//设置要应用于此解析器解析的模板的模板模式。
templateResolver.setTemplateMode("HTML5");
//非严格的HTML5
// templateResolver.setTemplateMode("LEGACYHTML5");
// 设置新的字符编码以读取模板资源。
templateResolver.setCharacterEncoding("utf-8");
// 设置要添加到所有模板名称的新(可选)前缀,以将模板名称转换为资源名称。
templateResolver.setPrefix("/");
//设置要添加到所有模板名称的新(可选)后缀,以将模板名称转换为资源名称。
templateResolver.setSuffix(".html");
//将模板缓存关闭,默认是true
templateResolver.setCacheable(false);
//给模板引擎设置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
}
3、请求处理(反复执行)
当Servlet初始化后,容器就可以准备处理客户机请求了。当容器收到对这一Servlet的请求,就调用Servlet的service()
方法,并把请求和响应对象作为参数传递。当并行的请求到来时,多个service()方法能够同时运行在独立的线程中。service()
方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用 doGet()
、doPost()等方法。
/**
* Servlet implementation class aaa
*/
@WebServlet("/aaa")
public class aaa extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
//无参构造
public aaa() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.getWriter().append("Served at: ").append(request.getContextPath());
写代码
处理乱码
获取资源
数据
响应用户
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
4、销毁(执行一次)
一旦Servlet容器检测到一个Servlet要被卸载,这可能是因为要回收资源或者因为它正在被关闭,容器会在所有Servlet的service()
线程之后,调用Servlet的destroy()
方法。然后,Servlet就可以进行无用存储单元收集清理。这样Servlet对象就被销毁了。这四个阶段共同决定了Servlet的生命周期。
该生命周期通过javax.servlet.Servlet
接口中的 init
、service
和 destroy
这些 API 来表示,所有 Servlet 必须直接或间接的实现HttpServlet
抽象类。
三、线程安全问题
Servlet容器通过维护一个线程池来处理多个请求,线程池中维护的是一组工作者线程(Worker Thread)。Servlet容器通过一个调度线程(Dispatcher Thread)来调度线程池中的线程。
当客户端的servlet请求到来时,调度线程会从线程池中选出一个工作者线程并将请求传递给该线程,该线程就会执行对应servlet实例的service方法。同样,当客户端发起另一个servlet请求时,调度线程会从线程池中选出另一个线程去执行servlet实例的service方法。Servlet容器并不关心这些线程访问的是同一个servlet还是不同的servlet,当多个线程访问同一个servlet时,该servlet实例的service方法将在多个线性中并发执行。
简单的来说就是在多个请求下访问的是同一个Servlet实例对象
所以,Servlet对象是单实例多线程,Servlet不是线程安全的
举个例子:
public class aaaServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
private String username1 = null;//实例变量
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException{
username1 = request.getParameter("username1");
String username2 = request.getParameter("username2");//局部变量
}
}
username1则是共享变量,多个线程会同时访问该变量,是线程不安全的。
username2是局部变量,不管多少个线程同时访问,都是线程安全的。