Servlet

28 阅读15分钟

静态资源 / 动态资源

  • 静态资源:无需在程序运行时通过代码运行生成的资源,在程序运行之前就写好的资源。例如:html、css、js、img,音频文件和视频文件;
  • 动态资源:- 需要在程序运行时通过代码运行生成的资源,在程序运行之前无法确定的数据,运行时动态生成,例如 Servlet、Thymeleaf 等;

Servlet简介

用来接收、处理客户端请求、响应给浏览器的动态资源。在整个 Web 应用中,Servlet 主要负责处理请求、协同调度功能以及响应数据。可以把 Servlet 称为 Web 应用中的控制器image.png

  1. tomcat 接收到请求后,会将请求报文的信息转换为一个HttpServletRequest对象,该对象中包含了请求中的所有信息:请求行、请求头、请求体。
  2. tomcat 同时创建了一个HttpServletResponse对象,该对象用于承载要响应给客户端的信息,后面,该对象会被转换成响应的报文:响应行、响应头、响应体。
  3. tomcat 根据请求中的资源路径找到对应的 servlet,将 servlet 实例化,调用service方法,同时将HttpServletRequestHttpServletResponse对象传入。

Servlet开发流程

  1. 创建一个 JavaWeb 项目
  2. 开发一个 UserServlet
