【愚公系列】2023年03月 Java教学课程 086-Servlet服务器生命周期和映射

2023-03-16 19:09:17 浏览数 (2)

文章目录

  • 一、Servlet服务器的相关细节
    • 1.1 Servlet使用细节
      • 1.1.1 Servlet的生命周期
      • 1.1.2 Servlet的线程安全
      • 1.1.3 Servlet的注意事项
        • 1)映射Servlet的细节
        • 2)多路径映射Servlet
        • 3)启动时创建Servlet
        • 4)默认Servlet

一、Servlet服务器的相关细节

1.1 Servlet使用细节

1.1.1 Servlet的生命周期

Servlet的生命周期由创建、初始化、服务请求和销毁四个阶段组成。创建阶段: Servlet实例被创建,它的init()方法被调用。初始化阶段: Servlet实例被初始化,它的init()方法被调用,它可以访问ServletConfig对象。服务请求阶段: Servlet的service() 方法被调用,以响应客户端的请求。销毁阶段: Servlet实例被销毁,它的destroy()方法被调用。

1.1.2 Servlet的线程安全

由于Servlet运用了单例模式,即整个应用中只有一个实例对象,所以Servlet是线程不安全的,因为Servlet容器会为每个请求创建一个新的线程来处理请求,多个线程可能同时访问同一个Servlet实例,因此需要在编写Servlet时注意线程安全问题。

相关实例如下:

代码语言:javascript复制
/*
    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.1.3 Servlet的注意事项

1)映射Servlet的细节

Servlet支持三种映射方式,以达到灵活配置的目的。

首先编写一个Servlet,代码如下:

代码语言:javascript复制
/**
 * 演示Servlet的映射方式
 * @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

代码语言:javascript复制
<!--/开头 通配符的方式-->
<servlet>
    <servlet-name>servletDemo05</servlet-name>
    <servlet-class>com.itheima.servlet.ServletDemo05</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>servletDemo05</servlet-name>
    <url-pattern>/servletDemo5/*</url-pattern>
</servlet-mapping>

第二种:/开头 通配符的方式

​ 此种方式,只要符合目录结构即可,不用考虑结尾是什么。

​ 例如:映射为:/servlet/*

​ 访问URL:http://localhost:8585/servlet/itheima

​ http://localhost:8585/servlet/itcast.do

​ 这两个URL都可以。因为用的*,表示/servlet/后面的内容是什么都可以。

代码语言:javascript复制
<servlet>
    <servlet-name>servletDemo05</servlet-name>
    <servlet-class>com.itheima.servlet.ServletDemo05</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>servletDemo05</servlet-name>
    <url-pattern>/servlet/*</url-pattern>
</servlet-mapping>

第三种:通配符 固定格式结尾

​ 此种方式,只要符合固定结尾格式即可,其前面的访问URI无须关心(注意协议,主机和端口必须正确)

​ 例如:映射为:*.do

​ 访问URL:http://localhost:8585/servlet/itcast.do

​ http://localhost:8585/itheima.do

​ 这两个URL都可以方法。因为都是以.do作为结尾,而前面用*号通配符配置的映射,所有无须关心。

代码语言:javascript复制
<servlet>
    <servlet-name>servletDemo05</servlet-name>
    <servlet-class>com.itheima.servlet.ServletDemo05</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>servletDemo05</servlet-name>
    <url-pattern>*.do</url-pattern>
</servlet-mapping>

通过测试我们发现,Servlet支持多种配置方式,但是由此也引出了一个问题,当有两个及以上的Servlet映射都符合请求URL时,由谁来响应呢?注意:HTTP协议的特征是一请求一响应的规则。那么有一个请求,必然有且只有一个响应。所以,我们接下来明确一下,多种映射规则的优先级。

先说结论:指名道姓的方式优先级最高,带有通配符的映射方式,有/的比没/的优先级高

所以,我们前面讲解的三种映射方式的优先级为:第一种>第二种>第三种。

演示代码如下:

代码语言:javascript复制
/**
 * 它和ServletDemo5组合演示Servlet的访问优先级问题
 * @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);
    }
}
代码语言:javascript复制
<!--配置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多路径映射是指在web.xml文件中配置多个URL路径,使得一个Servlet可以处理多个请求。例如,可以将多个URL映射到同一个Servlet,这样就可以实现URL的重定向和请求转发等功能。

首先,创建一个Servlet:

代码语言:javascript复制
/**
 * 演示Servlet的多路径映射
 * @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:

代码语言:javascript复制
<!--配置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是单例的,也就是说在应用中只有唯一的一个实例,所以在Tomcat启动加载应用的时候就创建也是一个很好的选择。那么两者有什么区别呢?

  • 第一种:应用加载时创建Servlet,它的优势是在服务器启动时,就把需要的对象都创建完成了,从而在使用的时候减少了创建对象的时间,提高了首次执行的效率。它的弊端也同样明显,因为在应用加载时就创建了Servlet对象,因此,导致内存中充斥着大量用不上的Servlet对象,造成了内存的浪费。
  • 第二种:请求第一次访问是创建Servlet,它的优势就是减少了对服务器内存的浪费,因为那些一直没有被访问过的Servlet对象都没有创建,因此也提高了服务器的启动时间。而它的弊端就是,如果有一些要在应用加载时就做的初始化操作,它都没法完成,从而要考虑其他技术实现。

通过上面的描述,可以得出何时采用第一种方式,何时采用第二种方式。就是当需要在应用加载就要完成一些工作时,就需要选择第一种方式。当有很多Servlet的使用时机并不确定是,就选择第二种方式。

在web.xml中是支持对Servlet的创建时机进行配置的,配置的方式如下:我们就以ServletDemo3为例。

代码语言:javascript复制
<!--配置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>
4)默认Servlet

默认Servlet是由服务器提供的一个Servlet,它配置在Tomcat的conf目录下的web.xml中。如下图所示:

它的映射路径是<url-pattern>/<url-pattern>,我们在发送请求时,首先会在我们应用中的web.xml中查找映射配置,找到就执行,这块没有问题。但是当找不到对应的Servlet路径时,就去找默认的Servlet,由默认Servlet处理。所以,一切都是Servlet。

0 人点赞