Servlet

Servlet

Servlet是Tomcat给咱们提供的一组api,操作HTTP的
Servlet并非是咱们自己写一个独立的程序,而是写一个代码片段,把这段代码插入到Tomcat中
如何使用Servlet,在java中使用Servlet,先从一个helloworld着手,注意接下来将要见到咱们整个学习生涯中最复杂的helloworld,需要经历7个步骤,这些步骤对于初学者来说很不友好,但是这些步骤都是固定的操作,都是一个套路,刚开始会非常的不适应,类似于JDBC编程,但是你下去多敲几次代码就熟悉了!!!!
创建好maven项目之后有一个pom.xml,这个是maven一个最核心的文件,想针对maven进行一些配置啥的,都是以这个文件为主

第一个Servlet程序及其配置和使用

Servlet_第1张图片
Servlet_第2张图片
Servlet_第3张图片

这个jar包被下载到本地的一个隐藏目录中了

Servlet_第4张图片
Servlet_第5张图片

Servlet_第6张图片
Servlet_第7张图片
Servlet_第8张图片
Servlet_第9张图片
Servlet_第10张图片
tomcat中是有main方法的
main方法就像发动机
Servlet_第11张图片
Servlet_第12张图片
第五步:打包代码咱们的程序
不能直接独立运行,而是必须放到Tomcat上运行(部署),部署的前提,是先打包,对于一个规模比较大的项目,里面就会包含很多的java文件,进一步的就会产生很多的.class .文件,所以,把这些.class打成一个压缩包,再进行拷贝,是比较科学的
咱们平时见到的压缩包:rar,zip…
在java中,使用的压缩包是jar,war
普通的java程序打包成jar,部署给tomcat的程序打包成war
war和jar本质上没啥区别,都是把一堆.class文件给打包进去了.但是war包是属于tomcat的专属格式.里面会有一些特定的目录结构和文件,比如,web.xml.后续tomcat就要识别这些内容,来加载webapp
Servlet_第13张图片
打包操作,做的事情:
1.检查代码中是否存在一些依赖,依赖是否下载好.(这个事情都是maven负责的.之前引入了 serlvet的依赖)
2.把代码进行编译,生成一堆.class文件.
3.把这些.class文件,以及web.xml按照一定的格式进行打包
Servlet_第14张图片
Servlet_第15张图片

打好的war包,就是一个普通的压缩包,是可以使用解压缩工具(winrar)打开,看到里面的内容的但是并不需要手动解压缩.直接把整个war交给 tomcat,tomcat能够自动的解压缩.
第六步:把打好的jar包,拷贝到tomcat的webapps目录中
Servlet_第16张图片
Servlet_第17张图片
Servlet_第18张图片
注意:此处的hello_servlet和上述的在这里插入图片描述
是一样的,只不过我第一次起的名字和第二次起的名字不一样

以上就是基于maven和tomcat的hello world的全过程
总的来说,七个步骤如下
Servlet_第19张图片
那么可以简化吗?
当然可以!
可以简化5和6,可以把5和6一键式完成,就需要借助IDEA的插件来完成这个工作
IDEA 功能非常多,非常强大但是即使如此,IDEA也无法做到“面面俱到”为了支持这些特定的,小众的功能,就引入了“插件体系”插件可以视为是对IDEA原有功能的扩充程序猿可以按需使用同理,很多这样的程序都引入了插件体系。VSCode也是类似(千奇百怪)
Servlet_第20张图片
Servlet_第21张图片
使用smart tomcat
Servlet_第22张图片
Servlet_第23张图片
Servlet_第24张图片
Servlet_第25张图片

Servlet_第26张图片

注意!!!
红色不一定是报错错
下图都是tomcat正常的日志,tomcat在idea中运行了,编码方式一样了,就不是乱码了
Servlet_第27张图片
Servlet_第28张图片
后续随时修改代码,随时点下图这个运行就可以了
在这里插入图片描述

