【JavaEE】Servlet的API详解

本文涉及的产品
云解析 DNS,旗舰版 1个月
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
简介: 【JavaEE】Servlet的API详解

JavaEE & Servlet的API详解


对于Tomcat服务器的代码实现,就不做讲解了,本质上就是一个TCP服务器,感兴趣的可以去了解!


接下来就是Servlet的API(有很多很多)

主要就是三个重点类:


HttpServlet

HttpServletRequest

HttpServletResponse

而后面两个类,只要你熟悉HTTP协议,我想你也会知道怎么使用(猜)~


1. HttpServlet抽象类


每一个Servlet程序都要继承这个HttpServlet类,重写其中的方法

25fe6b3e41114899b92567e8e786b1df.png


既然如此,我们就需要熟悉我们能重写什么方法,以及这些方法有什么用~


按住ctrl点进HttpServlet:


25561e288a6c453e9e6d610bf6ff03df.png


方法名称 调用时机

init 在HttpServlet实例化之后呗调用一次

destory 在HttpServlet实力不再使用的时候调用一次

service 收到HTTP请求的时候调用

doGet 顾名思义

doPost 顾名思义

doPut/doDelete/doOptions/… 顾名思义


1.1 init方法


初始化相关的工作

HttpServlet被实例化之后会被调用一次


即,首次收到匹配的请求(建立连接请求)的时候,实例化


04e5cdefff67451a8e3b6dbf16526c60.png

记得打开服务器

记得发送请求(建立连接,后得到request)

所以init的打印在helloworld之前(doGet之前),且只调用了一次:



1bd5071e921a4e5c8fdbb33a4a2188c3.png


1.2 destroy方法


这个方法是该webapp被卸载,被销毁之前执行一次

做一些收尾工作

示例:


编写代码,启动服务器再关闭:


de36c5cfe34f4be881646d554e6109cd.png

然而好像并没有执行这个语句


实际上,destroy不太靠谱

通过8005管理端口,来停止服务区,此时destroy都执行

直接通过杀死进程(ctrl + f2),此时destroy执行不了

常见的简单粗暴的方法

所以不建议使用destroy方法,因为可能也不作数呀~


8005管理端口


前面提到,Tomcat用到两个端口

8080业务端口

8005管理端口

这就类似于,一个人的两个微信

工作微信,(同事客户领导…)

生活微信,(家人朋友…)

而不同的端口,就会有不同的请求响应


8080就是我们的业务,我们需要通过这个端口定位到服务器的位置,以及其部署的资源

8005则是做一些加载配置,重新启动,调整设置项…

其中就包括,关闭服务器


1.3 service方法


每次收到路径匹配的请求,都会执行

这是一个写好了的子框架!


doGet和doPost…都会在service中被调用,所以我们一般是不会重写这个方法,而是重写doGet和doPost就行了


init在连接时刻下调用一次

destroy在结束之前调用一次

service每次收到路径匹配的请求都会调用一次

Servlet的生命周期


一个事物的“一生”,各个阶段干什么,就是生命周期

doGet等doXXX方法,已经写过很多次了,不做讲解~


举一反三~

响应乱码问题:


6a3b0538f5db4ecbac68f946c28ea118.png


原因就是,数据返回的编码方式和浏览器展示的编码方式,两者对应不上


统一编码方式即可


IDEA默认返回的编码方式为utf8

java中,char类型是unicode(两个字节)


但是String类型的字符是utf8的(三个字节)


只不过java中不太留意字节大小,所以无感

这也解决粘包问题(unicode的硬伤)

而我们只需要通过一些类进行套壳(Scanner),就会自动解析字节流了


至于char数组到String的转化,java帮你做了,不必留意这些细节~

而java中sout的时候,都是转化为字符串输出的~

浏览器是根据系统默认的编码方式走的,(Windows11默认gbk)

gbk:两个字节去表示,能表示的汉字有限~


utf8:三个字节,可以表示丰富的文字


f3482be4a9da408bb85054a0318b86a6.png


关闭再启动:


d440d06c5b4b4071803520a2ddc184a9.png



