irpas技术客

Servlet基本原理与常见API方法的应用_未见花闻_site:csdn.net

网络投稿 6133

??前面的话??

本篇文章将介绍Servlet的基本原理已经Servlet API中常用的一些方法,我们将利用这些方法实现一些Servlet的小案例。

📒博客主页:未见花闻的博客主页 🎉欢迎关注🔎点赞👍收藏??留言📝 📌本文由未见花闻原创,CSDN首发! 📆首发时间:🌴2022年7月2日🌴 ??坚持和努力一定能换来诗与远方! 💭参考书籍:📚《暂无》 💬参考在线编程网站:🌐牛客网🌐力扣 博主的码云gitee,平常博主写的程序代码都在里面。 博主的github,平常博主写的程序代码都在里面。 🍭作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!


📌导航小助手📌 🍎1.Tomcat如何调用Servlet🍏1.1Servlet原理🍏1.2Tomcat的执行逻辑 🍎2.Servlet中关键的几个API🍏2.1常用方法列举🍏2.2Post请求的构造🍏2.3获取请求信息🍏2.4Post请求信息的获取🍏2.5响应的构造



🍎1.Tomcat如何调用Servlet 🍏1.1Servlet原理

Servlet是属于上层建筑,它处在应用层,它的下层有传输层,网络层,数据链路层,硬件,属于“经济基础”,毕竟下层经济基础决定上层建筑。前面说过,Servlet是一组操作HTTP的API,Tomcat可作为HTTP服务器来处理请求,这个处理请求的关键就是调用Servlet来操作HTTP给客户端做出响应。

我们所写的Servlet代码没有main方法,那他是如何运行的呢?其实是Tomcat在调用Servlet,Tomcat其实就是一个应用程序,是运行在用户态上的一个普通的Java进程。 当浏览器发送请求给服务器的时候,Tomcat作为HTTP Server会调用Serlvet API,然后执行我们所写的Servlet程序来处理请求。 处理请求的过程中牵涉的不仅仅只有HTTP,还有其他层的协议,但是我们并没有感知到其他层协议的细节,只关注了应用层HTTP协议的细节,这就是协议分层好处,程序员在实现处理请求时,不必去关心应用层下面的细节。

🍏1.2Tomcat的执行逻辑

为了方便描述Tomcat的执行逻辑,我们使用伪代码的形式来分析:

初始化与收尾工作,又细分为以下部分: 1)从指定的目录中找到Servlet类,并加载。 2根据加载的结果,给这些类创建实例。 3)创建好实例后,调用Servlet对象中的init方法。 4)创建TCP socket对象,监听8080端口,等待客户端来连接。 5)如果请求处理完毕,也就是处理请求的循环退出了,那Tomcat也结束了,调用destroy方法结束进程,但是这个环节不一定可靠,正常退出的情况下,需要在管理端口(8005)去调用destroy,将Tomcat关闭,但很多时候都是直接杀死进程来达到关闭的目的,此时根本来不及调用dsetroy方法。