Servlet常见的错误

Servlet_第29张图片
2.
Servlet_第30张图片
Servlet_第31张图片
Servlet_第32张图片
3.Servlet_第33张图片
4.
Servlet_第34张图片
5.
Servlet_第35张图片

Servlet的API详解

虽然这里的API有很多,但是咱们重点掌握三个类就够了

1.HttpServlet

Servlet_第36张图片
咱们写一个Servlet程序,都是要继承这个类的
我们就需要知道,哪些方法,是能够被重写的.也就是HttpServlet中都有啥方法,都是干啥的
Servlet_第37张图片
Servlet_第38张图片
在这里插入图片描述
Servlet_第39张图片
Servlet_第40张图片

Servlet_第41张图片
在这里插入图片描述
Servlet_第42张图片
Servlet_第43张图片
Servlet_第44张图片
Servlet_第45张图片

Servlet_第46张图片
2.HttpServletRequst
Servlet_第47张图片
Servlet_第48张图片
Servlet_第49张图片
Servlet_第50张图片
Servlet_第51张图片
在这里插入图片描述
为什么是字节流而不是字符流呢??
因为当前数据如果是文本文件,此时使用字节流或者字符流都可以,但是如果是二进制文件,只能使用字节流,虽然HTTP是文本协议,但是其实HTTP的body也是可以携带二进制数据的,比如如果请求或者响应的body是压缩过的,此时body就是二进制的

@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        StringBuilder result = new StringBuilder();
        result.append(req.getProtocol());
        result.append("
"
); result.append(req.getMethod()); result.append("
"
); result.append(req.getRequestURI()); result.append("
"
); result.append(req.getQueryString()); result.append("
"
); result.append(req.getContextPath()); result.append("
"
); result.append("=========================
"
); Enumeration<String> headerNames = req.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName = headerNames.nextElement(); String headerValue = req.getHeader(headerName); result.append(headerName + ": " + headerValue + "
"
); } // 在响应中设置上 body 的类型. 方便浏览器进行解析 resp.setContentType("text/html;charset=utf8"); resp.getWriter().write(result.toString()); } }

下面来说getParameter方法
这个是最常用的API之一
1).通过query string传递

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("/getParameter")
public class GetParameter extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 前端通过 url 的 query string 传递 username 和 password 两个属性.
        String username = req.getParameter("username");
        if (username == null) {
            System.out.println("username 这个 key 在 query string 中不存在!");
        }
        String password = req.getParameter("password");
        if (password == null) {
            System.out.println("password 这个 key 在 query string 中不存在!");
        }
        System.out.println("username=" + username + ", password=" + password);

        resp.getWriter().write("ok");
    }
}
//在url中,键值对是通过字符串表示的,但是tomcat收到请求后,会自动的把url中的键值对转成Map,方便咱们查询
//这个不是调用的时候才解析的,这个是在收到请求的时候就解析了,因为解析url操作不是一件很复杂的操作

Servlet_第52张图片
Servlet_第53张图片

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        // 前端通过 body, 以 form 表单的格式, 把 username 和 password 传给服务器.
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        if (username == null) {
            System.out.println("username 这个 key 在 body 中不存在");
        }
        if (password == null) {
            System.out.println("password 这个 key 在 body 中不存在");
        }
        System.out.println("username=" + username + ", password=" + password);

        resp.getWriter().write("ok");
    }

Servlet_第54张图片

Servlet_第55张图片
Servlet_第56张图片

class User {
    public String username;
    public String password;
}

@WebServlet("/json")
public class JsonServlet extends HttpServlet {
    // 使用 jackson, 最核心的对象就是 ObjectMapper
    // 通过这个对象, 就可以把 json 字符串解析成 java 对象; 也可以把一个 java 对象转成一个 json 格式字符串.
    private ObjectMapper objectMapper = new ObjectMapper();

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过 post 请求的 body 传递过来一个 json 格式的字符串.
        User user = objectMapper.readValue(req.getInputStream(), User.class);
        System.out.println("username=" + user.username + ", password=" + user.password);