2. HttpRequest接口


一个HTTP请求里有啥,这个对象中就有啥


方法

URL(host,queryString)

版本号

header

body

Cookie

而这些内容,在这个对象中,都可以用对应的方法进行获取和设置~-


方法总览:


不需要我们重写,因为这些是因为doXXX方法的传参向上转型,已经是框架重写好的了~


dd3324baff604d2abfc57c240f8b69b5.png

Protocol:

协议名称和版本号


URL 与 URI:

前者是网络资源定位符,后者是网络资源标识符

特别相似,相似到我们很多时候**直接混着用~**


cd25af4bf30241d7a605504d9628f109.png

5fc11a04454a491eb8f6f06f31081528.png



Parameter:参数

其实就请求中的键值对

Enumeration:列举

是请求中所有键值对的所有key的名称

querystring

form(body)

前者就是获取参数名称(所有key),后者通过key获取value(首个)


3084f41b29cc4251a2694677ea348ef2.png

key是可以对应多个值的(在querystring允许key重复出现)


所以此方法能够获取key对应的value的字符串数组

很罕见,这种方法,一般有这种特殊要求,服务器也是知道的,所以也会调用这个方法

(不会出现使用了只能获取一个value的那个方法,因为都是约定好的)

处理header的方法:

8ab5af9c1f8340389bcade78c7c84b94.png



类似:


前者是获取header里的所有key,后者是获取key对应的value


一般header的key是不重复的(一般不会用getHeaders)

获取特定的header的key对应的值:

不需要输入名字字符串,而是通过编译器提词

9fcec391425f449c96a3c9507f2fb053.png


获取字节输入流对象:


用Scanner套壳可以处理字节输入流

进一步去读取body的内容!(压缩的body会被自动转换,不必担心)


除了文本/二进制模式外的格式,随后讲解

而这些格式的读写是得通过第三方库(依赖)的

自己构造太麻烦了

表中未出现的其他方法,暂时不讲,随用随查随学!


2.1 在浏览器上显示请求首行


@WebServlet("/show")//没有分号~
public class ShowRequest extends HttpServlet {
        StringBuilder result = new StringBuilder();
        result.append(req.getProtocol());//版本号
        result.append('\n');
        result.append(req.getMethod());
        result.append('\n');
        result.append(req.getRequestURI());
        result.append('\n');
        result.append(req.getQueryString());//无字符串
        result.append('\n');
        result.append(req.getContextPath());
        result.append('\n');
        resp.getWriter().write(result.toString());
    }
}


启动服务器,通过浏览器发送请求~

4e372b6abc43494f98c23c2c1b575164.png

自己搞个queryString:

a60e5c2019e04ba090af55c5a222f90b.png


如果响应的body是html格式的,则回车符就不能生效了~


设置响应的body格式(提前一提),用的是setContentType方法


dfebeaf429dc4fda938f83af26aeddd5.png

如果不改回车符:

重新启动服务器哦!

342aa2f601654c799131b442acb5e4d9.png


没有起到换行作用,而是相当于在html代码中按了一下回车,展示效果其实就是一个空格~

用<br>代替:


1edb9899980c4693aacbd98dbd4c1c71.png



2.2 在浏览器上显示请求header


result.append("<hr>");
Enumeration<String> headerNames = req.getHeaderNames();
//用不了for each语法,因为这个集合类没继承那个集合接口,也不是数组~
//而其本身,也可以看做是自身的迭代器
while(headerNames.hasMoreElements()) {
    String name = headerNames.nextElement();
    String value = req.getHeader(name);
    result.append(name);
    result.append(": ");
    result.append(value);
    result.append("<br>");
}
resp.setContentType("text/html; charset=utf8");
resp.getWriter().write(result.toString());


这个headerNames对象,本身是一个集合,也是一个自身的一个迭代器~

效果:


7a3ac1f3502e4b2485b45d5da1e64bf4.png


2.3 getParameter方法 - 最常用的API之一


前端给后端传递数据,是很常见的:


querystring传递

body(form)

body(json)

result.append("<hr>");
Enumeration<String> keys = req.getParameterNames();
//同样可以迭代~
while(keys.hasMoreElements()) {
    String key = keys.nextElement();
    String value = req.getParameter(key);
    result.append(key);
    result.append("=");
    result.append(value);
    result.append("<br>");
}
result.append("<hr>");
String value1 = req.getParameter("a");
String value2 = req.getParameter("b");
String value3 = req.getParameter("c");
result.append(value1 == null ? "noFound" : value1);
result.append("<br>");
result.append(value2 == null ? "noFound" : value2);
result.append("<br>");
result.append(value3 == null ? "noFound" : value3);
result.append("<br>");


效果:


1ee47e86ea0944e3805c233f7f212bf8.png


querystring传递

body(form)

body(json)


@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //获取请求的body
    StringBuilder result = new StringBuilder();
    Enumeration<String> keys = req.getParameterNames();
    while(keys.hasMoreElements()) {
        String key = keys.nextElement();
        String value = req.getParameter(key);
        result.append(key);
        result.append("=");
        result.append(value);
        result.append("<br>");
    }
    resp.setContentType("text/html; charset=utf8");
    resp.getWriter().write(result.toString());
}



通过Postman构造post请求发送个服务器


166757283ce948ffb98476b814b32b03.png


可见,获取到了body里的form相关的键值对


中文乱码现象1:


在querystring中写中文也是有风险的(成功也只不过凑巧可以打印)

querystring的键值对包含中文/特殊字符,就需要用urlencode的方式进行转码,否则存在风险(querystring的机制似乎优化过了,软件/浏览器支持的很好)

进行转码是有必要的,减少变数


733bc3c7f6d349f1889a2d5be8b0905c.png

如果出现乱码,服务器将无法正确识别,从而无法返回正确的响应!!!

UrlEncode编码和UrlDecode解码-在线URL编码解码工具

通过转码工具转码:


4e4f57d697e6415db0d33dde6e8de84c.png


作为后端程序员,一般也感知不到这个问题,因为我们获得乱码,那肯定是前端程序员的锅~


自己的前后端分离的项目也是要注意一下的

中文乱码现象2:


在form(body)的键值对中,前端传过来的utf8,而后端并不清楚它就是utf8,所以后端代码不知道/用其他编码方式读取,就会乱码

d46a9f45b9fe425992cf66d005ee0a56.png


显示告知编码方式:


51f5deaca96744dcbdb8caf357608faf.png


请求本来就有一套编码方式,这个操作设置的是后端代码怎么识别这串请求的

效果:


bb991c50a1f24bd8b51c202d5a96ef14.png


2.4 json(body)的解析与构造


querystring传递

body(form)

body(json)

肥肠重要!

json一般有对象构造而来,以对象来理解,所以通过String识别字符串(key与value),即utf8,所以不告知编码方式也行

用getParameter方法的话,似乎是摄取不到json里面的键值对的


因为Servlet是没有内置解析json的功能的~


ffe556a9198644e6a452713aa97babfe.png

没有关系!我们只需要引入一个第三方库(一个依赖)


json的依赖,有很多

用法差不多,功能相似,例如fastjson、gson、jackson…

jackson是spring官方的指定产品(提前认识点,后续spring有关操作也恰好要用到jackson)

Maven Repository: Central (mvnrepository.com)


中央仓库里去下载/复制代码

搜jackson,选择这个:


e42574fe055a4b37aebd3545ed06625f.png


我选择的是这个版本:

32b19573947b433291cc523e49ba61d9.png

复制代码:


<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>

3bec0218b7f147a68e4e433cc6fef9fe.png


在pom.xml中引入依赖:

刷新触发加载~

b1ec3dc3a962403584a15ed58fffdb6b.png


2.4.1 ObjectMapper类


private ObjectMapper objectMapper = new ObjectMapper();
//解析json的核心类就是,ObjectMapper


这个类我的理解就是:对象映射制造器

object即对象,map即映射

通过对象内部的映射关系,制作json格式的字符串

通过json格式的字符串,构造对象

class User {
    public String username;
    private String password;
    public double score;
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}