class Tomcat { // 用来存储所有的 Servlet 对象 private List<Servlet> instanceList = new ArrayList<>(); public void start() { // 根据约定,读取 WEB-INF/web.xml 配置文件; // 并解析被 @WebServlet 注解修饰的类 // 假定这个数组里就包含了我们解析到的所有被 @WebServlet 注解修饰的类. Class<Servlet>[] allServletClasses = ...; // 这里要做的的是实例化出所有的 Servlet 对象出来; for (Class<Servlet> cls : allServletClasses) { // 这里是利用 java 中的反射特性做的 // 实际上还得涉及一个类的加载问题,因为我们的类字节码文件,是按照约定的 // 方式(全部在 WEB-INF/classes 文件夹下)存放的,所以 tomcat 内部是 // 实现了一个自定义的类加载器(ClassLoader)用来负责这部分工作。 Servlet ins = cls.newInstance(); instanceList.add(ins); } // 调用每个 Servlet 对象的 init() 方法,这个方法在对象的生命中只会被调用这一次; for (Servlet ins : instanceList) { ins.init(); } // 利用我们之前学过的知识,启动一个 HTTP 服务器 // 并用线程池的方式分别处理每一个 Request ServerSocket serverSocket = new ServerSocket(8080); // 实际上 tomcat 不是用的固定线程池,这里只是为了说明情况 ExecuteService pool = Executors.newFixedThreadPool(100); while (true) { Socket socket = ServerSocket.accept(); // 每个请求都是用一个线程独立支持,这里体现了我们 Servlet 是运行在多线程环境下的 pool.execute(new Runnable() { doHttpRequest(socket);//处理请求 }); } // 调用每个 Servlet 对象的 destroy() 方法,这个方法在对象的生命中只会被调用这一次; for (Servlet ins : instanceList) { ins.destroy(); } } public static void main(String[] args) { new Tomcat().start(); } }

Tomcat处理请求工作: 1)读取socket中的数据,并按照HTTP协议的格式来进行解析,获取请求。 2)判断请求是需要静态内容还是动态内容,如果是静态内容,可以在根路径上找到目的文件,返回请求 3)如果是动态文件,则需要通过URL上的一级路径与二级路径来确定通过哪一个Servlet类来进行处理,没有的话就会返回404 4)找到对应Servlet对象,调用对象里面的service方法,根据请求的方法来调用对应的do...方法

class Tomcat { void doHttpRequest(Socket socket) { // 参照我们之前学习的 HTTP 服务器类似的原理,进行 HTTP 协议的请求解析,和响应构建 HttpServletRequest req = HttpServletRequest.parse(socket); HttpServletRequest resp = HttpServletRequest.build(socket); // 判断 URL 对应的文件是否可以直接在我们的根路径上找到对应的文件,如果找到,就是静态 // 直接使用我们学习过的 IO 进行内容输出 if (file.exists()) { // 返回静态内容 return; } // 走到这里的逻辑都是动态内容了 // 根据我们在配置中说的,按照 URL -> servlet-name -> Servlet 对象的链条 // 最终找到要处理本次请求的 Servlet 对象 Servlet ins = findInstance(req.getURL()); // 调用 Servlet 对象的 service 方法 // 这里就会最终调用到我们自己写的 HttpServlet 的子类里的方法了 try { ins.service(req, resp); } catch (Exception e) { // 返回 500 页面,表示服务器内部错误 } } }

service方法执行逻辑:

class Servlet { public void service(HttpServletRequest req, HttpServletResponse resp) { String method = req.getMethod(); if (method.equals("GET")) { doGet(req, resp); } else if (method.equals("POST")) { doPost(req, resp); } else if (method.equals("PUT")) { doPut(req, resp); } else if (method.equals("DELETE")) { doDelete(req, resp); } ...... } }

在整个流程中,有三个关键的方法:

init方法,在初始化阶段执行,用来初始化每一个Servlet对象,对象创建好之后,就会执行,用户可重写该方法,来执行一些初始化程序的逻辑,没有重写,init方法一般是空的,也就是什么也不执行。destroy方法,退出请求处理的主循环之后,tomcat结束之前就会执行到该方法,用来释放资源。service方法,在处理请求的阶段调用,针对每个动态资源的请求都需要调用。 🍎2.Servlet中关键的几个API 🍏2.1常用方法列举

HttpServlet关键方法:

方法名称调用时机init在 HttpServlet 实例化之后被调用一次destory在 HttpServlet 实例不再使用的时候调用一次service收到 HTTP 请求的时候调用doGet收到 GET 请求的时候调用(由 service 方法调用)doPost收到 POST 请求的时候调用(由 service 方法调用)doPut/doDelete/doOptions/…收到其他请求的时候调用(由 service 方法调用)

这些方法的调用时机,就构成了“Servlet”的生命周期。

HttpServletRequest关键方法:

方法描述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 对象.

HttpServletResponse关键方法:

方法描述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 中写入二进制格式数据.
🍏2.2Post请求的构造

在同一webapp里面,关联路径不能够相同,不然Tomcat跑不起来,对于GET请求,可以使用URL的查询字符串进行构造,但是POST请求不行,需要使用form或者ajax。

构造Post请求(使用ajax构造): 在webapp目录下创建一个HTML文件,用来构造POST请求,首先我们先的引入jquery依赖(博主使用的是本地导入,你可以如果网络地址:https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js导入依赖),然后调用ajax构造请求。

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <script> $.ajax({ type: "post", url: "method", success: function (body){ console.log(body); } }) </script>

注意上面的URL属性不能加/,加上表示的就是绝对路径了,当然你也可以使用./来表示相对路径,但是在Servlet注解关联路径必须得加上/。

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("/method") public class MethodServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.getWriter().write("POST请求"); } }

我们访问http://127.0.0.1:8080/hello_servlet/test.html来看控制台输出的返回结果。 我们发现与我们的预期不一致,我们处理请求的时候返回了POST请求,而这里显示了POST??,原因是发生了乱码,idea默认编码格式为utf-8,Windows默认的编码格式是gbk,那浏览器解析body的时候也是以gbk格式去进行解析,要想统一格式,就得先告诉浏览器响应数据的编码格式是什么,我们需要在Servlet程序里面设置字符格式,设置方法为调用HttpServletResponse对象的setContentType方法,传入参数text/html; charset=utf8。

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("/method") public class MethodServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html; charset=utf8"); resp.getWriter().write("POST请求"); } }

重新打包部署,刷新页面:

🍏2.3获取请求信息

对于请求的信息,我们运用HttpServletRequest类的方法来进行请求信息的获取: 比如我们访问的url为http://127.0.0.1:8080/hello_servlet/showreq?key=10&a=100&b=200,很明显这是使用查询字符串构造的一个GET请求,通过HttpServletRequest类一系列对应的方法,我们可以获取到这个请求的方法类型,协议版本,URL,查询字符串,头部的一些信息等。其中查询字符串与头部信息的获取先要使用getParameterNames方法或者getHeaderNames方法获取所有的查询字符串或头部信息的所有key值,这个一个枚举对象,然后在根据getParameter或者getHeader方法通过key值遍历枚举对象获取value。

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("/showreq") public class ShowRequestServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //访问链接:http://127.0.0.1:8080/hello_servlet/showreq?key=10&a=100&b=200 StringBuilder stringBuilder = new StringBuilder(); resp.setContentType("text/html; charset=utf-8"); //1协议名称与版本 stringBuilder.append("协议版本:"); stringBuilder.append(req.getProtocol()); stringBuilder.append("<br>"); //2方法类型 stringBuilder.append("方法:"); stringBuilder.append(req.getMethod()); stringBuilder.append("<br>"); //3获取查URL路径 stringBuilder.append("URL路径:"); stringBuilder.append(req.getRequestURI()); stringBuilder.append("<br>"); //4URL(不包括查询字符串后面的部分) stringBuilder.append("URL(不包括查询字符串后面的部分):"); stringBuilder.append(req.getRequestURL()); stringBuilder.append("<br>"); //5一级路径 stringBuilder.append("一级路径:"); stringBuilder.append(req.getContextPath()); stringBuilder.append("<br>"); //6查询字符串 stringBuilder.append("查询字符串:"); stringBuilder.append(req.getQueryString()); stringBuilder.append("<br>"); //7正文编码格式 stringBuilder.append("正文编码格式:"); stringBuilder.append(req.getCharacterEncoding()); stringBuilder.append("<br>"); //8mine stringBuilder.append("mine:"); stringBuilder.append(req.getContentType()); stringBuilder.append("<br>"); //9正文长度 stringBuilder.append("正文长度:"); stringBuilder.append(req.getContentLength()); stringBuilder.append("<br>"); //10获得每一个查询字符串的键值: stringBuilder.append("<h3>获得每一个查询字符串的键值:</h3>"); Enumeration query = req.getParameterNames(); while(query.hasMoreElements()) { String key = (String)query.nextElement(); stringBuilder.append(key); stringBuilder.append(":"); stringBuilder.append(req.getParameter(key)); stringBuilder.append("<br>"); } //11获得头部的键值 stringBuilder.append("<h3>获得头部的键值:</h3>"); Enumeration header = req.getHeaderNames(); while(header.hasMoreElements()) { String key = (String)header.nextElement(); stringBuilder.append(key); stringBuilder.append(":"); stringBuilder.append(req.getHeader(key)); stringBuilder.append("<br>"); } resp.getWriter().write(stringBuilder.toString()); } }

