文章目录
- Java 官方提供了 Servlet 标准,Java 官方写好一套接口就和抽象类,用于连接 Tomcat 开发者 和 应用开发者的桥梁。Servlet 是一种实现动态页面的技术,是一组 Tomcat 提供的 API, 帮助我们简单高效的开发一个 web app。
1、 servlet 开发流程
1.1 开发阶段
- servlet path
- 设置pom.xml 文件
<!--设置打包方式为 war 方式 -->
<packaging>war</packaging>
<!-- 设置 maven 的编译器版本是 1.8 -->
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<!-- 添加 servlet 依赖 scope = provided表示这个依赖下的所有类只在开发、构建需要,运行时 Tomcat 内部已经有这套类了 -->
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
查找标准的依赖写法:https://search.maven.org/artifact/javax.servlet/javax.servlet-api/3.1.0/jar
- 新建存在静态资源的目录
- 设置Web应用配置
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
</web-app>
- 新建java文件,动态资源
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.io.PrintWriter;
/*
1. 继承 HttpServlet 抽象类
2. 通过 @WebServlet 注解,修饰类,填写资源路径,必须以 / 开头
3. 重写 doGet(...) 方法
*/
@WebServlet("/first")
public class FirstServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
/*
1. 设置响应头 Content-Type:
*/
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
/*
2. 准备响应的正文部分
*/
String s = "你好中国";
/*
3. 拿到写入正文的 writer
*/
PrintWriter writer = resp.getWriter();
/*
4. 写入正文
*/
writer.println(s);
}
}
1.2 构建阶段
-
context path
-
编译(build)----》打包(Package)----》部署(deploy)
-
也可以使用 build artifact
-
使用专业版 IDEA 自带 Tomcat 插件启动
1.3 运行阶段
- 运行 Tomcat,动态资源被修改以后,必须要重新启动 Tomcat,才可以生效。
- 在浏览器中访问资源。URL = context path + servlet path 。
1.4 常见错误
1.4.1 Tomcat 没有启动
- 之前有写过 Tomcat 无法启动的解决方案:https://blog.csdn.net/ccyzq/article/details/122182173
1.4.2 资源 URL 错误
- HTTP:404
- URL = context path(web 应用) + servlet path(各个资源)。如果在浏览器中没有输入正确的 URL ,就会出现 404 。
1.4.3 服务器内部出现问题
- 例如 动态资源中的代码出现异常,就会出现 HTTP:500 错误。
1.4.4 排查错误思路
- 小明写了一个 Web 应用,启动 Tomcat 后,访问了一个 URL 是 /hello.do 的资源,出现以下现象时,小明的怀疑是否合理?
- 1、浏览器出现 404 ,小明怀疑自己的 Tomcat 启动失败了。
不合理,浏览器出现 404 ,就说明有一个 web 服务器告诉浏览器资源是不存在的,所以,web 服务器一定是启动成功的。
但是在某些场景下是合理的,例如之前安装过类似 Tomcat 这样的 Web 服务器,这次 Tomcat 启动失败,看到的 404 是之前安装的 web 服务器所导致的。
总结:看到了HTTP 响应(1xx、2xx、3xx、4xx、5xx),都表示 web 服务器应答浏览器。没有启动只会显示无法访问,不能看到状态码。 - 2、看到资源出现404,小明怀疑自己的资源路径放置错误。
有合理性,但是还有可能是自己的 URL 输入错误。 - 3、看到资源出现500时,小明怀疑自己的资源路径放置错了/ URL输入错误。
不合理,出现500,直接看页面上的提示信息就可以。 - 4、通过开发者工具看到响应是200,但是浏览器上没有显示。
HTTP 响应 = 控制数据 (响应行 + 响应头) + 负荷数据(响应体)。说明该资源的响应行和响应头是正确的,响应体(正文)可能是空,或者其他原因导致空白的内容(html 中 < body> 下为空… ) - 5、出现405状态码。
- Method Not Allowed:客户端的问题。使用了错误的 HTTP 访问某个资源,就是 URL 对应的资源存在,但是访问的方法不对。
- 不重写 doGet 方法,通过 GET 方法访问时,实际上会执行 对象doGet 方法(METHOD_NOT_ALLOWED),就会出现 405 。
- 想要通过 post 方法访问某个资源:1、通过 form 表单,指定 method 属性。2、通过 js 代码来实现。
<form method="post" action="method">
<button>通过 post 访问</button>
</form>
1.4.5 Tomcat 打印日志信息
- 可以自己添加打印等工作。
public class method extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("运行到这里");
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
resp.getWriter().println("我是处理 method 的 POST 方法的");
}
}
1.4.6 浏览器开发者工具
- 元素面板(HTML、CSS)
- 网络面板(HTTP协议)
- 前端调试面板(JS)
- 控制台面板(JS)
1.4.7 IDEA 调试器
- 主要调试后端
2、Servlet 运行基本原理
- 当浏览器给服务器发送请求的时候, Tomcat 作为 HTTP 服务器, 就可以接收到这个请求。
- Tomcat = HTTP 服务器(对接浏览器)+ Servlet 容器(对接 Web 应用)。
- HTTP 协议作为一个应用层协议, 需要底层协议栈来支持工作。
-
Tomcat的工作:
- 1、通过网络读取 HTTP 请求,并解析。
- 2、判断是 静态/动态 资源。
- 3、动态资源找到对应的 Servlet 对象去处理,静态资源找路径对应下的文件,如果找不到就是404。
- 4、根据动态资源的不同方法,来调用 doGet/doPost 。
- 5、根据请求内容来填充响应对象。※
- 6、根据响应对象来组装 HTTP 相应数据。
- 7、通过网络把响应发送给浏览器。
3、Servlet 的使用
3.1 读取 HTTP 请求内容
- 请求方法、资源路径、请求头
3.2 发送响应
- ① 设置状态码,不修改的情况下就是200。
resp.setStatus(200)
。 - ② 这是响应头,
resp.setCharacterEncoding("utf-8");resp.setContentType("text/plain");
text/html、text/css、application/javascript、application/json。 - ③ 发送正文,
resp.getWriter().println();
3.3 获取浏览器(用户)提交的数据
- ① GET 方法获取(参数来自 URL 的 querystring 部分,?key1=value1&key2=value2)
package com.cc.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;
import java.io.PrintWriter;
@WebServlet("/get-param")
public class GetParam extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
String age = req.getParameter("age");
System.out.printf("name = %s, age = %s\n",name, age);
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
PrintWriter writer = resp.getWriter();
writer.printf("name = %s, age = %s\n",name, age);
}
}
- ② 通过 POST 发送的参数(指的是 form 表单发送)。来自 query string,请求体。
<form method="post" action="post-param">
<input type="text" name="name">
<input type="text" name="age">
<button>提交</button>
</form>
@WebServlet("/post-param")
public class PostParam extends HttpServlet{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("utf-8");
String name = req.getParameter("name");
String age = req.getParameter("age");
System.out.printf("name = %s, age = %s\n", name, age);
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
PrintWriter writer = resp.getWriter();
writer.printf("name = %s, age = %s\n",name, age);
}
}
- ③ 读取用户通过 form 表单提交的参数(get/post),都可以加上
req.setCharacterEncoding("utf-8");String name = req.getParameter("name");String[] names = req.getParameterValues("name");
3.4 关联数据库操作
- 1、引入 JDBC 依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
- 2、创建库、表,并添加数据
3.4.1 裸文本
package com.cc.servlet;
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 java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/user-list.txt")
public class UserListServlet extends HttpServlet {
//使用 text/plain 形式显示
// 通过添加打印信息, 观察中间过程的数据正确性
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
List<User> userList = queryFromDB();
System.out.println(userList);
String responseBody = userListToString(userList);
System.out.println(responseBody);
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
PrintWriter writer = resp.getWriter();
writer.println(responseBody);
} catch (SQLException exc) {
// 由于 SQLException 不在方法签名可以抛的异常列表内,并且不能更改方法签名,否则就不是方法重写了
throw new ServletException(exc);
}
}
private String userListToString(List<User> userList) {
StringBuilder sb = new StringBuilder();
for (User user : userList) {
sb.append(user.toString());
sb.append("\r\n");
}
return sb.toString();
}
private List<User> queryFromDB() throws SQLException {
List<User> userList= new ArrayList<>();
MysqlDataSource db = new MysqlDataSource();
db.setServerName("127.0.0.1");
db.setPort(3306);
db.setUser("root");
db.setPassword("123456");
db.setDatabaseName("db_12_28");
db.setUseSSL(false);
db.setCharacterEncoding("utf8");
db.setServerTimezone("Asia/Shanghai");
try (Connection c = db.getConnection()) {
String sql = "SELECT uid, name, age FROM users ORDER BY uid ASC";
try (PreparedStatement ps = c.prepareStatement(sql)) {
System.out.println(ps);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
User user = new User(
rs.getInt("uid"),
rs.getString("name"),
rs.getInt("age")
);
userList.add(user);
}
}
}
}
return userList;
}
}
User.java
package com.cc.servlet;
public class User {
public Integer uid;
public String name;
public Integer age;
public User(Integer uid, String name, Integer age) {
this.uid = uid;
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"uid=" + uid +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
3.4.2 html
package com.cc.servlet;
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 java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/user-list.html")
public class UserListHtmlServlet extends HttpServlet {
// 用 text/html 形式显示
// 通过添加打印信息, 观察中间过程的数据正确性
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
List<User> userList = queryFromDB();
System.out.println(userList);
String responseBody = userListToString(userList);
System.out.println(responseBody);
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/html");
PrintWriter writer = resp.getWriter();
writer.println(responseBody);
} catch (SQLException exc) {
// 由于 SQLException 不在方法签名可以抛的异常列表内,并且不能更改方法签名,否则就不是方法重写了
throw new ServletException(exc);
}
}
// 以 table 形式展示
private String userListToString(List<User> userList) {
StringBuilder sb = new StringBuilder("<!DOCTYPE html>\n" +
"<html lang=\"en\">\n" +
"<head>\n" +
" <meta charset=\"UTF-8\">\n" +
" <title>用户列表</title>\n" +
" <link rel=\"stylesheet\" href=\"css/style.css\">\n" +
"</head>\n" +
"<body>\n");
sb.append("<table border=\"1\">\n");
for (User user : userList) {
sb.append("<tr>\n");
sb.append("<td>").append(user.uid).append("</td>\n");
sb.append("<td>").append(user.name).append("</td>\n");
sb.append("<td>").append(user.age).append("</td>\n");
sb.append("</tr>\n");
}
sb.append("</table>");
sb.append("</body>\n" +
"</html>");
return sb.toString();
}
private List<User> queryFromDB() throws SQLException {
List<User> userList= new ArrayList<>();
MysqlDataSource db = new MysqlDataSource();
db.setServerName("127.0.0.1");
db.setPort(3306);
db.setUser("root");
db.setPassword("123456");
db.setDatabaseName("db_12_28");
db.setUseSSL(false);
db.setCharacterEncoding("utf8");
db.setServerTimezone("Asia/Shanghai");
try (Connection c = db.getConnection()) {
String sql = "SELECT uid, name, age FROM users ORDER BY uid ASC";
try (PreparedStatement ps = c.prepareStatement(sql)) {
System.out.println(ps);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
User user = new User(
rs.getInt("uid"),
rs.getString("name"),
rs.getInt("age")
);
userList.add(user);
}
}
}
}
return userList;
}
}
使用结构化方式进行输出,结构化的方式有很多。结构化实际上是对象序列化的一种。
3.4.3 JSON(JavaScript Object Notation)
-
JSON是一种基于文本的轻量级的数据交换格式可以被任何编程语言读取和作为数据的格式传递。
-
JS 对象格式
{ “key1”:“value1”, “key2”:“value2” }
-
数组格式
[ “item1”,“item2” ]
-
使用 java 中的第三方库来完成:
- jackson:推荐,在 Spring 中默认使用的 jackson
- gson:google公司
- fast-json:阿里
导入依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>
package com.cc.servlet;
import com.fasterxml.jackson.core.JsonProcessingException;
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 java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
@WebServlet("/user-list.json")
public class UserListJSONServlet extends HttpServlet {
//使用 text/plain 形式显示
// 通过添加打印信息, 观察中间过程的数据正确性
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
List<User> userList = queryFromDB();
System.out.println(userList);
String responseBody = userListToString(userList);
System.out.println(responseBody);
resp.setCharacterEncoding("utf-8");
resp.setContentType("text/plain");
PrintWriter writer = resp.getWriter();
writer.println(responseBody);
} catch (SQLException | JsonProcessingException exc) {
// 由于 SQLException 不在方法签名可以抛的异常列表内,并且不能更改方法签名,否则就不是方法重写了
throw new ServletException(exc);
}
}
private String userListToString(List<User> userList) throws JsonProcessingException {
ObjectMapper om = new ObjectMapper();
return om.writerWithDefaultPrettyPrinter().writeValueAsString(userList);
}
private List<User> queryFromDB() throws SQLException {
List<User> userList= new ArrayList<>();
MysqlDataSource db = new MysqlDataSource();
db.setServerName("127.0.0.1");
db.setPort(3306);
db.setUser("root");
db.setPassword("123456");
db.setDatabaseName("db_12_28");
db.setUseSSL(false);
db.setCharacterEncoding("utf8");
db.setServerTimezone("Asia/Shanghai");
try (Connection c = db.getConnection()) {
String sql = "SELECT uid, name, age FROM users ORDER BY uid ASC";
try (PreparedStatement ps = c.prepareStatement(sql)) {
System.out.println(ps);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
User user = new User(
rs.getInt("uid"),
rs.getString("name"),
rs.getInt("age")
);
userList.add(user);
}
}
}
}
return userList;
}
}