Servlet API

x33g5p2x  于2022-04-29 转载在 其他  
字(18.7k)|赞(0)|评价(0)|浏览(492)

1. Servlet API

1.1 HttpServlet

方法

方法名称调用时机
init在 HttpServlet 实例化之后被调用一次
destory在 HttpServlet 实例不再使用的时候调用一次
service收到 HTTP 请求的时候调用
doGet收到 GET 请求的时候调用(由 service 方法调用)
doPost收到 POST 请求的时候调用(由 service 方法调用)
doPut/doDelete/doOptions/…收到其他请求的时候调用(由 service 方法调用)

我们实际开发的时候很少会重写 init / destory / service

这些方法的调用时机, 就称为 “Servlet 生命周期”. (也就是描述了一个 Servlet 实例从生到死的过程)

这里会有一道面试题: 说一下 Servlet 的生命周期

  1. Servlet 在实例化之后调用一次 init()
  2. Servlet 在每次收到请求调用一次 service()
  3. Servlet 在销毁之前,调用一次 destroy()

代码示例: 处理 GET 请求 和 POST 请求

java代码

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("/method")
public class MethodServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().write("GET 响应");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html;charset=utf-8");
        resp.getWriter().write("POST 响应");
    }
}

使用前端代码进行测试.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <button onclick="getMethod()">发送 GET 请求</button>
    <button onclick="postMethod()">发送 POST 请求</button>

    <script src="https://releases.jquery.com/git/jquery-3.x-git.min.js"></script>
    <script>
        function getMethod() {
            $.ajax({
                type: "get",
                url: "method",
                success: function(data,status){
                    console.log(data);
                }
            })
        }
        function postMethod() {
            $.ajax({
                type: "post",
                url: "method",
                data: "request body",
                success:function(data,status){
                    console.log(data);
                }
            })
        }
    </script>
    
    
</body>
</html>

浏览器按F12可以查看

使用 Postman 测试

1.2 HttpServletRequest

方法

方法描述
String getProtocol()返回请求协议的名称和版本。
String getMethod()返回请求的 HTTP 方法的名称,例如,GET、POST 或 PUT。
String getRequestURI()从协议名称直到 HTTP 请求的第一行的查询字符串中,返回该请求的 URL 的一部分。
String getContextPath()返回指示请求上下文的请求 URI 部分。
String getQueryString()返回包含在路径后的请求 URL 中的查询字符串。
Enumeration getParameterNames()返回一个 String 对象的枚举,包含在该请求中包含的参数的名称。
String getParameter(String name)以字符串形式返回请求参数的值,或者如果参数不存在则返回null。
String[] getParameterValues(String name)返回一个字符串对象的数组,包含所有给定的请求参数的值,如果参数不存在则返回 null。
Enumeration getHeaderNames()返回一个枚举,包含在该请求中包含的所有的头名。
String getHeader(String name)以字符串形式返回指定的请求头的值。
String getCharacterEncoding()返回请求主体中使用的字符编码的名称。
String getContentType()返回请求主体的 MIME 类型,如果不知道类型则返回 null。
int getContentLength()以字节为单位返回请求主体的长度,并提供输入流,或者如果长度未知则返回 -1。
InputStream getInputStream()                    用于读取请求的 body 内容. 返回一个 InputStream 对象.

代码示例: 打印请求方式

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.util.Enumeration;

@WebServlet("/show")
public class ShowServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");

        // 将 生成的响应的body 放入到 respBody 中
        StringBuilder respBody = new StringBuilder();
        respBody.append(req.getProtocol()+"<br>");
        respBody.append(req.getMethod()+"<br>");
        respBody.append(req.getRequestURI()+"<br>");
        respBody.append(req.getContextPath()+"<br>");

        respBody.append("<h1>headers:</h1>");
        Enumeration<String> enumeration = req.getHeaderNames();
        while(enumeration.hasMoreElements()){
            String headerName = enumeration.nextElement();
            respBody.append(headerName + ": ");
            respBody.append(req.getHeaders(headerName)+"<br>");
        }

        resp.getWriter().write(respBody.toString());
    }
}

代码示例: 获取 GET 请求中的参数

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("/parameter")
public class GetParameterServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");

        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("userId: " + userId + "," + "classId: " + classId);
    }
}

在浏览器中输入 http://127.0.0.1:8080/Servlet/parameter,此时是null,因为当前没有 query string

在浏览器中输入 http://127.0.0.1:8080/Servlet/parameter?userId=500&classId=1

代码示例: 获取 POST 请求中的参数(form表单按钮)

