Servlet细节详解
对象的生命周期,就是对象从生到死的过程,即:出生——活着——死亡。用更偏向 于开发的官方说法就是对象创建到销毁的过程。
出生:请求第一次到达Servlet时,对象就创建出来,并且初始化成功。只出生一次,就放到内存中。
活着:服务器提供服务的整个过程中,该对象一直存在,每次只是执行service方法。
死亡:当服务停止时,或者服务器宕机时,对象消亡。
通过分析Servlet的生命周期我们发现,它的实例化和初始化只会在请求第一次到达Servlet时执行,而销毁只会在Tomcat服务器停止时执行,由此我们得出一个结论,Servlet对象只会创建一次,销毁一次。所以,Servlet对象只有一个实例。如果一个对象实例在应用中是唯一的存在,那么我们就说它是单实例的,即运用了单例模式。
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中定义类成员要慎重。如果类成员是共用的,并且只会在初始化时赋值,其余时间都是获取的话,那么是没问题。如果类成员并非共用,或者每次使用都有可能对其赋值,那么就要考虑线程安全问题了,把它定义到doGet或者doPost方法里面去就可以了。
Servlet的注意事项
1)映射Servlet的细节
Servlet支持三种映射方式,以达到灵活配置的目的。
首先编写一个Servlet,代码如下:
/** * 演示Servlet的映射方式 * @author * @Company http://www.itheima.com */ 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
访问URL:http://localhost:8585/servlet_demo/servletDemo5
第二种:/开头+通配符的方式
例如:映射为:/servlet/*
访问URL:http://localhost:8585/servlet/itheima
http://localhost:8585/servlet/itcast.do
这两个URL都可以。因为用的*,表示/servlet/后面的内容是什么都可以。
第三种:通配符+固定格式结尾
此种方式,只要符合固定结尾格式即可,其前面的访问URI无须关心(注意协议,主机和端口必须正确)
例如:映射为:*.do
访问URL:http://localhost:8585/servlet/itcast.do
http://localhost:8585/itheima.do
这两个URL都可以方法。因为都是以.do作为结尾,而前面用*号通配符配置的映射,所有无须关心。
通过测试我们发现,Servlet支持多种配置方式,但是由此也引出了一个问题,当有两个及以上的Servlet映射都符合请求URL时,由谁来响应呢?注意:HTTP协议的特征是一请求一响应的规则。那么有一个请求,必然有且只有一个响应。所以,我们接下来明确一下,多种映射规则的优先级。
先说结论:指名道姓的方式优先级最高,带有通配符的映射方式,有/的比没/的优先级高
所以,我们前面讲解的三种映射方式的优先级为:第一种>第二种>第三种。
演示代码如下:
/** * 它和ServletDemo5组合演示Servlet的访问优先级问题 * @author * @Company http://www.itheima.com */ public class ServletDemo6 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("ServletDemo6接收到了请求"); } /** * 调用doGet方法 * @param req * @param resp * @throws ServletException * @throws IOException */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
<!--配置ServletDemo6--> <servlet> <servlet-name>servletDemo6</servlet-name> <servlet-class>com.itheima.web.servlet.ServletDemo6</servlet-class> </servlet> <servlet-mapping> <servlet-name>servletDemo6</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping>
运行结果如下:
2)多路径映射Servlet
上一小节我们讲解了Servlet的多种映射方式,这一小节我们来介绍一下,一个Servlet的多种路径配置的支持。
它其实就是给一个Servlet配置多个访问映射,从而可以根据不同请求URL实现不同的功能。
/** * 演示Servlet的多路径映射 * @author * @Company http://www.itheima.com */ public class ServletDemo7 extends HttpServlet { /** * 根据不同的请求URL,做不同的处理规则 * @param req * @param resp * @throws ServletException * @throws IOException */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //1.获取当前请求的URI String uri = req.getRequestURI(); uri = uri.substring(uri.lastIndexOf("/"),uri.length()); //2.判断是1号请求还是2号请求 if("/servletDemo7".equals(uri)){ System.out.println("ServletDemo7执行1号请求的业务逻辑:商品单价7折显示"); }else if("/demo7".equals(uri)){ System.out.println("ServletDemo7执行2号请求的业务逻辑:商品单价8折显示"); }else { System.out.println("ServletDemo7执行基本业务逻辑:商品单价原价显示"); } } /** * 调用doGet方法 * @param req * @param resp * @throws ServletException * @throws IOException */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req,resp); } }
接下来,在web.xml配置Servlet:
<!--配置ServletDemo7--> <servlet> <servlet-name>servletDemo7</servlet-name> <servlet-class>com.itheima.web.servlet.ServletDemo7</servlet-class> </servlet> <!--映射路径1--> <servlet-mapping> <servlet-name>servletDemo7</servlet-name> <url-pattern>/demo7</url-pattern> </servlet-mapping> <!--映射路径2--> <servlet-mapping> <servlet-name>servletDemo7</servlet-name> <url-pattern>/servletDemo7</url-pattern> </servlet-mapping> <!--映射路径3--> <servlet-mapping> <servlet-name>servletDemo7</servlet-name> <url-pattern>/servlet/*</url-pattern> </servlet-mapping>
最后,启动服务测试运行结果:
3)启动时创建Servlet
我们前面讲解了Servlet的生命周期,Servlet的创建默认情况下是请求第一次到达Servlet时创建的。但是我们都知道,Servlet是单例的,也就是说在应用中只有唯一的一个实例,所以在Tomcat启动加载应用的时候就创建也是一个很好的选择。那么两者有什么区别呢?
-
第一种:应用加载时创建Servlet,它的优势是在服务器启动时,就把需要的对象都创建完成了,从而在使用的时候减少了创建对象的时间,提高了首次执行的效率。它的弊端也同样明显,因为在应用加载时就创建了Servlet对象,因此,导致内存中充斥着大量用不上的Servlet对象,造成了内存的浪费。
-
第二种:请求第一次访问是创建Servlet,它的优势就是减少了对服务器内存的浪费,因为那些一直没有被访问过的Servlet对象都没有创建,因此也提高了服务器的启动时间。而它的弊端就是,如果有一些要在应用加载时就做的初始化操作,它都没法完成,从而要考虑其他技术实现。
通过上面的描述,相信同学们都能分析得出何时采用第一种方式,何时采用第二种方式。就是当需要在应用加载就要完成一些工作时,就需要选择第一种方式。当有很多Servlet的使用时机并不确定是,就选择第二种方式。
在web.xml中是支持对Servlet的创建时机进行配置的,配置的方式如下:我们就以ServletDemo3为例。
<!--配置ServletDemo3--> <servlet> <servlet-name>servletDemo3</servlet-name> <servlet-class>com.itheima.web.servlet.ServletDemo3</servlet-class> <!--配置Servlet的创建顺序,当配置此标签时,Servlet就会改为应用加载时创建 配置项的取值只能是正整数(包括0),数值越小,表明创建的优先级越高 --> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>servletDemo3</servlet-name> <url-pattern>/servletDemo3</url-pattern> </servlet-mapping>
默认Servlet是由服务器提供的一个Servlet,它配置在Tomcat的conf目录下的web.xml中。如下图所示: