Servlet

Servlet

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

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述

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

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

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

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

Servlet常见的错误

在这里插入图片描述
2.
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.在这里插入图片描述
4.
在这里插入图片描述
5.
在这里插入图片描述

Servlet的API详解

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

1.HttpServlet

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
2.HttpServletRequst
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为什么是字节流而不是字符流呢??
因为当前数据如果是文本文件,此时使用字节流或者字符流都可以,但是如果是二进制文件,只能使用字节流,虽然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("<br>");
        result.append(req.getMethod());
        result.append("<br>");
        result.append(req.getRequestURI());
        result.append("<br>");
        result.append(req.getQueryString());
        result.append("<br>");
        result.append(req.getContextPath());
        result.append("<br>");

        result.append("=========================<br>");

        Enumeration<String> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String headerValue = req.getHeader(headerName);
            result.append(headerName + ": " + headerValue + "<br>");
        }

        // 在响应中设置上 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操作不是一件很复杂的操作

在这里插入图片描述
在这里插入图片描述

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");
    }

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

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
在这里插入图片描述
在这里插入图片描述

@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 响应!");
    }
}

在这里插入图片描述
在这里插入图片描述

@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下
在这里插入图片描述
在这里插入图片描述
编写后端代码之前,需要先明确网页给服务器发啥样的请求,服务器给网页返回啥样的响应
用户与服务器交互的时机有两个:
1.页面加载完毕之后,需要给服务器发个请求,获取当前的留言数据都有啥
2.用户点击提交的时候,就需要告诉服务器,当前用户发了的消息是啥
在前后端交互的过程中,还要考虑到前后端交互的接口,也就是请求具体是啥样子的,响应具体是啥样子的,这是一个特别关键的工作,这个工作不弄清楚了,后面的代码就没法写
这里有一种典型的约定方式(不是唯一,你也可以自己定义)
在这里插入图片描述
对于接口二,服务器要做的事情就是解析请求中的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<Message> 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;
    }

}

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

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

Session(会话机制)

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

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值