一、Servlet 概述

1.1、什么是 Servlet?

  • Servlet 是 JavaWeb 的三大组件之一,它属于动态资源,Servlet 的作用是处理请求,服务器会把接收到的请求给 Servlet 处理,在 Servlet 中需要:
    • 接收请求数据
    • 处理请求
    • 完成响应

每个 Servlet 是唯一的,它们处理的请求时不同的。

二、实现 Servlet 的方式

实现 Servlet 有三种方式:

  • 实现 java.servlet.Servlet 接口
  • 继承 javax.servlet.GenericServlet 类
  • 继承 javax.servletHttpServlet 类

2.1 实现 java.servlet.Servlet 接口

  1. import javax.servlet.*;
  2. import java.io.IOException;
  3. /**
  4. * Servlet 中的方法大多由 Tomcat 调用,Servlet 的对象也是有 Tomcat 生成
  5. *
  6. * */
  7. public class Aservlet implements Servlet {
  8. /**
  9. * 它是生命周期方法
  10. * 它会在 Servlet 对象创建之后马上执行,并执行一次(出生之后)
  11. * */
  12. @Override
  13. public void init(ServletConfig servletConfig) throws ServletException {
  14. System.out.println("init");
  15. }
  16. /**
  17. * 可以获取 Servlet 的配置信息
  18. * */
  19. @Override
  20. public ServletConfig getServletConfig() {
  21. System.out.println("getServletConfig");
  22. return null;
  23. }
  24. /**
  25. * 它是声明周期方法
  26. * 它会被调用多次,每次请求都是调用这个方法
  27. * */
  28. @Override
  29. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  30. System.out.println("service");
  31. }
  32. /**
  33. * 获取 Servlet 的信息
  34. * */
  35. @Override
  36. public String getServletInfo() {
  37. System.out.println("getServletInfo");
  38. return "Hello World";
  39. }
  40. /**
  41. * 它是声明周期方法
  42. * 它会在 Servlet 被销毁之前调用,并且只会调用一次
  43. * */
  44. @Override
  45. public void destroy() {
  46. System.out.println("destroy");
  47. }
  48. }

2.2 继承 javax.servlet.GenericServlet 类

查看源码,我们发现 GenericServlet 是实现了 Servlet,ServletConfig, Serializable 的接口
image.png

  1. import javax.servlet.*;
  2. import java.io.IOException;
  3. public class Bservlet extends GenericServlet {
  4. @Override
  5. public void init(ServletConfig servletConfig) throws ServletException {
  6. }
  7. @Override
  8. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  9. // 编写处理的方法
  10. }
  11. @Override
  12. public void destroy() {
  13. System.out.println("destroy");
  14. }
  15. }

2.3 继承 javax.servletHttpServlet 类

查看源码,我们发现 HttpServlet 是继承了 GenericServlet 类。
image.png

所以我们一般在 JavaEE 项目中创建的 Servlet 都是直接继承于 HttpServlet

  1. import javax.servlet.ServletException;
  2. import javax.servlet.ServletRequest;
  3. import javax.servlet.ServletResponse;
  4. import javax.servlet.annotation.WebServlet;
  5. import javax.servlet.http.HttpServlet;
  6. import javax.servlet.http.HttpServletRequest;
  7. import javax.servlet.http.HttpServletResponse;
  8. import java.io.IOException;
  9. import java.io.PrintWriter;
  10. @WebServlet(name = "TestServlet")
  11. public class TestServlet extends HttpServlet {
  12. // 监听 post 请求
  13. protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  14. }
  15. // 监听 get 请求
  16. protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  17. PrintWriter writer = response.getWriter();
  18. writer.println("Hello World");
  19. writer.close();
  20. }
  21. // 参数已经是 Http协议相关的,使用起来更方便
  22. /**
  23. * 通过 request 得到当前请求的请求方式,例如 GET 和 POST
  24. * 根据请求方式调用 doGet() 或 doPost()
  25. *
  26. * */
  27. @Override
  28. protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  29. super.service(request, response);
  30. }
  31. // 强制两个参数为 http 协议相关的类型
  32. // 调用本类的 service(HttpServletRequest, HttpServletResponse)
  33. @Override
  34. public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
  35. super.service(req, res);
  36. }
  37. }

servlet 执行过程
image.png

三、Servlet 生命周期

这里是基于第一种实现 Servlet 的方式来编写的

3.1 如何让游览器访问 Servlet?

  1. 给Servlet指定一 个Servlet路径! (让Servlet与一 个路径绑定在一起)
  2. 游览器访问Servlet路径