java代码

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("/postParameter")
public class PostParameterServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf-8");

        String userId = req.getParameter("userId");
        String classId = req.getParameter("classId");
        resp.getWriter().write("userId: "+userId+","+"classId: "+classId);
    }
}

html 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="postParameter" method="POST">
        <input type="text" name="userId">
        <input type="text" name="classId">
        <input type="submit" value="提交">
    </form>
</body>
</html>

输入 123 ,456之后提交

代码示例: 获取 POST 请求中的参数(JSON格式)

这里要导入一个 Json库

import com.fasterxml.jackson.databind.ObjectMapper;

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;
class JsonData{
    public int userId;
    public int classId;
}
@WebServlet("/Json")
public class PostJsonServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf-8");
        String respBody = readBody(req);
        ObjectMapper objectMapper = new ObjectMapper();
        JsonData jsonData = objectMapper.readValue(respBody,JsonData.class);
        resp.getWriter().write("userId: "+ jsonData.userId+","+"classId: "+jsonData.classId);
    }

    private String readBody(HttpServletRequest req) throws IOException {
        int contentLength = req.getContentLength();
        byte[] buffer = new byte[contentLength];
        InputStream inputStream = req.getInputStream();
        inputStream.read(buffer);
        return new String(buffer,"utf-8");
    }
}

1.3 HttpServletResponse

方法

方法描述
void setStatus(int sc)为该响应设置状态码。
void setHeader(String name,String value)设置一个带有给定的名称和值的 header. 如果 name 已经存在,则覆盖旧的值.
void addHeader(String name, String value)添加一个带有给定的名称和值的 header. 如果 name 已经存在,不覆盖旧的值, 并列添加新的键值对
void setContentType(String type)设置被发送到客户端的响应的内容类型。
void setCharacterEncoding(String charset)设置被发送到客户端的响应的字符编码MIME 字符集)例如,UTF-8。
void sendRedirect(String location)使用指定的重定向位置 URL 发送临时重定向响应到客户端。
PrintWriter getWriter()用于往 body 中写入文本格式数据.
OutputStream getOutputStream()用于往 body 中写入二进制格式数据.

代码示例: 设置状态码

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("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String status = req.getParameter("status");
        if (status != null){
            resp.setStatus(Integer.parseInt(status));
        }
        resp.getWriter().write("status: "+status);
    }
}

代码示例: 自动刷新

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("/refresh")
public class AutoRefreshServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setHeader("Refresh","1");
        long time = System.currentTimeMillis();
        resp.getWriter().write(time+"");
    }
}

每次刷新都会有一个HTTP请求

代码示例: 重定向

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("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(302);
        // resp.setHeader("Location","http://www.baidu.com");
        resp.sendRedirect("http://www.baidu.com");
    }
}

直接从http://127.0.0.1:8080/Servlet/redirect跳转到了https://www.baidu.com/

1.4 上传文件

方法

HttpServletRequest 类方法

方法描述
Part getPart(String name)获取请求中给定 name 的文件
Collection getParts()获取所有的文件

Part 类方法

方法描述
String getSubmittedFileName()获取提交的文件名
String getContentType()获取提交的文件类型
long getSize()获取文件的大小
void write(String path)把提交的文件数据写入磁盘文件

代码示例: 上传文件

首先写好前端代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="upload" method="POST" enctype="multipart/form-data">
        <input type="file" name="myImage">
        <input type="submit" value="提交图片">
    </form>
</body>
</html>

写java代码,一定要写一个注解@MultipartConfig

import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.IOException;

@MultipartConfig
@WebServlet("/upload")
public class uploadServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 从 req 对象中,读取 Part 对象
        Part part = req.getPart("myImage");
        // 2. 读取到 Part 对象中的一些参数
        // 这里是打印 上传文件的真实文件名
        System.out.println(part.getSubmittedFileName());
        // 这里是打印 文件的类型
        System.out.println(part.getContentType());
        // 这里是打印 文件的大小
        System.out.println(part.getSize());
        // 3. 将文件写入指定目录
        part.write("D:/gihub创库/JavaEE_CODE/image/myImage.jpg");
        // 4. 返回一个响应
        resp.getWriter().write("upload successful");
    }
}

结果演示:
选择一个图片,点击提交

查看抓包的结果
请求:

响应:

服务器运行结果

2. 实现一个 Web 版 表白墙

2.1 首先创建好一个 maven 项目

  1. 新建 maven 项目
  2. 创建目录 webapp/WEB-INF/web.xml

在web.xml写入

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
    <display-name>Archetype Created Web Application</display-name>
</web-app>
  1. 引入依赖
    servlet 和 Jackson

  1. 将之前所写的 表白墙的前端代码拷贝到webapp中

2.2 约定好前后端交互接口

① 从服务器上获取留言的内容

② 发表新的留言到服务器上

2.3 服务器端代码

首先创建一个 Confession

class Confession{
    public String from;
    public String to;
    public String message;
}

创建一个 confessionServlet 类 继承 HttpServlet重写 doGetdoPost 方法,注解@WebServlet("/confession")
创建一个objectMapper对象用于转换 JSON 字符串

private ObjectMapper objectMapper = new ObjectMapper();

① 重写 doGet 方法

这里是为了处理从服务器上获取到消息.

// 这个用来处理从服务器获取到消息数据
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf-8");
        // 这里的 load() 是用来 读取服务器上的数据
        List<Confession> confessionList = load();
        objectMapper.writeValue(resp.getWriter(),confessionList);
    }

② 重写 doPost 方法

这是为了处理提交留言到服务器上

// 这个用来处理客户端提交数据给服务器
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Confession confession = objectMapper.readValue(req.getInputStream(),Confession.class);
        // 这里的 save() 是用来 提交数据在服务器上
        save(confession);

        resp.setContentType("application/json;charset=utf-8");
        resp.getWriter().write("{\"ok\":1}");
    }

2.4 数据存储

由于每次刷新页面,数据就不见了.想要每次输入的数据都在,就要将数据存起来.有两种方法.一种是存文件的方法,一种是存数据库的方法.

1) 存文件的方式存储

创建一个filePath保存文件的存储路径

private String filePath = "d:/confessionWall.txt";

约定好存文件的格式.每行是一个数据,每个数据的内容之间用\t隔开

