初识 Servlet
Servlet 是什么
Servlet 是 Java 编写的服务端程序,用于处理客户端(通常是浏览器)发送的请求,并向客户端返回响应。它是Java EE 规范的一部分,提供了一种在服务器上动态生成内容的方式。
Servlet 是基于Java 的实现的,因此具有跨平台性,可以在不同的操作系统上运行。它遵循 Java 的安全性和可靠性,也能充分利用 Java 语言的优势,如面向对象编程和丰富的类库。
Servlet 主要用于开发 Web应用程序,如网站、Web服务、Web应用程序后端等。Tomcat 容器会在接收到请求的时候将其传递给相应的Servlet 类进行处理。同时,Servlet 可以根据请求参数、会话状态、数据库查询等动态生成HTML、XML、JSON等内容,然后将响应发送给客户端。
总而言之,Servlet 是一种实现动态页面的技术,是一组 Tomcat 提供给开发人员的 API,帮助开发人员能够简单高效的开发 Web 程序。
Servlet 的主要工作
Servlet 在 Java Web 开发中主要负责以下工作:.
注册 Servlet 类并处理 HTTP 请求:
- Servlet 允许程序员注册一个 Java 类,并在 Tomcat收到特定的 HTTP 请求时执行该类中的代码。通过在web.xml 文件来配置 Servlet 映射,将 Servlet 与特定的URL或URL关联起来
- 当客户端发送匹配的 HTTP 请求时,Servlet 容器将实例化并调用相应的 Servlet 来处理请求。开发者可以在 Servlet 中编写业务逻辑,从请求中获取参数、执行数据库操作、生成动态内容等,并生成HTTP响应返回给客户端。
解析 HTTP 请求
- Servlet 帮助程序员解析 HTTP 请求,将客户端发送的 HTTP 请求从字符串解析成一个
HttpRequest
对象。
HttpRequest
对象提供了各种方法,使得开发者能够轻松地获取 HTTP 请求的方法(GET、POST等)、请求头、请求参数、会话信息等。
构造 HTTP 响应
Servlet 帮助程序员构造HTTP响应,使得开发者只需设置指定的 HttpResponse 对象的属性字段,Servlet 会自动根据 HTTP 协议的方式构造出一个 HTTP 响应字符串,并通过 Socket 写回给客户端。
HttpResponse 对象提供了设置响应状态码、响应头、响应内容等的方法,开发者可以根据业务需求进行设置,Servlet 会将设置的响应信息返回给客户端。
总的来说,Servlet 作为 Java 的服务器端技术,帮助开发者处理 HTTP 请求和响应,允许注册和执行特定的 Java 类,解析 HTTP 请求,并根据设置的 HttpResponse 对象构造 HTTP 响应。这使得 Java 开发者能够轻松地开发动态、交互式的 Web 应用程序。
Servlet 和 Tomcat 的关系
Servlet 和 Tomcat 之间有着密切的关系,可以说它们是一对密不可分的组合,以下是对它们之间关系的解释:
Tomcat 是 Servlet 的容器,它提供了 Servlet 的执行环境和支持,使得 Servlet 能够在 Tomcat 服务器上运行。
当在 Tomcat 上部署一个 Web 应用程序时,Tomcat 会负责加载和初始化其中的 Servlet 对象,并在接收到 HTTP 请求时调用相应的Servlet 对象来处理请求和生成响应。
Tomcat 还提供了管理和监控 Web 应用程序的功能,可以通过 Tomcat 的管理界面来管理已部署的 Servlet 和 Web 应用程序。
总结而言,Servlet 是 Java 编写的服务器端程序,而 Tomcat 是一个能够执行和管理 Servlet 的容器。它们共同组成了Java Web 应用程序的基础架构,使得开发者能够构建动态、交互式的 Web 应用程序。
第一个 Servlet 程序
创建 Maven 项目
pom,xml
文件:pom.xml
是 Maven 项目的核心配置文件,它位于项目的根目录下。pom.xml
文件用于描述项目的元数据信息(如项目的名称、版本、描述等),定义项目的依赖关系(所需的第三方库和插件),配置项目的构建过程(编译、打包等),以及其他项目相关配置。
在 IDEA 中新建一个项目,在
Build System
选项中选择 Maven,注意项目路径不要出现中文或其他特殊符号。
引入依赖
当 Maven 项目创建完成后,会自动生成一个
pom.xml
文件,我们需要在pom.xml
文件中引入 Servlet API 所依赖的JAR
包。
- 在中央仓库(https://mvnrepository.com)中搜索
servlet
,一般第一个结果就是我们所需要的 API。
2.选择版本,这里我选择的是
3.1.0
版本。
关于 Servlet 和 Tomcat 版本的匹配问题:
可以在 https://tomcat.apache.org/whichversion.html 上查询版本对应关系。
3.将中央仓库提供的
Maven xml
复制粘贴到pom.xml
文件中
注意要新增一个 dependencies
标签,将其复制到此标签中:
创建目录
当创建完 Maven 项目后,IDEA 会自动帮我们生成如下的目录结构:
- 创建 webapp 目录
- 创建
WEB-INF
目录- 创建
web.xml
文件
编写代码
编写 web.xml
文件
往
web.xml
文件中拷贝以下代码:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
编写java文件
在 java 目录中创建一个 HelloServlet 类,代码如下:
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// super.doGet(req, resp);
System.out.println("hello world");
resp.getWriter().write("hello world");
}
}
对这段代码的解释
- 创建一个继承自 HttpServlet 的 HelloServlet 类;
- 在这个类上方加上 @WebServlet("/hello") 的注解,用于指定 Servlet 的 URL 映射。在这个示例中,Servlet 将被映射到 URL 路径为/hello,当客户端发起对/hello的 HTTP 请求时,HelloServlet 将被调用。
- 重写 doGet 方法,doGet 的参数有两个,分别表示收到的 HTTP 请求和要构造的 HTTP 响应。这个方法会在 Tomcat 收到 GET 请求时触发被 Tomcat 自动调用。
- HttpServletRequest 表示 HTTP 请求,Tomcat 按照 HTTP 请求的格式把字符串格式的请求转成了一个 HttpServletRequest 对象,后续想获取请求中的信息(比如方法、url、header、body 等)都是通过这个对象来获取。
- HttpServletResponse 表示 HTTP 响应,需要程序员在代码中把这个对象构造好(如构造响应的状态码、header、body 等)。
- resp.getWriter() 会获取到一个 Writer 字符流对象,通过这个流对象就可以写入一些数据,写入的数据会被构造成一个 HTTP 响应的 body 部分,Tomcat 会把整个响应转成字符串,通过 socket 写回给客户端。
虽然这几行代码很少,但是缺包含了大量的信息:
1. 在 Servlet 程序的代码中,不需要自己编写 main 方法作为程序的入口了,因为 main 方法已经包含在 Tomcat 里面了,我们自己编写的代码会在合适的时候被 Tomcat 所调用。(这个后面会深入讲解如何实现)
2. 我们写的代码并不是一个完整的程序,而是 Tomcat 这个程序的一小部分逻辑,如果要想在合适的时候正确的被 Tomcat 调用,需要满足一下三个条件:
1)创建的类需要继承自 HttpServlet 类;
2)这个类需要使用 @WebServlet 注解关联上一个 HTTP 的路径;
3) 这个类需要实现 doXXX 方法。
打包程序
在 IDEA 中可以使用 Maven 进行打包,一般在 IDEA 右侧就可以看到这个 Maven 窗口
修改 pom.xml
配置文件,以生成 WAR
包和修改 WAR
包名称
点击package
当出现如下信息,则表示打包成功:
打包成功后,可以看到在 target
目录下,生成了一个 JAR
包:
部署程序
将上面打包形成的 WAR
包文件复制到 Tomcat 的 webapps
目录下
运行 Tomcat(start),会将其自动解压,此时便部署完成。然后生成java109这个文件夹
验证程序
此时通过浏览器访问http://127.0.0.1:8080/java109/hello 就可以看到以下结果了
Smart Tomcat 部署程序
以上的手动打包,然后拷贝到 Tomcat 的过程显得非常繁琐,但是 IDEA 上为我们提供了更加方便的打包部署程序的插件。“Smart Tomcat” 插件就是是为了简化和改进在 IntelliJ IDEA 中使用 Apache Tomcat 的体验而开发的。使用 “Smart Tomcat” 插件,可以更方便地配置和管理 Tomcat 服务器,轻松部署和调试 Java Web 应用程序。
安装 Smart Tomcat 插件
在 IDEA 上打开:File -> Settings -> plugins
,然后搜索 Smart Tomcat,然后按照重启 IDEA 即可。
配置 Smart Tomcat 插件
- 在 IDEA 右上角点击 “Current File”
2.然后点击 “Edit Configurations”
点击做上角的+,然后点击左侧的smart Tomcat
然后进行如下配置
content path是你接下来将要使用的上级路径
当配置完成后,我们会发现开始的 “Current File” 位置就变成了 “run”,然后点击旁边的绿色三角形,就能够自动运行 Tomcat 并部署我们的程序了。
运行程序
此时 Tomcat 日志就会输出在 IDEA 的控制台中,并且不会出现乱码
注意:这时候要先退出Tomcat,不然进程会结束
访问服务器
这里如果正常写英文的话应该是没有问题的,但是有点时候,如果你写汉字的话就可能会出现问题,因为浏览器默认是的用jdk,而idea默认的是utf8,所以俩个直接就可能出现乱码的情况,我们这里可以修改浏览器,也可以修改idea,但是由于utf8一般是通用的语言,所以我们修改浏览器。(浏览器默认是jdk,当我们不主动确认的时候,它就默认jdk了,所以我们可以让他明确一下)
访问出错的原因
出现404
404 表示用户访问的资源不存在,大概率是
URL
的路径写的不正确。
错误示例一:少写了 “Context Path”,直接通过 “/hello” 访问服务器
错误示例二:少写了 “Servlet Path”,直接通过 “/hello_servlet” 访问服务器
出现405
405 表示对应的 HTTP 请求方法没有实现。
例如,现在将HelloServlet类中重写的 doGet方法注释掉,然后重启 Tomcat 再次访问服务器
出现“无法访问此网站”
出现这种情况一般就是没有启动 Tomcat 或者启动 Tomcat 服务器失败。
比如,当 “Servlet Path” 写错的时候,就会出现启动 Tomcat 服务器失败。
此时,启动 Tomcat,就会出错
访问服务器就会出现 “无法访问此网站”
Servlet 运行原理
在 Servlet 的代码中我们并没有写 main 方法, 那么它是如何运行的?那么对应的 doGet 代码是如何被调用的呢?响应又是如何返回给浏览器的?
其实是Tomcat在调用Servlet,我们在重写doGet和doPost方法的时候,启动Tomcat来运行,当浏览器给服务器发送请求的时候,Tomcat作为HTTP服务器,就可以接收到这个请求。重写的doGet和doPost方法会在Tomcat内部被自动调用执行,Tomcat 程序可以理解为是一个普通的Java进程。
Servlet API
主要就是三个重点类:
- HttpServlet(抽象类)
- HttpServletRequest
- HttpServletResponse
HttpServlet(抽象类)
我们写 Servlet 代码的时候, 首先第一步就是先创建类, 继承自 HttpServlet, 并重写其中的某些方法,每一个Servlet程序都要继承这个HttpServlet类~~~
核心方法:
我们实际开发的时候主要重写 doXXX 方法, 很少会重写 init / destory / service,这些方法的调用时机, 就称为 "Servlet 生命周期". (也就是描述了一个 Servlet 实例从生到死的过程)
要注意的是,destory到底能否被执行,是不确定的,如果我们通过IDEA的停止按钮来关闭服务器,这个本质操作是通过Tomcat的8005管理端口,主动停止才能触发destroy
直接通过杀死进程(ctrl + f2),此时destroy执行不了,而这种暴力的关闭方式却是更方便更常用的, 所以不建议在 destroy 内执行有效代码,所以destory是非常尴尬的
前面提到,Tomcat用到两个端口
- 8080业务端口
- 8005管理端口
这就类似于,一个人的两个微信
- 工作微信,(同事客户领导…)
- 生活微信,(家人朋友…)
而不同的端口,就会有不同的请求响应
8080就是我们的业务,我们需要通过这个端口访问服务器,以及其部署的资源
8005则是做一些加载配置,重新启动,调整设置项…
其中就包括,关闭服务器我们java网页开发一般接触的都是业务端口而不是管理端口
HttpServletRequest
一个HTTP请求里有啥,这个对象中就有啥
- 方法
- URL(host,queryString)
- 版本号
- header
- body
- Cookie
- …
核心方法:
Protocol:
协议名称和版本号
URL 与 URI:
URL是唯一资源定位符,URI是唯一资源标识符,特别相似,相似到我们很多时候直接混着用
Parameter:参数
• 其实就请求中的键值对
Enumeration:列举
• 是请求中所有键值对的所有key的名称
• 用不了for each语法,因为这个集合类没继承那个集合接口,也不是数组~ 而其本身,也可以看做是自身的迭代器
Header:请求头
HttpServletRequest常用方法演示
@WebServlet("/showRequest")
public class ShowRequest extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, 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());
}
}
前端给后端传输数据的三种方式
前端给后端传输数据, 是非常常见的需求, 常见的有以下三种方式:
- 发送
Get
请求通过query string
传输数据- 发送
Post
请求通过form
提交数据- 发送
Post
请求通过json
格式提交数据
发送Get请求通过query string传输数据
我们约定前端通过URL来传递 username 和 password 这两个信息,url为:http://localhost:8080/servletDemo/getParameter?username=zhangsan&password=123
由于我们这里的
key
值是前后端交互前提前约定好的, 所以我们可以直接使用getParameter
方法通key
从req
中得到value
, 然后在后端我们就可以根据前端传来的数据构造响应返回给前端.
@WebServlet("/getParameter")
public class GetParameter extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, 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");
}
当然, 在不知道 query string 的 key 的情况下也是可以使用 getParameter 拿到查询字符串的各个键值对的, 可以先使用 getParameterNames方法获取所有的查询字符串的所有 key 值, 这个一个枚举对象, 然后再根据 getParameter 方法通过 key 值遍历枚举对象获取 value
@WebServlet("/getParameter")
public class GetParameterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
StringBuilder stringBuilder = new StringBuilder();
Enumeration query = req.getParameterNames();
while(query.hasMoreElements()) {
String key = (String)query.nextElement();
stringBuilder.append(key + ": " + req.getParameter(key));
stringBuilder.append("<br>");
}
}
}
由于这里的规则是前后端所约定好的,所以不必担心对应不上的情况
发送Post请求通过form提交数据
使用 Post 请求来传递数据, 数据此时是在 HTTP 格式中的 body 部分的, 使用 from 表单进行构造, 此时 body 中的请求内容的格式 (Content-Type) 是 application/x-www-form-urlencode 格式, 在形式上和 query string 是一样的, 后端仍然使用 getParameter 来获取.
1.前端创建页面testPost.html
,里面创建一个 form
表单
<form action="postParameter" method="post">
<input type="text" name="studentId">
<input type="text" name="classId">
<input type="submit" value="提交">
</form>
2.后端创建一个 PostParameterServlet 类,用于接收 form表单提交的数据
@WebServlet("/postParameter")
public class PostParameterServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf8");
String studentId = req.getParameter("studentId");
String classId = req.getParameter("classId");
resp.getWriter().write("studentId: " + studentId + ", classId: " + classId);
}
}
3.访问服务器页面,然后提交参数
localhost是本地的意思,一般来说可以替代为127.0.0.1
4.提交数据后会自动跳转至响应页面
5.通过 Fiddler
工具抓包,可以发现通过 POST
方法传递的参数在 body
中
通过 JSON 获取 POST 请求中的参数
json格式:
[
{
key1: value,
key2: value
},
{
key3: value,
key4: value
},
...
]
使用 Post 请求传输数据, 还可以使用当前比较主流的
json
数据格式组织body
中的数据, 它也是键值对格式的,用getParameter方法的话,似乎是取不到json里面的键值对~
对于 json 格式, Servlet 自身是没有内置 json 的解析功能的, 如果我们自己进行手动解析并不容易
那么我们就需要引入json依赖,json依赖有很多,用法差不多,功能相似,例如fastjson、gson、jackson…
• jackson是spring官方的指定产品(后续spring有关操作也恰好要用到jackson)
1. 约定前后端交互接口
POST /jsonPostParameter
2. 后端获取 body
并通过 Jackson
解析 JSON 字符串
在Java代码中,需要引入
Jackson
这个库,完成对 JSON 的解析工作:1.在中央仓库中搜索
jackson
,选择 “JackSon Databind”:
2.把中央仓库中的依赖拷贝到
pom.xml
文件中,形如:
3.创建一个JsonPostServlet
类:
// 使用 jackson, 最核心的对象就是 ObjectMapper
// 通过这个对象, 就可以把 json 字符串解析成 java 对象; 也可以把一个 java 对象转成一个 json 格式字符串.
ObjectMapper objectMapper = new ObjectMapper();
- 通过对象内部的映射关系,制作json格式的字符串
- 通过json格式的字符串,构造对象
通过json构造对象
用readValue方法(readValue的话,就是传过来的body是json数组)
User user = objectMapper.readValue(req.getInputStream(), User.class);
1.读取 body 中 json 格式的数据字符串, 并解析成若干键值对.
2.根据第二个参数实体类对象, 创建 User 实例.
3.遍历解析出来的键值对, 获得 key, 并与所需传入的对象中的属性相比, 如果 key 与属性的名字相同, 则把 key 对应的 value赋值给这个属性(通过反射完成).
4.返回该 User 对象.
通过对象构造json字符串
一般构造json,是为了写入响应返回给客户端
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, 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");
}
}
Student
类用于储存 JSON 解析出来的数据
4.启动服务器,然后通过 Postman 工具发送 JSON 格式的数据
服务器响应内容
这块知识虽然已经不再是主流,但是它的底层原理对我们后续学习spring有一定的帮助,万字解析,希望对大家有所帮助