        resp.getWriter().write("ok");
    }
}

3.HttpServletResponse
Servlet_第57张图片
Servlet_第58张图片

@WebServlet("/status")
public class StatusServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setStatus(404);
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("返回 404 响应!");
    }
}

Servlet_第59张图片
Servlet_第60张图片

@WebServlet("/redirect")
public class RedirectServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 用户访问这个路径的时候, 自动重定向到 搜狗主页 .
//        resp.setStatus(302);
//        resp.setHeader("Location", "https://www.sogou.com");
        resp.sendRedirect("https://www.sogou.com");
    }
}

表白墙后端

先做好准备工作,把表白墙的前端代码放到webapp下
Servlet_第61张图片
Servlet_第62张图片
编写后端代码之前,需要先明确网页给服务器发啥样的请求,服务器给网页返回啥样的响应
用户与服务器交互的时机有两个:
1.页面加载完毕之后,需要给服务器发个请求,获取当前的留言数据都有啥
2.用户点击提交的时候,就需要告诉服务器,当前用户发了的消息是啥
在前后端交互的过程中,还要考虑到前后端交互的接口,也就是请求具体是啥样子的,响应具体是啥样子的,这是一个特别关键的工作,这个工作不弄清楚了,后面的代码就没法写
这里有一种典型的约定方式(不是唯一,你也可以自己定义)
Servlet_第63张图片
对于接口二,服务器要做的事情就是解析请求中的body,转成Message对象,然后把这个Message对象给保存起来
为了防止服务器重启数据丢失,我们这里引入了数据库的JDBC
后端代码

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;

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.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

class Message {
    // 这几个属性必须设置 public !!!!
    // 如果设置 private, 必须生成 public 的 getter 和 setter !!!
    public String from;
    public String to;
    public String message;

    @Override
    public String toString() {
        return "Message{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

@WebServlet("/message")
public class MessageServlet extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    // private List messageList = new ArrayList<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过这个方法来处理 "获取所有留言消息"
        // 需要返回一个 json 字符串数组. jackson 直接帮我们处理好了格式.
        List<Message> messageList = load();
        String respString = objectMapper.writeValueAsString(messageList);
        resp.setContentType("application/json; charset=utf8");
        resp.getWriter().write(respString);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 通过这个方法来处理 "提交新消息"
        Message message = objectMapper.readValue(req.getInputStream(), Message.class);
        save(message);
        System.out.println("消息提交成功! message=" + message);

        // 响应只是返回 200 报文. body 为空. 此时不需要额外处理. 默认就是返回 200 的.
    }