结果:

🍏2.4Post请求信息的获取

我们知道post请求的请求信息在http格式中的body部分当中,而body中的请求内容的格式是有很多种的,比如最常见的有:

x-www-form-urlencode格式,通过form表单或者postman构造。json格式form-data格式

x-www-form-urlencode格式:

k e y = v a l u e & k e y = v a l u e & . . . key=value\&key=value\&... key=value&key=value&...

form表单创建x-www-form-urlencode格式请求:

<!DOCTYPE html> <html lang="ch"> <head> <meta charset="UTF-8"> <title>post</title> </head> <body> <form action="./postParameter" method="post" accept-charset="utf-8"> <span>userId</span> <input type="text" name="userId"> <span>classId</span> <input type="text" name="classId"> <input type="submit" value="提交"> </form> </body> </html>

Servlet程序接收和处理请求: 对于x-www-form-urlencode格式请求可以直接使用HttpServletRequest中的getParameter方法依据key来获取value,然后再将获取到的数据返回,form表单构造的请求会自动跳转页面。

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("/postParameter") public class GetPostParameterServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //获取post请求body请求中的参数 //设置请求与响应编码格式 req.setCharacterEncoding("utf-8"); resp.setContentType("text/html; charset=utf8"); //比如useId = classId= String userId = req.getParameter("userId"); String classId = req.getParameter("classId"); //写会数据 resp.getWriter().write("userId=" + userId + ", " + "classId=" + classId); } }

运行结果:

json格式: { \{ { k e y : v a l u e , key:value, key:value, k e y : v a l u e , key:value, key:value, k e y : v a l u e , key:value, key:value, . . . ... ... } \} }

对于json格式,手动解析不容易,因为json里面的字段是可以嵌套的,但我们可以借助第三方库来解析处理json,比如Jackson,Jackson依赖导入过程如下:

处理json请求步骤: 第一步,在前端js代码中构造出格式为json格式的请求。 其中ajax构造post请求,使用contentType来说明请求的类型,data属性来设置body的内容。

<!DOCTYPE html> <html lang="cn"> <head> <meta charset="UTF-8"> <title>json</title> </head> <body> <!-- 前端HTML部分 --> <input type="text" id="userId"> <input type="text" id="classId"> <input type="button" id="submit" value="提交"> <!-- 如果已经准备好本地的jQuery就导入本地的,否则可以找网上的jQuery cdn网络路径 --> <script src="./jquery3.6.0.js"></script> <script> let userIdInput = document.querySelector("#userId"); let classIdInput = document.querySelector("#classId"); let button = document.querySelector("#submit"); button.onclick = function() { $.ajax({ type : "post", url: "getJsonPost", contentType: "appliaction/json", data:JSON.stringify({ userId: userIdInput.value, classId:classIdInput.value }), success: function(body){ console.log(body); } }) } </script> </body> </html>

第二步,在java后端代码中使用Jackson处理。

