目录
前言
在上一篇博客中,我们已经介绍了Tomcat和Servlet的基本使用,在本篇博客中,将继续接上一篇博客继续深入介绍Servlet,主要介绍一下Servlet的一些常用API。
一、HttpServlet
HttpServlet这个类我们在前面的Servlet版本的Hello World就已经使用过了,我们一般都是继承HttpServlet类,接着重写里面的一些方法就可以了。
1.1、HttpServlet的方法
方法名称 | 调用时机 |
init | 在 HttpServlet 实例化之后被调用一次 |
destory | 在HttpServlet 实例不再使用的时候调用一次 |
service | 收到 HTTP 请求的时候调用 |
doGet | 收到 GET 请求的时候调用(由 service 方法调用) |
doPost | 收到 POST 请求的时候调用(由 service 方法调用) |
doPut/doDelete/doOptions | 收到其他请求的时候调用(由 service 方法调用) |
在实际开发中,我们一般都是重写do×××方法,而不会重写init/destory/service。
1.2、Servlet的生命周期
生命周期:这些方法的调用时机, 就称为 "Servlet 生命周期". (也就是描述了一个 Servlet 实例从生到死的过程).
这是一个经典的面试题:①在HttpServlet实例化之后,首次收到匹配的请求(也就是首次收到Http请求时)的时候会执行一次init方法;②destory方法是结束之前调用一次;③service会在每次收到路径匹配时调用一次。
这里虽然是说到destroy方法会在结束之前会执行一次,但是是否真的会调用,还是有条件的。具体什么条件可以接着往下看。
1.3、HttpServlet方法的代码演示及一些问题
在下面的代码中我们重写了多个do方法,接下去我们就通过Postman来构造请求,看看响应的结果。
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("/test")
public class TestDo extends HttpServlet {
@Override
public void init() throws ServletException {
System.out.println("这是init方法");
}
@Override
public void destroy() {
System.out.println("这是destroy方法");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("调用GET方法");
resp.getWriter().write("调用GET方法");
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("调用Post方法");
resp.getWriter().write("调用Post方法");
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("调用Put方法");
resp.getWriter().write("调用Put方法");
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("调用Delete方法");
resp.getWriter().write("调用Delete方法");
}
@Override
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("调用Options方法");
resp.getWriter().write("调用Options方法");
}
}
通过以上图片发现的两个问题:
init方法的调用时机和destroy方法存在的问题:以上图片我们分别构造了一个post和options请求,并且强制结束之后的结果。我们可以看到服务器端先调用了一次init方法,并不是一执行起来就调用的,而是收到第一次请求之后才调用的,并且只调用一次。强制结束之后却没有调用destroy方法,因此,destroy其实是不靠谱的,我们不应该去依赖destroy。destroy只有通过8005管理端口来停止服务器才会执行到,但是一般我们都是直接停止服务器的,所以是不会执行destroy方法的。
Postman收到的响应乱码问题:通过图片我们可以看出Postman收到的响应中莫名出现了很多的问号,而且原本该出现的中文却都没了,其实这里就是出现了乱码的情况,这里乱码的原因就是IDEA和Postman编码方式和解码方式的不同导致的,我们IDEA这边使用的utf8的编码方式。而Postman使用的是gbk的编码方式,这必然就会出现乱码。我们只需要在IDEA中显示的告诉对方使用utf8编码方式解码,乱码的问题自然就解决了。只要加上以下语句即可:resp.setContentType("text/html; charset=utf8");
二、HttpRequest
HttpRequest是什么:当 Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并且按照 HTTP 协议的格式把字符串解析成 HttpServletRequest 对象.
2.1、核心方法
方法 | 描述 |
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 对象. |
2.2、使用HttpRequest获取请求中的各种信息
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("/showRequest")
public class ShowRequest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//将响应设置成html格式
resp.setContentType("text/html;charset=utf8");
//在doGet中获取请求中的键值对
StringBuilder result = new StringBuilder();
result.append(req.getProtocol());//获取协议名称及版本号
result.append("<br>");
result.append(req.getMethod());//获取请求的方法名
result.append("<br>");
result.append(req.getRequestURI());//获取请求的URI
result.append("<br>");
result.append(req.getContextPath());//获取指示请求上下文的请求URI 部分
result.append("<br>");
result.append(req.getQueryString());//获取请求中包含在路径后的查询字符串
result.append("<br>");
// resp.getWriter().write(result.toString());//将result写入到响应中,并显示出来
result.append("------------------------------<br>");//分割线
//获取全部的head中的键值对
Enumeration<String> headerNames = req.getHeaderNames();//获取head中的所有键
while (headerNames.hasMoreElements()){//循环遍历到每一个head
String headerName = headerNames.nextElement();//先获取到header中的key
String headValue = req.getHeader(headerName);//通过键获取值
result.append(headerName+":"+headValue+"<br>");
}
resp.getWriter().write(result.toString());
}
}
运行效果:(跟抓包看到的结果是一样的)
2.3、前端给后端传数据的三种方式
前端给后端传递数据主要有三种通过Query String直接在URI后加上对应的键值对即可,也可以通过body的来传递,body又分为通过form和json来传参。接下来我们就逐一来介绍一下具体该如何传递。
2.3.1、通过Query String传递
通过Query String传参实际上就是直接在输入的网址中加入对应的键值对,在二级路径后加上英文问号,接着就可以书写键值对了。
2.3.2、通过body传参(form表单)
代码如下所示,分别是doGet方法和doPost方法:
这里我们通过Postman来构造一个form表单类型的doPost请求就可以了。如下所示:
假设我们在后端这边没设置解码方式的话,使用汉字就可能出现乱码的情况,如下所示:(因此我们如果需要使用汉字就需要注意编码和解码的问题,否则非常容易出现乱码问题。)
注意事项:当我们通过Query String传递参数时,如果涉及到使用汉字进行传参,为了保险起见,可以先把汉字通过转码工具转成编码来传参,如下所示:(以下是将张三转成了对应的编码来传参):
2.3.3、通过body传参(json)重点
json本身也是键值对格式的数据,但是Servlet本身没有内置json解析的功能,因此我们需要引入第三方库,常用的解析json的第三方库有很多,比如fastjson,gson,jackson等等,这里我们使用jackson,jackson也是Spring官方指定的。
第一步:引入jackson
引入jackson我们可以去MVN中央仓库引入即可
点击进去之后有很多的版本,这些版本都差别不大,任选一个下载就可以:
复制进去之后点击刷新之后就会自动下载,等到字体变白了就可以了,太久没变白也可以尝试重启以下IDEA就可以了。
解析json格式数据代码展示:
import com.fasterxml.jackson.core.FormatSchema;
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.util.HashMap;
class User{
public String username;
public String password;
}
@WebServlet("/json")
public class JsonServlet extends HttpServlet {
//ObjectMapper是一个可以将Java对象和json格式互转的对象,json中的一个核心类
private ObjectMapper objectMapper = new ObjectMapper();
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//使用readValue方法来将请求中的json格式数据转成Java对象,并且存入对象中
//使用HashMap来接收对象
// HashMap<String,String> info = objectMapper.readValue(req.getInputStream(), HashMap.class);
// System.out.println(info);
//也可以创建一个类来接收json格式的数据
User user = objectMapper.readValue(req.getInputStream(), User.class);
System.out.println("username="+user.username+";password="+ user.password);
resp.getWriter().write("ok");
}
}
对于代码中的readValue方法的解释:readValue方法有很多重载的方法,其中我们上面使用的都是第一个参数都是传入的是一个字节流对象,第二个参数是一个类对象,在这里使用Map和自己创建的类都是可以的,但是使用自己创建的类的话会相对灵活一点。相应的,writeValue就是将我们这边的数据转成json格式数据并写进响应中。
readValue方法在解析过程中到底做了什么:
①解析json字符串,转换成若干键值对;
②根据第二个参数,去找到类对象中的所有public属性,一次遍历;
③遍历属性,根据属性名字,去上述准备好的键值对中,查询,匹配,看看这个属性名字是否存在对应的value,如果存在及将value的值赋给该属性。(这里类里面的属性名和传过来的json数据中键的属性名要一致才可以,需要事先约定好)
三、HttpServletResponse
3.1、核心方法
方法 | 描述 |
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 中写入二进制格式数据. |
注意: 响应对象是服务器要返回给浏览器的内容, 这里的重要信息都是程序猿设置的. 因此上面的方 法都是 "写"(set) 方法.
3.2、代码展示
以下是设置自动刷新及设置状态码:
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 {
//设置状态码
resp.setStatus(200);
resp.setContentType("text/html;charset=utf8");
resp.getWriter().write("返回200响应<br>");
//设置自动刷新,显示当前时间戳
resp.setHeader("Refresh","1");
resp.getWriter().write(String.valueOf(System.currentTimeMillis()));
}
}
效果展示:这里时间戳是会一直刷新的
设置重定向跳转:以下代码的效果是输入之后直接跳转到搜狗主页
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","https://www.sogou.com");
//resp.sendRedirect("https://www.sogou.com");//和上面的两行代码是等效的
}
}
输入对应的网址后就会跳转到搜狗主页,这就是重定向:
四、简单的留言墙网站
网站介绍:
这个留言墙的网站主要是基于Servlet的,主要的功能就是:进入网站之后,就将所有的留言展示出来,留言的数据是保存在数据库中的。。每次访问网站都会将数据库中已有的数据加载出来,不提供修改留言和删除留言的功能,只能新增留言,这个练习的目的就是为了练习使用Servlet来进行前后端的交互。
网站的效果图:
网站的源代码可以参照我的gitee链接: MessageWall · 白志彬/java -code - 码云 - 开源中国 (gitee.com)