2.4.1 通过json构造对象


用readValue方法(readValues的话,就是传过来的body是json数组)


传入req的输入流(输入,即喂给后端代码)

传入类对象(类名.class)

这个方法通过这个类名(普通类,不能是抽象类/接口),构造对类名对应的对象!

没错,底层它会通过反射(只有这个反射才能通过类名通过成员名方法名去设置和获取)去构造一个对象,


json的key对应成员名,value对应成员的值


不存在的还是默认值~

private或者其他此时权限不够的修饰符修饰成员,必须有对应的getter和setter,才能被反射获取到!才能正确制造对象


注意:setter和getter用编译器快速构造即可(alt + insert)

boolean外的其他类型,都是getXxx和setXxx

boolean类型,则是isXxx和setXxx

这个方法的重载方法很多,你甚至可以传一个字符串和类对象就够了

1e027f8ec38644d79d2823cf3b5ca700.png



@WebServlet("/show_json")
public class showJSON extends HttpServlet {
    private ObjectMapper objectMapper = new ObjectMapper();
    //解析json的核心类就是,ObjectMapper
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        User user = objectMapper.readValue(req.getInputStream(), User.class);
        resp.setContentType("application/json; charset=utf8");
    }
}



启动服务器,通过Postman发送请求:

0501cfc7478648ec94ef8fcd028297e6.png



这里是500,就是认定请求的body为标准,只是后端代码(服务器那边)没有做好匹配


错误1 :


成员的类型与值不匹配

36b25c9c7929444d8c207e38b1cdbacb.png


错误2:

不存在此成员:

7b05012e1b874d6f9b0679464da5a51c.png


请求的key值较少,无所谓~


竟然是映射关系,那么也可以用Map储存:


这样用的话,我们还要手写成员名才能获取value(这两个编译器都不会提词)

麻烦!

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    req.setCharacterEncoding("utf8");
    //User user = objectMapper.readValue(req.getInputStream(), User.class);
    //System.out.println(user.username + " " + user.getPassword() + " " + user.score);
    resp.setContentType("application/json; charset=utf8");
    Map<String, String> map = objectMapper.readValue(req.getInputStream(), HashMap.class);
    System.out.println(map.get("username") + " " +
                       map.get("password") + " " + map.get("score"));
}


重新启动服务器,发送post请求:


a815a8e2ef6c4334bdc6029f40826be9.png


2.4.3 通过对象构造json:


一般构造json,是为了写入响应返回给客户端,所以从习惯上是客户端发送GET请求,所以重写doGet方法


用writeValueAsString方法

5454c3d58e994205b53aa122ef1ebfd0.png


传入任何对象,都会帮你转换为json

基本数据类型/String/包装类/非复杂(自定义)类型的数组和集合类:


"value.toString()"
//基本数据类型就是:value
//数组就是Arrays.toString(value)

48b5b06f06dc40c2b22604e32c9fab63.png


自定义类


{
    "成员1": "value1",
    "成员2": "value2",
    //......
    "成员n": "valuen"//最后没有逗号
}




自定义类的数组/集合


[
    {
        //json1
    }
    {
      //json2
    }
  //  ...
  {
        //json_n
    }
]

92c31e2d6e5741e8bee12b324984f018.png



注意:


如果对象没有new出来,还是null,那么转换的也就是null(没有指向)

c6d181d024df4a3fb0b39f711a504909.png



自带映射的Map


{
    "key1": "value1",
    //...
    "keyn": "value2"
}

491f9bc221af42ef97d985b9054d0dc4.png


3. HttpResponse接口


表示一个HTTP响应,响应有什么,里面就有什么~


只讲几个常用的~

7e034aaf30ba408b952e048638bc9aa2.png


处理header的方法

8b42a49d4ad9436cae3eeb60938abde6.png



set => key存在,覆盖;key不存在,创建一个key: value键值对

add => key存在/不存在:创建一个key: value键值对

即,header可能出现重复

设置body的格式,设置字符集…

490cfc36ac5c4525a1b57a22a5c4204c.png