public class UserServlet extends HttpServlet { 
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    // 获取请求中的键值对参数 key=value 
    String username = req.getParameter("username"); 
    if ("atguigu".equals(username)) { 
        // 通过响应对象响应信息 
        resp.getWriter().write("NO"); 
    } else { 
        resp.getWriter().write("YES"); 
    } 
  } 
}
自定义一个类,要继承`HttpServlet`类; 
重写`service`方法,该方法主要就是用于处理用户请求的服务方法; 
`HttpServletRequest`代表请求对象,是由请求报文经过Tomcat转换而来的,通过该对象可以获取请求中的信息; 
`HttpServletResponse`代表响应对象,该对象会被Tomcat转换为响应的报文,通过该对象可以设置响应中的信息; 
Servlet对象的生命周期(创建、初始化、处理服务、销毁)是由Tomcat管理的,无需自己`new`; 
`HttpServletRequest`、`HttpServletResponse`两个对象也是由Tomcat负责转换,在调用`service`方法时传入给我们用的;
  1. 在 web.xml 为 UseServlet 配置请求的映射路径:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"   
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <servlet>
        <!--给UserServlet起一个别名,用于关联请求的映射路径-->
        <servlet-name>userServlet</servlet-name>
        <!--用于高速 Tomcat 要实例化的 Servlet 类-->
        <servlet-class>com.atguigu.servlet.UserServlet</servlet-class>
    </servlet> 
    <servlet-mapping> 
        <!--关联别名和映射路径--> 
        <servlet-name>userServlet</servlet-name> 
        <!--可以为一个Servlet匹配多个不同的映射路径,但是不同的Servlet不能使用相同的url-pattern--> 
        <url-pattern>/userServlet</url-pattern> 
        <!-- <url-pattern>/userServlet2</url-pattern>--> 
        <!-- 
            / 表示通配所有资源,不包括jsp文件 
            /* 表示通配所有资源,包括jsp文件 
            /a/* 匹配所有以a前缀的映射路径 
            *.action 匹配所有以action为后缀的映射路径 
        -->
        <!-- <url-pattern>/*</url-pattern>--> 
    </servlet-mapping> 
</web-app>
  1. 开发一个表单页面
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <form action="userServlet"> 
            请输入用户名:<input type="text" name="username" /> <br> 
            <input type="submit" value="校验"> 
        </form> 
    </body> 
</html>

content-type

  1. 如果请求的静态资源,Tomcat 中的 conf/web.xml 会给出一个 MIME 值,并赋值给 content-type: image.png

  2. 如果是一个接口请求,如果不设置,默认是以HTML的类型返回:

//应该设置Content-Type响应头
response.setHeader("Content-Type","text/html"); 
// 或者
response.setContentType("text/html");

url-pattern

根据访问路径,找到 class 执行: image.png 匹配规则:

  • / 表示通配所有资源,不包括jsp文件
  • /* 表示通配所有资源,包括jsp文件
  • /a/* 匹配所有以a前缀的映射路径
  • *.action 匹配所有以action为后缀的映射路径

Servlet 注解方式配置

使用@WebServlet注解替换Servlet配置:

@WebServlet(
        // servlet 起别名
        name = "userServlet",
        // value 和 urlPatterns 互相之间是别名
        //value = "/user",
        // 一个 Servlet 可以配置多个 urlPatterns 路径
        urlPatterns = {"/userServlet1", "/userServlet2", "/userServlet"},
        // 
        initParams = {@WebInitParam(name = "encoding", value = "UTF-8")},
        loadOnStartup = 6
)
public class UserServlet extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String encoding = getServletConfig().getInitParameter("encoding");
        System.out.println(encoding);
        // 获取请求中的参数
        String username = req.getParameter("username");
        if ("atguigu".equals(username)) {
            //通过响应对象响应信息
            resp.getWriter().write("NO");
        } else {
            resp.getWriter().write("YES");
        }
    }
}

Servlet 生命周期

image.png

package com.atguigu.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

public class ServletLifeCycle extends HttpServlet {
    public ServletLifeCycle() {
        System.out.println("构造器");
    }
    @Override
    public void init() throws ServletException {
        System.out.println("初始化方法");
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("service方法");
    }
    @Override
    public void destroy() {
        System.out.println("销毁方法");
    }
}

配置:

<servlet>
    <servlet-name>servletLifeCycle</servlet-name>
    <servlet-class>com.atguigu.servlet.ServletLifeCycle</servlet-class>
    <!--load-on-startup 如果配置的是正整数则表示容器在启动时就要实例化Servlet,数字表示的是实例化的顺序 -->
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>servletLifeCycle</servlet-name>
    <url-pattern>/servletLiftCycle</url-pattern>
</servlet-mapping>
  • 通过生命周期测试发现 Servlet 对象在容器中是单例的;
  • 容器是可以处理并发的用户请求的,每个请求在容器中都会开启一个线程;
  • 多个线程可能会使用相同的 Servlet 对象,所以在 Servlet 中,我们不要轻易定义一些需要经常修改的成员变量;
  • load-on-startup 中定义的正整数表示实例化顺序,如果数字重复了,容器会自行解决实例化顺序问题,但是应该避免重复;
  • Tomcat 容器中,已经定义了一些随系统启动实例化的 Servlet,自定义的 Servlet 的 load-on-startup 尽量不要占用数字 1-5;

image.png

default-Servlet

如果是接口请求,则会匹配 Servlet,如果是请求静态资源,也会挨个判断 Servlet,如果都没匹配,会进入 default-Servlet 逻辑中。 image.png

Servlet 继承结构

未命名绘图.drawio.png

ServletConfig

  • WEB-INF/web.xml:
<servlet>
    <servlet-name>servlet1</servlet-name>
    <servlet-class>com.atguigu.servlet.Servlet1</servlet-class>
    <!--配置Servlet的初始参数-->
    <init-param>
        <param-name>keya</param-name>
        <param-value>valueA</param-value>
    </init-param>
    <init-param>
        <param-name>keyb</param-name>
        <param-value>valueb</param-value>
    </init-param>
</servlet>
<servlet-mapping>
<servlet-name>servlet1</servlet-name>
<url-pattern>/servlet1</url-pattern>
</servlet-mapping>
  • Tomcat 产生一个对象 ServletConfig,这个对象中有 init-param 中的信息。自定义的 servlet 构造 init 之后,将 ServletConfig 传入。
  • 获取 ServletConfig 对象数据:
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        ServletConfig servletConfig = this.getServletConfig();
        // 根据参数名获取单个参数
        String value = servletConfig.getInitParameter("param1");
        System.out.println("param1:"+value);
        // 获取所有参数名
        Enumeration<String> parameterNames = servletConfig.getInitParameterNames();
        // 迭代并获取参数名
        while (parameterNames.hasMoreElements()) {
            String paramaterName = parameterNames.nextElements();
            System.out.println(paramaterName+":"+servletConfig.getInitParameter(paramaterName));
        }
    }
}

ServletContext

  • ServletContext 对象有称呼为上下文对象,或者叫应用域对象 (后面统一讲解域对象);

  • 容器会为每个 app 创建一个独立的唯一的 ServletContext 对象;

  • ServletContext 对象为所有的 Servlet 所共享;

  • ServletContext 可以为所有的 Servlet 提供初始配置参数; image.png

  • 配置ServletContext参数

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
         https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
         version="5.0">
    <context-param>
        <param-name>paramA</param-name>
        <param-value>valueA</param-value>
    </context-param>
    <context-param>
        <param-name>paramB</param-name>
        <param-value>valueB</param-value>
    </context-param>
</web-app>
  • ServletContext 配置参数被 Tomcat 读取,Tomcat 创建 ServletContext 对象,并且提供给每一个 Servlet
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 从ServletContext中获取为所有的Servlet准备的参数
        ServletContext servletContext = this.getServletContext();

        String valueA = servletContext.getInitParameter("paramA");
        System.out.println("paramA:"+valueA);
        // 获取所有参数名
        Enumeration<String> initParameterNames = servletContext.getInitParameterNames();
        // 迭代并获取参数名
        while (initParameterNames.hasMoreElements()) {
            String paramaterName = initParameterNames.nextElement();
            System.out.println(paramaterName+":"+servletContext.getInitParameter(paramaterName));
        }
    }
}

ServletContext 作用

  • 获取资源的磁盘路径
    String realPath = servletContext.getRealPath("资源在web目录中的路径");
    例如我们的目标是需要获取项目中某个静态资源的路径,不是工程目录中的路径,而是部署目录 中的路径;我们如果直接拷贝其在我们电脑中的完整路径的话其实是有问题的,因为如果该项目 以后部署到公司服务器上的话,路径肯定是会发生改变的,所以我们需要使用代码动态获取资源的 真实路径.只要使用了servletContext动态获取资源的真实路径,那么无论项目的部署路径发生什么 变化,都会动态获取项目运行时候的实际磁盘路径,所以就不会发生由于写死真实路径而导致项 目部署位置改变引发的路径错误问题。

  • 获项目的上下文:
    String contextPath = servletContext.getContextPath();
    项目的部署名称,也叫项目的上下文路径,在部署进入tomcat时所使用的路径,该路径是可能发生 变化的,通过该API动态获取项目真实的上下文路径,可以帮助我们解决一些后端页面渲染技术或 者请求转发和响应重定向中的路径问题。

  • 域对象
    域对象:一些用于在一些特定的范围内存储数据和传递数据的对象,不同的范围称为不同的“域”, 不同的域对象代表不同的域,共享数据的范围也不同; ServletContext代表应用,所以ServletContext域也叫作应用域,是webapp中最大的域,可以在本应 用内实现数据的共享和传递;可以实现跨 servlet 的数据传递。 image.png

HttpServletRequest

  • HttpServletRequest是一个接口,其父接口是ServletRequest;
  • HttpServletRequest是Tomcat将请求报文转换封装而来的对象,在Tomcat调用service方法时传入;
  • HttpServletRequest代表客户端发来的请求,请求中的所有信息都可以通过该对象获得; image.png image.png image.png image.png

HttpServletResponse

  • HttpServletResponse是一个接口,其父接口是ServletResponse;
  • HttpServletResponse是Tomcat预先创建的,在Tomcat调用service方法时传入;
  • HttpServletResponse代表对客户端的响应,该对象会被转换成响应的报文发送给客户端,通过该对 象我们可以设置响应信息; image.png image.png image.png
  • MIME类型
    MIME类型,可以理解为文档类型,用户表示传递的数据是属于什么类型的文档; 浏览器可以根据MIME类型决定该用什么样的方式解析接收到的响应体数据; 可以这样理解::前后端交互数据时,告诉对方发给对方的是html/css/js/图片/声音/视频...; tomcat/conf/web.xml中配置了常见文件的拓展名和MIMIE类型的对应关系; 去除图片水印.png

请求转发 & 响应重定向

请求转发

image.png

// servletA
@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求转发器,转发给一个 servlet
        RequestDispatcher requestDispatcher = req.getRequestDispatcher("servletB");
        // 将 servletA 的请求转发给一个视图资源 ok,将 welcome.html 当作结果返回了
        //RequestDispatcher requestDispatcher = req.getRequestDispatcher("welcome.html");
        // 将 servletA 的请求转发给WEB-INF下的资源 ok,将 "WEB-INF/views/view1.html 返回给客户端了
        //RequestDispatcher requestDispatcher = req.getRequestDispatcher("WEB-INF/views/view1.html");
        // 将 servletA 的请求转发给外部资源 no 不能把外部资源返回给客户端
        //RequestDispatcher requestDispatcher = req.getRequestDispatcher("http://www.atguigu.com");
        // 向请求域中添加数据
        req.setAttribute("reqKey", "requestMessage");
        // 做出转发动作,将 Tomcat 生成的 req 对象和 resp 对象转发给 servletB
        requestDispatcher.forward(req, resp);
    }
}
// servletB
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求参数
        String username = req.getParameter("username");
        System.out.println(username);

        // 获取请求域中的数据
        String reqMessage = (String) req.getAttribute("reqKey");
        System.out.println(reqMessage);

        // 做出响应
        resp.getWriter().write("servletB response");
    }
}
  • 请求转发通过HttpServletRequest对象获取请求转发器实现;
  • 请求转发是服务器内部的行为,对客户端是屏蔽的;
  • 客户端只发送了一次请求,客户端地址栏不变;
  • 服务端只产生了一对请求和响应对象,这一对请求和响应对象会继续传递给下一个资源;
  • 因为全程只有一个HttpServletRequset对象,所以请求参数可以传递,请求域中的数据也可以传递;
  • 请求转发可以转发给其他Servlet动态资源,也可以转发给一些静态资源以实现页面跳转;
  • 请求转发可以转发给WEB-INF下受保护的资源;
  • 请求转发不能转发到本项目以外的外部资源;

响应重定向

image.png 过程:浏览器发送了请求,servletA 返回重定向地址给客户端,客户端再次对重定向地址发起请求。

@WebServlet("/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求参数
        String username = req.getParameter("username");
        System.out.println(username);
        // 向请求域中添加数据
        req.setAttribute("reqKey", "requestMessage");
        
        // 响应重定向
        // 重定向到servlet动态资源 OK
        resp.sendRedirect("servletB");
        // 重定向到视图静态资源 OK
        //resp.sendRedirect("welcome.html");
        // 重定向到WEB-INF下的资源 NO
        //resp.sendRedirect("WEB-INF/views/view1");
        // 重定向到外部资源
        //resp.sendRedirect("http://www.atguigu.com");
    }
}
@WebServlet("/servletB")
public class ServletB extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 获取请求参数
        String username = req.getParameter("username");
        System.out.println(username);
        // 获取请求域中的数据
        String reqMessage = (String) req.getAttribute("reqKey");
        System.out.println(reqMessage);
        // 做出响应
        resp.getWriter().write("servletB response");
    }
}
  • 响应重定向通过HttpServletResponse对象的sendRedirect方法实现
  • 响应重定向是服务端通过302响应码和路径,告诉客户端自己去找其他资源,是在服务端提示下的,客户端的行为
  • 客户端至少发送了两次请求,客户端地址栏是要变化的
  • 服务端产生了多对请求和响应对象,且请求和响应对象不会传递给下一个资源
  • 因为全程产生了多个HttpServletRequset对象,所以请求参数不可以传递,请求域中的数据也不可以传递
  • 重定向可以是其他Servlet动态资源,也可以是一些静态资源以实现页面跳转
  • 重定向不可以到给WEB-INF下受保护的资源
  • 重定向可以到本项目以外的外部资源

乱码问题

1.数据的编码和解码使用的不是同一个字符集
2.使用了不支持某个语言文字的字符集
字符集兼容性: 去除图片水印 (2).png 由上图得知,上述字符集都兼容了ASCII,所以这些东西无论使用什么字符集都不会乱码。

HTML乱码问题

  • 查看当前文件的字符集。 image.png
  • 查看项目字符集配置,将Global Encoding 全局字符集,Project Encoding项目字符集,Properties Files 属性配置文件字符集设置为UTF-8。 image.png 当前视图文件的字符集通过<meta charset="UTF-8">来告知浏览器通过什么字符集来解析当 前文件:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    中文
</body>
</html>

GET请求乱码

  • GET方式提交参数的方式是将参数放到URL后面,如果使用的不是UTF-8,那么会对参数进行URL编 码处理;
  • HTML中的<meta charset='字符集'/>影响了GET方式提交参数的URL编码;
  • Tomcat10.1.7的URI编码默认为UTF-8;
  • 当GET方式提交的参数URL编码和Tomcat10.1.7默认的URI编码不一致时,就会出现乱码; 去除图片水印 (3).png 去除图片水印 (4).png 解决方法:
  • 设置GET方式提交的编码和Tomcat10.1.7的URI默认解析编码一致即可(推荐)。 去除图片水印 (5).png

POST方式请求乱码

不能直接用 GET 请求乱码配置那种方式解决了,因为那种方式对 URI 生效,但是 post 请求没有 URI 参数。 去除图片水印 (6).png 去除图片水印 (7).png 去除图片水印 (8).png 解决方法:
方式1 : 请求时,使用UTF-8集提交请求体 (推荐)。
方式2:后端在获取参数前,设置解析请求体使用的字符集和请求发送时使用的字符集一致(此时不推荐)。 去除图片水印.png

响应乱码问题

在Tomcat10.1.7中,向响应体中放入的数据默认使用了工程编码 UTF-8,浏览器在接收响应信息时,使用了不同的字符集或者是不支持中文的字符集就会出现乱码。 去除图片水印 (1).png 浏览器接收数据解析乱码。 去除图片水印 (2).png 解决方法: 去除图片水印 (1).png

路径问题

响应重定向中的路径问题

目标:由 /x/y/z/servletA 重定向到 a/b/c/test.html

相对路径写法
访问 ServletA 的 url 为:http://localhost:8080/web03_war_exploded/x/y/z/servletA 当前资源为:servletA
当前资源的所在路径为:http://localhost:8080/web03_war_exploded/x/y/z/
要获取的目标资源 url 为:http://localhost:8080/web03_war_exploded/a/b/c/test.html
ServletA 重定向的路径:../../../../a/b/c/test/html
寻找方式就是在当前资源所在路径 (http://localhost:8080/web03_war_exploded/x/y/z/) 后拼接 (../../../../a/b/c/test/html),形成 (http://localhost:8080/web03_war_exploded/x/y/z/../../../../a/b/c/test/html),每个../抵消一层目录,正好是目标资源正常获取的 url (http://localhost:8080/web03_war_exploded/a/b/c/test/html)

@WebServlet("/x/y/z/servletA")
public class ServletA extends HttpServlet {
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 相对路径重定向到test.html
        resp.sendRedirect("../../../../a/b/c/test.html");
    }
}

绝对路径写法
访问 ServletA 的 url 为:http://localhost:8080/web03_war_exploded/x/y/z/servletA
绝对路径的基准路径为:http://localhost:8080
要获取的目标资源 url 为:http://localhost:8080/web03_war_exploded/a/b/c/test.html
ServletA 重定向的路径:/web03_war_exploded/a/b/c/test.html 寻找方式就是在基准路径 (http://localhost:8080) 后面拼接 (/web03_war_exploded/a/b/c/test.html),得到 (http://localhost:8080/web03_war_exploded/a/b/c/test.html),这正是目标资源访问的正确路径。 绝对路径中需要填写项目上下文路径,但是上下文路径是变换的:

  • 可以通过 ServletContextgetContextPath() 获取上下文路径
  • 可以将项目上下文路径定义为 /(缺省路径),那么路径中直接以 / 开头即可
// 绝对路径中,要写项目上下文路径 
// resp.sendRedirect("/web03_war_exploded/a/b/c/test.html"); 
// 通过ServletContext对象动态获取项目上下文路径 
// resp.sendRedirect(getServletContext().getContextPath()+"/a/b/c/test.html"); 
// 缺省项目上下文路径时,直接以/开头即可
resp.sendRedirect("/a/b/c/test.html");

MVC模式

MVC(Model View Controller)是软件工程中的一种软件架构模式,它把软件系统分为模型、视 图和控制器三个基本部分。用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻 辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

  • M:Model 模型层,具体功能如下
    1.存放和数据库对象的实体类以及一些用于存储非数据库表完整相关的VO对象
    2.存放一些对数据进行逻辑运算操作的的一些业务处理代码
  • V:View 视图层,具体功能如下
    1.存放一些视图文件相关的代码 html css js等
    2.在前后端分离的项目中,后端已经没有视图文件,该层次已经衍化成独立的前端项目
  • C:Controller 控制层,具体功能如下
    1.接收客户端请求,获得请求数据
    2.将准备好的数据响应给客户端

MVC模式下项目中的常见包:

  • M:
    1.实体类包(pojo /entity /bean) 专门存放和数据库对应的实体类和一些VO对象
    2.数据库访问包(dao/mapper) 专门存放对数据库不同表格CURD方法封装的一些类
    3.服务包(service) 专门存放对数据进行业务逻辑预算的一些类
  • C:
    1.控制层包(controller)
  • v:
    1. web目录下的视图资源 html css js img 等
    2.前端工程化后,在后端项目中已经不存在了

非前后端分离 MVC

去除图片水印 (2).png

前后端分离 MVC

去除图片水印 (3).png

Cookie

Session

域对象

过滤器

监听器

后端处理跨域

PostMan