    // 这个方法用来往数据库中存一条记录
    private void save(Message message) {
        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java107?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("2222");

        try {
            Connection connection = dataSource.getConnection();
            String sql = "insert into message values(?, ?, ?)";
            PreparedStatement statement = connection.prepareStatement(sql);
            statement.setString(1, message.from);
            statement.setString(2, message.to);
            statement.setString(3, message.message);
            statement.executeUpdate();

            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 这个方法用来从数据库查询所有记录
    private List<Message> load() {
        List<Message> messageList = new ArrayList<>();

        DataSource dataSource = new MysqlDataSource();
        ((MysqlDataSource)dataSource).setUrl("jdbc:mysql://127.0.0.1:3306/java107?characterEncoding=utf8&useSSL=false");
        ((MysqlDataSource)dataSource).setUser("root");
        ((MysqlDataSource)dataSource).setPassword("2222");

        try {
            Connection connection = dataSource.getConnection();
            String sql = "select * from message";
            PreparedStatement statement = connection.prepareStatement(sql);
            ResultSet resultSet = statement.executeQuery();

            while (resultSet.next()) {
                Message message = new Message();
                message.from = resultSet.getString("from");
                message.to = resultSet.getString("to");
                message.message = resultSet.getString("message");
                messageList.add(message);
            }

            resultSet.close();
            statement.close();
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return messageList;
    }

}

前端代码(博主是主后端的,于是前端就截图了)
Servlet_第64张图片
在这里插入图片描述
Servlet_第65张图片
Servlet_第66张图片

Servlet_第67张图片
因此不存在跨域问题。
小结:
通过上述代码,就已经写出来了一个很简单的网站了,未来写很多复杂的网站,都是这一套逻辑
1.约定前后端交互接口
2.实现服务器代码(通常会操作数据库)
3.实现客户端代码(通常会使用ajax构造请求,并使用一些js的webapi操作页面内容)

Session(会话机制)

服务器同一时刻收到的请求是很多的. 服务器需要清除的区分清楚每个请求是从属于哪个用户, 就需要在服务器这边记录每个用户令牌以及用户的信息的对应关系.
每个就诊卡和患者信息之间的关联关系. 会话的本质就是一个 “哈希表”, 存储了一些键值对结构. key 就是令牌的 ID(token/sessionId), value 就是用户信息(用户信息可以根据需求灵活设计).
sessionId 是由服务器生成的一个 “唯一性字符串”, 从 session 机制的角度来看, 这个唯一性字符串称为 “sessionId”. 但是站在整个登录流程中看待, 也可以把这个唯一性字符串称为 “token”. sessionId 和 token 就可以理解成是同一个东西的不同叫法(不同视角的叫法)
Servlet_第68张图片
Session会话:
给当前的用户分配一个sessionld,同时记录下当前用户的一些身份信息(可以自定义的),sessionld 就会被返回到浏览器的 cookie 中,后续浏览器访问服务器都会带着这个sessionld .从而能够让服务器识别出当前的用户身份了.
Session就是服务器这边用来实现用户身份区分的一种机制,通常是和cookie配合使用的
Servalet里针对cookie和session做出了哪些支持呢?
Servlet_第69张图片
如果参数为true,getSession的行为是:
1.读取请求中cookie 里的 sessionld
2.在服务器这边根据sessionld来查询对应的Session对象.
3.如果查到了,就会直接返回这个 session对象.
4.如果没查到,就会创建一个Session对象,同时生成一个sessionld以 sessionld 为 key, Session 对象为value,把这个键值对存储到服务器里的一个hash 表中.同时把 sessionld 以 Set-Cookie 的方式返回给浏览器

getSession可以让我们在不存在的时候创建新的会话,这个对登录功能的实现起到了很重要的作用,true是允许创建session对象,false是不允许创建

下面来举一个例子

模拟实现登录功能

首先,提供两个页面:
1.登录页(包含两个输入框,输入用户名密码.还要有一个登录按钮)点击登录按钮,就会发起一个http请求I服务器处理这个请求的时候就会验证用户名密码.如果用户名密码ok,就会跳转到主页.
2.主页,只是单纯的显示出当前用户的用户名(欢迎XXX)

其中,登录页,就是一个单纯的html
还需要写一个Servlet,实现登录时候的用户名密码校验。
还要再写一个Servlet来生成主页.(主页里的内容是动态的,不能光一个html就完了)

第一步:前端html代码

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>登录页面title>
head>
<body>
    <form action="login" method="post">
        <input type="text" name="username">
        <input type="password" name="password">
        <input type="submit" value="登录">
    form>
body>
html>

form会组织这里的数据以键值对的形式提交给服务器.
其中key就是input的name属性.
其中value就是input用户输入的内容.
最终会构造成post请求,在body里以键值对(类似于query string)的格式,进行组织。
服务器可以通过 getParameter来获取到指定key的value.

第二步:编写LoginServlet处理上述登录请求.登录请求形如:
POST/login
Content-Type: application/x-www-formurlencoded
username=zhangsan&password=123

package login;

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.servlet.http.HttpSession;
import java.io.IOException;

// 这个类用来实现登录时的校验.
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 先从请求中拿到用户名和密码.
        // 为了保证读出来的参数也能支持中文, 要记得设置请求的编码方式是 utf8
        req.setCharacterEncoding("utf8");
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        // 2. 验证用户名密码是否正确
        if (username == null || password == null || username.equals("") || password.equals("")) {
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前输入的用户名或密码不能为空!");
            return;
        }
        // 此处假定用户名只能是 zhangsan 或者 lisi. 密码都是 123
        // 正常的登录逻辑, 验证用户名密码都是从数据库读取的.
        if (!username.equals("zhangsan") && !username.equals("lisi")) {
            // 用户名有问题
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("用户名或密码有误");
            return;
        }
        if (!password.equals("123")) {
            // 密码有问题
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("用户名或密码有误");
            return;
        }
        // 3. 用户名和密码验证 ok, 接下来就创建一个会话.
        //    当前用户处于未登录的状态, 此时请求的 cookie 中没有 sessionId
        //    此处的 getSession 是无法从服务器的 哈希表 中找到该 session 对象的.
        //    由于此处把参数设为 true 了, 所以就允许 getSession 在查询不到的时候, 创建新的 session 对象和 sessionId
        //    并且会自动的把这个 sessionId 和 session 对象存储的 哈希表 中.
        //    同时返回这个 session 对象, 并且在接下来的响应中会自动把这个 sessionId 返回给客户端浏览器.
        HttpSession session = req.getSession(true);
        // 接下来可以让刚刚创建好的 session 对象存储咱们自定义的数据. 就可以在这个对象中存储用户的身份信息.
        session.setAttribute("username", username);
        // 4. 登录成功之后, 自动跳转到 主页
        resp.sendRedirect("index");
    }
}

Servlet_第70张图片
小结:登录逻辑的固定套路:
1.读取用户名和密码
2.验证用户名密码(很可能要用数据库查询)
3.创建会话,保存必要的用户信息4.重定向到主页

第三步:编写生成主页的servlet

package login;

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.servlet.http.HttpSession;
import java.io.IOException;

// 这个 Servlet 用来动态的生成主页面.
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 此处禁止创建会话. 如果没找到, 认为用户是未登录的状态!!
        // 如果找到了才认为是登录状态.
        HttpSession session = req.getSession(false);
        if (session == null) {
            // 未登录状态
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前用户未登录!");
            return;
        }
        String username = (String) session.getAttribute("username");
        if (username == null) {
            // 虽然有会话对象, 但是里面没有必要的属性, 也认为是登录状态异常.
            resp.setContentType("text/html; charset=utf8");
            resp.getWriter().write("当前用户未登录!");
            return;
        }

        // 如果上述检查都 ok, 接下来就直接生成一个动态页面.
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("欢迎你! " + username);
    }
}

Servlet_第71张图片
在服务器这边,Servlet内部维护了一个全局的哈希表,key就是sessionID,value就是session对象,通过getSession方法,其实就是在操纵这个全局的哈希表,因此这个session是个全局的
上述 sessionld也不会一直存在下去比如 服务器重新启动,原来hash表中的内容就没了.此时再次访问,就可能出现sessionld无法查询到,于是就被识别成“未登录状态了”服务器默认保存会话,是在内存中的,一旦重启服务器,之前的会话数据就没了
但是,Smart Tomcat为了方便程序猿调试程序,会在停止服务器的时候,把会话持久化保存并且在下次启动的时候,自动把会话恢复到内存中。这个时候,会话是不丢失的,这个取决于SmatrTomcat的版本
如果是手动部署程序到tomcat,则会话默认还是在内存中的,则重启会丢失

你可能感兴趣的:(servlet,java-ee,tomcat,maven)