设置body格式后面加个“; charset=XXX”,也能起到设置字符集的作用


b20141322f4c4e14b9a28dd7dd48a53f.png

设置重定向


c5037c743c6841e0a1685555007962ce.png


获得输出字符流,输出字节流(输出,即后端代码投喂给响应)


3.1 设置首行(版本号固定)


@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setStatus(200);
    resp.getWriter().write("200响应");
}



抓包:


Content-Length等非自定义的header属性自动补上了~


db21ac22359b43a18311a4aa1f59ef7c.png


3.2 设置格式和字符集


1ac34f11fe274e79a224bf2a2fa12310.png


3.3 自动刷新页面


通过setHeader方法,对Refresh属性进行设置value

含义就是,在value秒后,页面自动刷新(浏览器)

但是如果浏览器有互动性的东西,就不会触发刷新(因为强制刷新,就会清空用户输入了的东西,影响体验)

 

@Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/html; charset=utf8");
        //不设置状态码,默认为200 ok,状态码描述跟状态码对应的,并不需要设置
        resp.setHeader("Refresh", "1");
        resp.getWriter().write(String.valueOf(System.currentTimeMillis()));
    }




虽然不能精确到1000ms整,但是很接近1s了(机器也要工作时间嘛~)


3.4 设置重定向


3.4.1 设置状态码 + header属性Location


protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setContentType("text/html; charset=utf8");
    resp.setStatus(302);
    resp.setHeader("Location", "https://www.bilibili.com/");
}


效果:

image.png



3.4.2 调用sendRedirect方法


是等价于上述两步的

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    resp.setContentType("text/html; charset=utf8");
    resp.sendRedirect("https://www.bilibili.com/");
}

效果一致~

目录
相关文章
|
8月前
|
API
05JavaWeb基础 - Servlet的相关API
05JavaWeb基础 - Servlet的相关API
24 0
|
1月前
|
XML 网络协议 Java
JavaEE精选-Servlet
JavaEE精选-Servlet
22 0
|
1月前
|
JSON Java 应用服务中间件
|
10月前
|
JSON 应用服务中间件 API
【计算机网络】Servlet API重点知识汇总
【计算机网络】Servlet API重点知识汇总
|
11月前
|
网络协议 应用服务中间件 API
Servlet的常用Api—HttpServletResponse
Servlet的常用Api—HttpServletResponse
Servlet的常用Api—HttpServletResponse
|
JavaScript 前端开发 Java
【JavaEE】使Cookie与Session失效-Servlet上传文件操作-优化表白墙
虽然Cookie和Session都是暂时存在的,不久就会被删掉,但是我们要退出登录的时候,就不能等待其自然消除了~
54 0
|
存储 前端开发 Java
【JavaEE】使Cookie与Session失效-Servlet上传文件操作-优化表白墙
虽然Cookie和Session都是暂时存在的,不久就会被删掉,但是我们要退出登录的时候,就不能等待其自然消除了~
84 0
|
API
java202304java学习笔记第六十二天-ssm-获取servlet相关api
java202304java学习笔记第六十二天-ssm-获取servlet相关api
61 0
|
Web App开发 Java API
《Servlet和JSP学习指南》一第1章 Servlet 1.1 Servlet API概述
本节书摘来自华章出版社《Servlet和JSP学习指南》一书中的第1章,第1.0节,作者(加)Budi Kurniawan,更多章节内容可以访问云栖社区“华章计算机”公众号查看
1399 0
|
4天前
|
自然语言处理 安全 API
触发邮件接口有哪些?邮件API文档
**触发邮件接口**如AokSend、Mailgun、Amazon SES、Postmark和Sendinblue是自动化企业通信的关键。这些接口在特定事件时自动发送邮件,提高效率和客户体验。例如,AokSend提供详细的API文档,支持事件触发、模板管理和多语言集成;Mailgun以灵活性著称;Amazon SES适合大规模发送;Postmark专注于事务邮件;Sendinblue则提供邮件、短信和营销自动化功能。每种服务都有示例代码展示如何使用API发送邮件。选择合适的接口能提升企业通信效率和客户满意度。