3.2 给 Servlet 配置 Servlet 路径(这里在 web.xml 中配置)

这个 Servlet 一定要是实现了 Servlet 接口

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  5. version="4.0">
  6. <servlet>
  7. <servlet-name>xxx</servlet-name> <!-- Servlet 别名 -->
  8. <servlet-class>cn.ajax.study.Aservlet</servlet-class> <!-- Servlet 的路径 -->
  9. </servlet>
  10. <servlet-mapping>
  11. <servlet-name>xxx</servlet-name> <!-- 和上面一致 -->
  12. <url-pattern>/Aservlet</url-pattern> <!-- 路径第一个必须为斜杠 / -->
  13. </servlet-mapping>
  14. </web-app>

3.3 部署 Tomcat,并运行

image.png

3.4 访问游览器

我们发现没有 404, 显示成功。
image.png

然后在控制台看到了两个方法执行了!! 然后我们多刷新几次网页,可以看到 service 被执行了。
image.png

然后关闭 Tomcat 服务器,我们发现 destory 执行了
image.png

3.5 结论(Servlet 生命周期方法)

生命周期方法

  • void init(ServletConfig servletConfig); 执行之后(1次)
  • void service(ServletRequest servletRequest, ServletResponse servletResponse); (每次处理请求都会调用)
  • void destroy(); 临死之前(1次)

特性

  1. servlet 是单例的,一个类只有一个对象,当然可能存在多个 Servlet 类
  2. 线程不安全的,所以效率是最高的
  3. Servlet 由我们写,对象由服务器创建,并由服务器调用相应的方法

    四、Servlet 细节

  4. 不要创建 Servlet 成员变量,创建局部变量

  5. 可以创建无状态成员
  6. 可以创建有状态成员,但状态必须只读

    4.1 Servlet 与线程安全

  • 因为一个类型的 Servlet 只有一个实例对象 ,那么就有可能会现时出-个servlet,同时处理多个请求,那么Servlet是否为线程安全的呢?

    答案是:“不是线程安全的”。这说明Servlet的工作效率很高,

  • 但也存在线程安全问题 !

    所以我们不应该在Servlet 中便宜创建成员变量,因为可能会存在一个线程 对这个成员变量进行 写操作,另一个线程对这个成员变量进行读操作。。

4.2 在服务器启动时创建 Servlet

  • 服务器一般在某个 Servlet 第一次收到请求时创建它
  • 也可以在 web.xml 中进行配置,使服务器启动时就创建 Servlet
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>xxx</servlet-name>
        <servlet-class>cn.ajax.study.Aservlet</servlet-class>
        <load-on-startup>0</load-on-startup>
        <!-- 这个数字代表创建的顺序  -->
    </servlet>
    <servlet-mapping>
        <servlet-name>xxx</servlet-name>
        <url-pattern>/Aservlet</url-pattern>
    </servlet-mapping>

    <servlet>
        <servlet-name>yyy</servlet-name>
        <servlet-class>cn.ajax.study.Bservlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>yyy</servlet-name>
        <url-pattern>/Bservlet</url-pattern>
    </servlet-mapping>
</web-app>

4.3 url-pattern

的子元素,用来指定 Servlet 的访问路径,即 URL ,它必须是以 ‘/‘ 开头,我们可以给出多个 url-pattern,这样就说明绑定了两个 URL

    <servlet-mapping>
        <servlet-name>yyy</servlet-name>
        <url-pattern>/Bservlet</url-pattern>
       <url-pattern>/Bservlet1</url-pattern>
    </servlet-mapping>