① 使用文件的方法实现 load()
/**
     * 向文件夹中加载数据
     * @return
     */
    private List<Confession> load() {
        List<Confession> confessionList = new ArrayList<>();
        // 此处我们需要按行读取. FileReader 本身不支持. 需要套上一层 BufferedReader
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath))) {
            while(true){
                String line = bufferedReader.readLine();
                // line=null的时候 读完了
                if (line == null){
                    break;
                }
                // 设定存储的时候每行的内容是按 \t 的方法存储
                // 所以读取每行,然后按 \t的方法进行 分割
                String[] res = line.split("\t");
                Confession confession = new Confession();
                confession.from = res[0];
                confession.to = res[1];
                confession.message = res[2];

                confessionList.add(confession);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return confessionList;
    }
② 使用文件的方法实现 save()
/**
     * 向文件夹中写入数据
     * @param confession
     */
    private void save1(Confession confession) {
        // 这里的 true 表示追加写状态,表示不清空文件,而是从文件后面继续写
        try(FileWriter fileWriter = new FileWriter(filePath,true)) {
            // 按照约定好的方法写入数据
            fileWriter.write(confession.from+"\t"+confession.to+"\t"+confession.message+"\n");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2) 存数据库的方式存储

首先创建一个类 ConnectionDB 用来连接数据库
pom.xml中引入mysql jdbc的依赖

<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.17</version>
        </dependency>

类 ConnectionDB

import com.mysql.cj.jdbc.MysqlDataSource;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class ConnectionDB {
    private static final String URL="jdbc:mysql://127.0.0.1:3306/confessionWall?characterEncoding=utf-8&useSSL=true&serverTimezone=UTC";
    private static final String USERNAME = "root";
    private static final String PASSWORD = "0000";
    
    // 这里的volatile 和 synchronized 相当于懒汉模式
    private static volatile DataSource dataSource = null;

    public static DataSource getDataSource() {
        if(dataSource == null){
            synchronized (ConnectionDB.class){
                if(dataSource == null) {
                    dataSource = new MysqlDataSource();
                    ((MysqlDataSource) dataSource).setURL(URL);
                    ((MysqlDataSource) dataSource).setUser(USERNAME);
                    ((MysqlDataSource) dataSource).setPassword(PASSWORD);
                }
            }
        }
        return dataSource;
    }

    public static Connection getConnection() throws SQLException {
        return getDataSource().getConnection();
    }

    public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) {
        if(resultSet != null){
            try{
                resultSet.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if(connection != null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

数据库中的代码

create database confessionWall;

use confessionWall;

create table confession(
    `from` varchar(1024),
    `to` varchar(1024),
    `message` varchar(1024)
);
① 使用数据库的方法实现 load()
/**
     * 从数据库中读取数据
     * @return
     */
    private List<Confession> load() {
        List<Confession> confessionList = new ArrayList<>();
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try{
            // 和数据库建立连接
            connection = ConnectionDB.getConnection();
            // 借助 PreparedStatement 拼接 SQL 语句
            String sql = "select * from confession";
            statement = connection.prepareStatement(sql);
            // 执行 SQL 语句 
            resultSet = statement.executeQuery();
            // 遍历结果集
            while (resultSet.next()){
                Confession confession = new Confession();
                confession.from = resultSet.getString("from");
                confession.to = resultSet.getString("to");
                confession.message = resultSet.getString("message");
                confessionList.add(confession);
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            // 关闭释放资源
            ConnectionDB.close(connection,statement,resultSet);
        }
        return confessionList;
    }
② 使用数据库的方法实现 save()
/**
     * 从数据库中写入数据
     * @param confession
     */
    private void save(Confession confession) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            // 1. 先和数据库建立连接
            connection = ConnectionDB.getConnection();
            // 2. 构造拼装 SQL
            String sql = "insert into confession values (?,?,?)";
            // 这里的 ? 号是占位符,下标是从1开始
            statement = connection.prepareStatement(sql);
            statement.setString(1,confession.from);
            statement.setString(2,confession.to);
            statement.setString(3,confession.message);
            // 3. 执行 SQL
            int ret = statement.executeUpdate();
            // 这里 ret 表示的是受影响的行数,如果为1才是插入成功
            if(ret == 1){
                System.out.println("插入成功!");
            }else {
                System.out.println("插入失败!");
            }
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }finally {
        	// 4. 关闭释放资源
            ConnectionDB.close(connection,statement,null);
        }
    }

2.5 修改前端代码

首先加入 jquery

<script src="https://releases.jquery.com/git/jquery-git.min.js"></script>

① 加载页面的时候从服务器获取内容

<script>
        // 1. 在页面加载的时候,访问服务器,从服务器获取到的消息列表,并展示出来
        function load() {
            $.ajax({
                // 类型约定好了的是 GET ,url也约定好了是 confession
                type: "GET",
                url: "confession",
                success: function(data,status){
                    // data 是响应的 body
                    // 这里的 .parent 是根据之前写的代码来决定的
                    let parent = document.querySelector('.parent');
                    // 由于data存储的时候自动变成了数组的形式
                    // 如果没有变还需要 自己进行解析 JSON.parse(data);
                    let messages = data;
                    for (let message of messages){
                        let row = document.createElement('div');
                        row.className = 'elem';
                        row.innerHTML = message.from + '对' + message.to + '说: ' + message.message;
                        parent.appendChild(row);
                    }
                }
            });
        }
        load();
</script>

② 点击提交按钮的时候,把当前的数据构造一个HTTP请求,发给服务器

<script>
            // 这里需要构造一个 HTTP 请求.把消息发送给服务器保存起来
            
            $.ajax({
                type: "POST",
                url: "confession",
                // 将body的内容变成json的格式
                data: JSON.stringify({from: user1,to: user2,message: message}),
                contentType: "application/json; charset=utf-8",
                success: function(data,status){
                    if (data.ok == 1){
                        console.log("提交消息成功!");
                    }else {
                        console.log("提交失败!");
                    }
                }
            });
</script>

2.6 运行结果图

文件的方式

首次进入页面没有内容

输入 小明 对 小强 说 hello
输入 小强 对 小红 说 你真好

刷新页面或者重启服务器.该页面的内容都在. 查看路径 d:/confessionWall.txt 下的文件内容

数据库的方式

首次访问还没有数据

输入 小明 对 小红 说 你好
输入 小红 对 小明 说 你也好

刷新页面或者重启服务器.该页面的内容都在.查看数据库

2.7 总结

  1. 数据存储的方式使用 文件 还是 数据库.主要是根据具体情况来看的
    当前我们写的程序比较简单, 存储的数据比较少, 数据格式也不复杂. 这种情况下使用文件是比数据库代码更精简一些
    当前我们的程序更复杂, 数据更多并且数据格式也更复杂的时候.这种情况下使用数据库的代码更好.
    例如 表白墙 这种 就更适合于 文件
  2. 在写 Web 程序的时候, 首先要约定好前后端交互的具体方法.不然前端代码和后端代码就可能不在一条路上
  3. 在出错的时候,在网页中按F12进行查看,是前端代码出错还是后端代码出错.如果前端出错再去改前端代码,后端出错再去改后端代码.

相关文章