Servlet是Tomcat给咱们提供的一组api,操作HTTP的
Servlet并非是咱们自己写一个独立的程序,而是写一个代码片段,把这段代码插入到Tomcat中
如何使用Servlet,在java中使用Servlet,先从一个helloworld着手,注意接下来将要见到咱们整个学习生涯中最复杂的helloworld,需要经历7个步骤,这些步骤对于初学者来说很不友好,但是这些步骤都是固定的操作,都是一个套路,刚开始会非常的不适应,类似于JDBC编程,但是你下去多敲几次代码就熟悉了!!!!
创建好maven项目之后有一个pom.xml,这个是maven一个最核心的文件,想针对maven进行一些配置啥的,都是以这个文件为主
这个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中运行了,编码方式一样了,就不是乱码了
后续随时修改代码,随时点下图这个运行就可以了
虽然这里的API有很多,但是咱们重点掌握三个类就够了
咱们写一个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("
");
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操作不是一件很复杂的操作
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");
}
}
@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 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操作页面内容)
服务器同一时刻收到的请求是很多的. 服务器需要清除的区分清楚每个请求是从属于哪个用户, 就需要在服务器这边记录每个用户令牌以及用户的信息的对应关系.
每个就诊卡和患者信息之间的关联关系. 会话的本质就是一个 “哈希表”, 存储了一些键值对结构. 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,则会话默认还是在内存中的,则重启会丢失