还可以在中使用通配符,所谓通配符就是星号“*”,星号可以匹配任何URL前
缀或后缀,使用通配符可以命名一个 Servlet绑定一组URL,例如:

  • /senvlet/: /servlet/a、 /servlet/b, 都匹配/servlet/; 。
  • .do: /abc/def/ghi.do、 /a.do, 都匹配.do; +
  • /*:匹配所有URL;

注意:

  1. 通配符要么为前缀,要么为后缀
  2. 不能出现在中间位置
  3. 也不能只有 通配符

    五、Servlet 补充

    5.1 ServletConfig介绍

    5.1.1 功能介绍

    在 web.xml 中,我会填写如下信息
     <servlet>
         <servlet-name>xxx</servlet-name> <!-- Servlet 别名   -->
         <servlet-class>cn.ajax.study.Aservlet</servlet-class> <!-- Servlet 的路径   -->
     </servlet>
    

ServletConfig 的作用就是读取 web.xml 这段内容到内存中。1个 ServletConfig对象,对应一般 web.xml 中 Servlet 配置信息!

5.1.2 内置 API

API 功能
String getServletName() 获取 中的内容
ServletContext getServletContext() 获取 Servlet 上下文对象
String getInitParameter(String name) 通过名称获取指定初始化参数的值
Enumeration getInitParameterNames0 获取所有初始化参数的名称

5.1.3 demo1

在 web.xml 中设置如下参数

    <servlet>
        <servlet-name>xxx</servlet-name>
        <servlet-class>cn.ajax.study.Aservlet</servlet-class>
        <init-param>
            <param-name>p1</param-name>
            <param-value>v1</param-value>
        </init-param>
        <init-param>
            <param-name>p2</param-name>
            <param-value>v2</param-value>
        </init-param>
    </servlet>

然后在 servlet 中获取参数

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        System.out.println("init");

//        获取初始化参数
        System.out.println(servletConfig.getServletName()); //  xxx
        System.out.println(servletConfig.getInitParameter("p1")); //v1
        System.out.println(servletConfig.getInitParameter("p2")); //v2
        // 获取素有初始化参数的名称
        Enumeration e = servletConfig.getInitParameterNames();

        while (e.hasMoreElements()) {
            System.out.println(e.nextElement());
        }
    }

然后访问 Servlet, xml 中的值都显示出来了
image.png

5.2 ServletContext (重点)

  • 一个项目只有一个 ServletContext 对象(application)
  • 我们可以在 N 多个 servlet 中获取这个唯一对象,使用它可以给多个 servlet 传递数据!
  • 这个对象在 Tomcat 启动时创建,在 tomcat 关闭时才死去

    5.2.1 ServletContext 概述

    服务器为每一个应用创建 ServletContext 对象;

  • ServletContext 对象的创建是在服务器启动时完成的

  • ServletContext 对象的销毁是在服务器关闭时完成的

SevletContext.对象的作用是在整个Web应用的动态资源之间共享数据! 例如在AServlet中向
ServletContext对象中保存一个值, 然后在BSerlet中就可以获取这个值,这就是共享数据了。

5.2.2 获取 ServletContext

  1. ServletConfig getServletContext();
  2. GenericServlet getServletContext();
  3. HttpSession getSerletContext();
  4. ServletContextEvent getServletContext();
  • 在Servlet中获取ServletContext对象:
    • 在void init(ServletConfig config)中 : ServletContext context = config.getServetContext(); ServletConfig,类的getServletContext()方法可以用来获取ServletContext对象,
    • 在Genericervlet或HittpServlet中获取servletContext对象:GenericServlet类有getServletContext()方法, 所以可以直接使用this.getSservletContext()来获取;

5.2.3 域对象的功能

域对象的作用是在多个 Servlet 中传递数据 (不完整表达)

域对象必须要要有 存对象 和 取对象 的功能

5.2.4 四大域对象

ServletContext 是 JavaWeb 四大域对象之一

  • PageContent()
  • ServletRequest()
  • HTTPSession()
  • ServletContext()

所有域都具有存取数据的功能,因为域对象内部有一个 Map,用来存储数据。接下来介绍 ServletContext 对象用来操作数据的方法

5.2.5 域对象演示(ServletContext() )

接下来就演示一下 设置属性以及获取属性

  1. 我们编写一个 TsetServlet1 ```java import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException;

// 这里使用了注解的方式,可以直接通过注解访问路由 @WebServlet(“/TestServlet1”) public class TestServlet1 extends HttpServlet {

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    /**
     * 1. 获取 ServletContext 对象
     * 2. 调用其 setAttribute() 方法完成存储数据
     * */
    ServletContext application = this.getServletContext();
    application.setAttribute("a", "123");
}

}