1)创建Jackson核心对象ObjectMapper对象。2)读取请求中的body信息,该过程通过ObjectMapper对象的readValue方法实现。3)创建用来接受json数据的类。4)readValue方法的参数有两个,第一个参数用来表示请求的来源,可以是路径字符串,与可以是InputSream对象,也可以是File对象,第二个参数表示接收json数据的类对象。5)处理并响应请求。 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; class User { public String userId; public String classId; } @WebServlet("/getJsonPost") public class GetJsonPostServlet extends HttpServlet { //1.创建一个Jackson的核心对象 private ObjectMapper objectMapper = new ObjectMapper(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //格式 resp.setContentType("text/html; charset=utf8"); //处理 //2.读取body请求的内容,使用ObjectMapper对象的readValue方法来解析 //就是将字符串转换成java的对象,readValue方法的第一个参数可以是路径字符串可以是输入流对象,引入可以是File对象 //第二个参数,表示需要将请求的json格式数据转换成哪一个java对象 User user = objectMapper.readValue(req.getInputStream(), User.class); System.out.println(user.userId); System.out.println(user.classId); resp.getWriter().write("userId=" + user.userId + " ,classId=" + user.classId); } }

运行结果:

readValue方法基本原理:

读取json格式的数据,并解析成键值对。便利这些键值对,获得key,并与所需传入的对象中的属性(反射)相比,如果key与属性的名字相同,则把key对应的value赋值给这个属性,否则就跳过,所有的键值对便利完后,这个对象差不多就被构造的差不多了。 🍏2.5响应的构造

案例1: 设置响应状态码

设置方法很简单,只需要调用httpServletResponse对象中的setStatus方法就可以了

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.setContentType("text/html; charset=utf8"); //设置状态码 int status = 200; resp.setStatus(status); resp.getWriter().write("hello,这是" + status + "状态码的响应内容!"); } }

启动程序,网页显示如下:

我把状态码修改为404,网页显示如下:

那为什么不是之前我们所遇到的那种404页面呢?这是因为我们设置的页面响应内容就是hello,这是404状态码的响应内容!,可以理解为自定义的404状态响应页面,就像其他的网站,如果访问不到页面,显示的提醒页面也是不一样的,比如b站的页面是这个样子的: 案例2:自动页面刷新

自动页面刷新只要在响应中设置一个header: Refresh就能实现页面的定时刷新了,对于响应header的设置,我们可以通过HttpServletResponse对象中的setHeader方法来设置Refresh属性和刷新频率。

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("/autorefresh") public class AutoRefreshServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //格式 resp.setContentType("text/html; charset = utf8"); //设置Refresh,第二个参数表示刷新频率,单位是秒 resp.setHeader("Refresh", "1"); //响应 resp.getWriter().write("时间戳:" + System.currentTimeMillis()); } }

效果:

案例3:重定向案例 第一步,设置状态码为302。 第二步,设置header:Location,调用setHeader方法时,第一个参数填Location,表示设置header字段为Location,第二个参数为重定向的目的地址,你要重定向到哪一个网址就传入哪一个地址的字符串。

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.setContentType("text/html; charset = utf8"); //设置状态码 resp.setStatus(302); //设置重定向字段与地址,如跳转到力扣官网 resp.setHeader("Location", "https://leetcode.cn/"); } }

效果:

当然,servlet提供了更为简便的重定向方法,就是使用HttpServletResponse类中的sendRedirect方法。

@WebServlet("/redirect") public class RedirectServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //格式 resp.setContentType("text/html; charset = utf8"); //设置状态码 // resp.setStatus(302); // //设置重定向字段与地址,如跳转到力扣官网 // resp.setHeader("Location", "https://leetcode.cn/"); resp.sendRedirect("https://leetcode.cn/"); } }

效果与上面第一种方法是一样的。


下期预告:表白墙小案例

觉得文章写得不错的老铁们,点赞评论关注走一波!谢谢啦!


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #sitecsdnnet