2. 然后编写 UseServlet1
```java
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@WebServlet("/UseServlet1")
public class UseServlet1 extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        /**
         * 1. 获取 ServletContext 对象
         * 2. 调用其 getAttribute() 方法完成存储数据
         * */

        ServletContext application = this.getServletContext();
        String a = (String)application.getAttribute("a");
        System.out.println(a);
    }
}
  1. 然后打开游览器,分别访问

    1. http://localhost:8080/MyWork/TestServlet1
    2. http://localhost:8080/MyWork/UsetServlet1
  2. 然后我们就在控制台看到了打印的值

image.png

5.2.6 获取公共的初始化参数

  1. 在 web.xml 中加入如下内容
    <context-param>
        <param-name>context-name</param-name>
        <param-value>context-value</param-value>
    </context-param>
  1. 在 Servlet 中获取初始化的参数
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * 演示 ServletContext 获取公共的初始化参数
 * */
@WebServlet("/TestServlet3")
public class TestServlet3 extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 得到 ServletContext
        // 2. 得到初始化的参数: getInitParameter
        ServletContext app = this.getServletContext();
        String val = (String)app.getInitParameter("context-name");

        // 获取输入流,将结果打印到游览器中
        PrintWriter writer = response.getWriter();
        writer.write("<h1>"+val+"</h1>");
        writer.close();
    }
}
  1. 结果正常显示

image.png

5.2.7 ServletContext 获取资源相关方法

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;

// 使用 ServletContext 获取资源路径
@WebServlet("/TestServlet4")
public class TestServlet4 extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 得到的时候又盘符的路径(绝对路径)
        String path = this.getServletContext().getRealPath("/index.jsp");
        System.out.println(path);

        // 获取资源路径后,再创建输入流对象
        InputStream input = this.getServletContext().getResourceAsStream("/index.jsp");

        // 获取当前路径下所有资源的路径
       Set<String> paths =  this.getServletContext().getResourcePaths("/WEB-INF");
       System.out.println(paths);
    }
}

image.png

六、练习

6.1 网站访问量统计

只要服务器不关,这个就可以一直显示

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.ws.Response;
import java.io.IOException;
import java.io.PrintWriter;

// 统计网站人数
@WebServlet("/CountServlet")
public class CountServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       response.setCharacterEncoding("gbk");
        /**
         * 1. 获取 ServletContext 对象
         * 2. 从 ServletConte对象中获取名为 count 的属性
         * 3. 如果存在,访问量 +1,然后保存回去
         * 4. 如果不存在,说明是第一次访问,问 ServletContext中保存名为 count 的属性,值为1
         * */
        ServletContext app = this.getServletContext();
        Integer count = (Integer)app.getAttribute("count");

        if (count == null) {
            app.setAttribute("count",1);
        } else  {
            app.setAttribute("count",count+1);
        }

        // 向游览器输出,需要响应对象
        PrintWriter writer = response.getWriter();
        writer.print("<h1>你是第"+count + "个人</h1>");
    }
}

六、BaseServlet

  1. 解决一个问题:一个 Servlet 中可以有多个请求处理方法
  2. 客户端多给一个参数,这个参数表示要执行的方法
  3. 请求处理方法的签名,必须与 service 相同,即返回值,参数,都相同
  4. 客户端必须传 名为 method 的参数

BaseServlet

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * 多个请求的处理方法
 * 请求处理方法:除了名字以外,都与 service 方法一致
 * */
@WebServlet("/AServlet")
public class AServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.service(req, resp);
        /**
         * 1. 获取参数,用来识别用户想请求的方法
         * 2. 然后判断是哪一个方法
         * */
        String methodName = req.getParameter("method");
//        if (methodName.equals("addUser")) {
//            this.addUser(req, resp);
//        } else if (methodName.equals("editUser")) {
//            this.editUser(req, resp);
//        } else if (methodName.equals("deleteUser")) {
//            this.deleteUser(req, resp);
//        }

        if (methodName == null || methodName.trim().isEmpty()) {
            throw new RuntimeException("你没有传递 method 参数,无法确定您要调用的方法");
        }

        /**
         * 但是如果有需求,要增加一个方法,那就得加一个 if 判断
         * 得到方法名称,通过反射调用方法
         * 1. 得到方法名,通过方法名在得到 Method 类的对象
         *  需要得到 Class,然后调用它的方法进行查询,得到 method
         *  我们要查询的是当前类的方法,所以我们需要得到当前类的 Class
         * */

        Class c = this.getClass(); // 得到当前类的 class 对象
        Method method = null;
        try {
            method = c.getMethod(methodName, HttpServletRequest.class, HttpServletResponse.class);
        } catch (Exception e) {
            throw new RuntimeException("您要调用的方法:"+methodName +",他不存在!");
        }

        /**
         * 调用 method 表示的方法
         * */
        try {
            method.invoke(this,req,resp); // this.addUser(req,resp), method(this.req, resp);
        } catch (Exception e) {
            System.out.println("您调用的方法:"+methodName +",它内部抛出了异常");
            throw new RuntimeException(e);
        }

    }

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }

    public void editUser(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("添加用户");
    }

    public void addUser(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("添加用户");
    }

    public void deleteUser(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("删除用户");
    }
}

image.png

image.png

我们传入的参数也执行了对应的方法。