3 Java Servlet
Goal
- Recap the basic concepts of HTTP request and response
- Create your first servlet to implement a simple sevice.
A message body is the one that carries the actual HTTP request data (including from data and uploaded etc.) and HTTP response data from the server (including files, image, etc. ) and HTTP response data from the server ( including files, images, etc). Normally we don't return static HTML code to the front end directly because it should be created by a front-end developer. We just need to return the correct data that should be displayed by the frontend. In our project, we'll use JSON for body format.
Java Class:
JSON(JavaScript Object Notation) Format
HelloServlet Update
- Open your project in Intellij, and verify that HelloServlet exists.
HelloSerlvet
:
package com.example.onlineorder;
import java.io.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
// 其中包括 I/O 库和 Jakarta Servlet 库
// 这个注解定义了这个 Servlet 的名称为 "helloServlet" 和其 URL 模式为 "/hello-servlet"。这意味着当访问该URL时,将执行此Servlet
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
//定义了一个 HelloServlet 类,它继承了 HttpServlet 类。这允许我们处理 HTTP 请求
private String message;
//这是一个私有字符串成员变量,用于存储要显示的消息
public void init() {
message = "Hello World!";
}
// 此方法用于处理 HTTP GET 请求。当浏览器或其他客户端通过 GET 方法请求该 Servlet 时,这个方法被调用
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
// 设置响应内容的类型为 HTML
// 下面的代码用于生成和发送一个简单的 HTML 响应,它显示了先前在 init 方法中设置的 message
// Hello
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>" + message + "</h1>");
out.println("</body></html>");
}
//当 Servlet 结束其生命周期时,此方法将被调用。在这个例子中,它是空的,因为没有特定的资源需要清理
public void destroy() {
}
}
The HelloServlet can handle different HTTP requests like POST
and GET
. Our job is to fill in code handling our business logic. It's like filling a template, you'll see this type of customization in many places. This is the power of inheritance and object-oriented programming.
- Let's say we want to use the HelloServlet to serve welcome requests. Clients will send the request with a user name as the parameter, and we're going to return the welcome message for the user. To handle the parameter from the HTTP request, in the
HelloServlet
, remove the implementation:
package com.example.onlineorder;
import java.io.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
private String message;
public void init() {
message = "Hello World!";
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html");
// Hello
String customer = request.getParameter("customer");
PrintWriter out = response.getWriter();
out.println("<html><body>");
out.println("<h1>Hello " + customer + "</h1>");
out.println("</body></html>");
}
public void destroy() {
}
}
Now build and run the Tomcat server by clicking the green button. Try this link http://localhost:8080/hello-servlet?customer=Eve
- It's recommended to use Postman from now on. It's more flexible and powerful.
- Notice how space is handled? The browser converts the space to "%20" and Tomcat converts it back.
- So far, the HelloServlet only returns the html text result to the clients. As we mentioned before, JSON is the recommended format for client-server communication. Now let's try to return some JSON format data in the HelloServlet. Open the pom.xml file, add the following at the end inside the
<dependencies> </dependencies>
body. Don't put it anywhere else.
<!-- ... existing dependencies -->
<!-- Only insert the following, do not change or touch other lines -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20230618</version>
</dependency>
# 这个库允许你在 Java 中处理 JSON。它提供了一套简单的类来编码和解码 JSON 文本,
# 以及将 JSON 与 XML、HTTP 头部和其他数据进行转换
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
# Apache Commons IO 是一个包含实用程序类、流实现、文件过滤器和 endian 转换类的库。
# 它通常用于各种 I/O 操作,如复制文件、从流中读取等
<!-- Only insert the above, do not change or touch other lines -->
</dependencies>
在 pom.xml
文件中,这些依赖项被添加是为了告诉 Maven 下载并包含它们在项目的类路径中。当你使用 Maven 构建项目时,它会从 Maven Central Repository(或其他配置的仓库)中检索这些库,并使它们可以供你的项目使用.
Then reload and install Maven changes by opening the Maven tool window on the right side.
Then will see BUILD SUCCESS
information from the console output
[INFO] Installing /Users/eve/Desktop/Projects/OnlineOrder/Restaurant-Website/Code/OnlineOrder/target/OnlineOrder-1.0-SNAPSHOT.war to /Users/eve/.m2/repository/com/example/OnlineOrder/1.0-SNAPSHOT/OnlineOrder-1.0-SNAPSHOT.war
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.228 s
[INFO] Finished at: 2023-10-23T18:17:49-05:00
[INFO] ------------------------------------------------------------------------
You can get rid of the warning File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent! by add UTF-8 property in the pom.xml
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<junit.version>5.9.2</junit.version>
</properties>
- Now the JSON library is ready, we can use it to return JSON format data in the Servlet:
package com.example.onlineorder;
import java.io.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import org.json.JSONObject;
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
private String message;
public void init() {
message = "Hello World!";
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("application/json");
JSONObject customer = new JSONObject();
customer.put("email", "test@gmail.com");
customer.put("first_name", "Eve");
customer.put("last_name", "L");
customer.put("age", 60);
response.getWriter().print(customer);
}
public void destroy() {
}
}
Now re-run the server by clicking the green button. Open postman and put http://localhost:8080/hello-servlet in the address bar and click send. You'll get a JSON response.
Let's come back and look at the code.
- What you saw is a JSON object. It's like a map, which contains multiple key-value pairs. It's a contract between the two sides.
- We specified the content-type
application/json
. This is the process of content negotiation. Other types of content can be passed too, as long as the client is able to understand it. - Notice how clumsy to manually write the field names? You can easily make mistakes. We'll learn how to avoid writing them every time. There's a term called "Boilerplate code".
- That was read by
GET
. The use case is for a client (web pages, mobile apps) retrieving information. Let's see how you would write data byPOST
. The use case is for a client saving and writing information. In the doPost:
package com.example.onlineorder;
import java.io.*;
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*;
import org.json.JSONObject;
import org.apache.commons.io.IOUtils;
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
private String message;
public void init() {
message = "Hello World!";
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException{
// Read customer information from request body
JSONObject jsonRequest = new JSONObject(IOUtils.toString(request.getReader()));
String email = jsonRequest.getString("email");
String firstName = jsonRequest.getString("first_name");
String lastName = jsonRequest.getString("last_name");
int age = jsonRequest.getInt("age");
// Print customer information to IDE console
System.out.println("Email: " + email);
System.out.println("First name: " + firstName);
System.out.println("Last name: " + lastName);
System.out.println("Age: " + age);
// Return status = ok as response body to the client
response.setContentType("application/json");
JSONObject jsonResponse = new JSONObject();
jsonResponse.put("status", "ok");
response.getWriter().print(jsonResponse);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("application/json");
JSONObject customer = new JSONObject();
customer.put("email", "test@gmail.com");
customer.put("first_name", "Eve");
customer.put("last_name", "L");
customer.put("age", 60);
response.getWriter().print(customer);
}
public void destroy() {
}
}
Now re-run the server by clicking the green button. Open postman and put http://localhost:8080/hello-servlet in the address bar imported POST customer request to test this. You'll see the log console output, and also the status ok response.
Jackson
Manually parsing JSON is not efficient, and error-prone. It doesn't impose type safety, you can put in anything for any field. A better way is having a model entity class, and use a library to perform the parsing. Jackson is one of such libraries. (Another popular one is Gson from Google). Let's see how the above steps can be simplified with Jackson.
Jackson 是一个用于 Java 平台的开源 JSON 解析和生成库。Jackson 提供了一系列简单的 API,使开发人员能够轻松地将 Java 对象转换为 JSON 字符串,反之亦然。Jackson 在 Java 世界中是最受欢迎和广泛使用的 JSON 库之一,以下是其主要特点和功能:
- 数据绑定:Jackson 允许你轻松地将 Java 对象转换为 JSON,反之亦然。这种功能称为数据绑定。例如,你可以直接将 Java 对象序列化为 JSON 字符串,或从 JSON 字符串反序列化为 Java 对象。
- 流式 API:除了数据绑定,Jackson 也提供了一套底层的流式 API,使得处理大型 JSON 数据或进行微调更为高效。
- 注解驱动:Jackson 提供了一系列注解,允许开发人员在 Java 对象中定义自定义的序列化和反序列化行为。例如,你可以使用
@JsonProperty
来指定一个不同的字段名,或使用@JsonIgnore
来避免某个属性被序列化。- 灵活性和可扩展性:Jackson 的设计是模块化的,你可以通过添加或替换模块来扩展或自定义其功能。
- 性能:Jackson 是为性能而优化的。与其他 JSON 库相比,它通常能提供更快的序列化和反序列化速度。
- 支持多种数据格式:虽然 Jackson 最初是为 JSON 设计的,但它也支持其他格式,例如 XML、YAML 和 CSV,这都是通过其插件模块实现的。
- 兼容性:Jackson 可以与多种 Java 版本和框架一起使用,包括 Spring、JAX-RS 和其他。
- 丰富的错误信息:当解析或生成 JSON 时出现错误,Jackson 提供了详细和有用的错误信息,有助于快速定位和解决问题。
总之,Jackson 是一个强大、灵活并且广泛使用的库,非常适合处理 JSON 数据在 Java 应用程序中的需求。如果你在 Java 项目中需要处理 JSON,那么 Jackson 绝对值得考虑
- Let's first add Jackson library to the project. In the pom.xml file, add the following at the end inside the
<dependencies> </dependencies>
body. Don't put it anywhere else.
<!-- ... existing dependencies -->
<!-- Only insert the following, do not change or touch other lines -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.15.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.14.2</version>
</dependency>
<!-- Only insert the following, do not change or touch other lines -->
<dependencies>
Reload, and Install.
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.502 s
[INFO] Finished at: 2023-10-23T18:54:30-05:00
[INFO] ------------------------------------------------------------------------
- Add a new package called
entity
undercom.example.onlineOrder
package
- Create a
Customer
class inside ofcom.example.onlineOrder.entity
package with the following content.
package com.example.onlineorder.entity;
public class Customer {
private String email;
private String firstName;
private String lastName;
private String password;
private int age;
private boolean enabled;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String first_name) {
this.firstName = first_name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String last_name) {
this.lastName = last_name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
此处的entity
主要指的是实体类。在 Java 和许多其他编程语言中,实体类通常用于表示和存储数据,特别是与数据库表对应的数据。
这里的 Customer
类就是一个典型的实体类。它代表了“客户”这一业务概念,类中的每个属性(如 email
, firstName
, lastName
等)对应于可能在数据库中的 Customer
表的某个列。
当我们使用像 Java Persistence API (JPA) 或 Hibernate 这样的对象关系映射 (ORM) 工具时,实体类常常与数据库表进行关联,使得我们可以直接操作对象来进行数据库的增删改查。
在项目的代码结构中,将这些实体类放在一个名为 entity
的包下是常见的做法,这有助于保持代码的组织和清晰度。
总之,这里的 entity
指的是代表业务领域中某个概念或对象的数据模型,这些模型通常与数据库中的表有直接关联
- Now in the doGet for
HelloServlet
:
package com.example.onlineorder; //声明了这个类所在的包名为 com.example.onlineorder。
import java.io.*; //导入了 Java 的 IO 包,该包包含与输入、输出操作相关的类
import com.example.onlineorder.entity.Customer; //从 com.example.onlineorder.entity 包中导入 Customer 类
import com.fasterxml.jackson.databind.ObjectMapper; //导入 Jackson 库的 ObjectMapper 类。它用于将 JSON 数据转换为 Java 对象,反之亦然
import jakarta.servlet.http.*;
import jakarta.servlet.annotation.*; // 导入 Jakarta Servlet 库,它提供与 HTTP Servlets 相关的类和注解
import org.json.JSONObject; // 导入 org.json 库的 JSONObject 类,用于处理 JSON 对象
import org.apache.commons.io.IOUtils; //导入 Apache Commons IO 库的 IOUtils 类,该类提供了一些 IO 操作的实用方法(尽管在这个例子中没有使用)
// 使用 @WebServlet 注解声明这个类为一个 Servlet,并设置其名称和 URL 映射
@WebServlet(name = "helloServlet", value = "/hello-servlet")
public class HelloServlet extends HttpServlet {
//声明了一个公共类 HelloServlet,它继承了 HttpServlet 类
private String message;
//定义一个私有字符串变量 message
//初始化方法。当这个 Servlet 被实例化时,它会被调用。这里将 "Hello World!" 字符串赋值给 message 变量
public void init() {
message = "Hello World!";
}
// 定义了 doPost 方法,该方法用于处理来自客户端的 POST 请求
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException{
// Read customer information from request body
ObjectMapper objectMapper = new ObjectMapper();
// 使用 ObjectMapper 从请求体中读取 JSON 数据并将其转换为 Customer 类的一个实例
Customer customer = objectMapper.readValue(request.getInputStream(), Customer.class);
//在控制台上打印从客户端发送过来的客户信息
System.out.println(customer.getEmail());
System.out.println(customer.getFirstName());
System.out.println(customer.getLastName());
// Return status = ok as response body
response.setContentType("application/json"); //设置响应的内容类型为 JSON
JSONObject jsonResponse = new JSONObject(); //创建一个 JSON 对象
jsonResponse.put("status", "ok"); //设置其状态为 "ok"
response.getWriter().print(jsonResponse); //将该 JSON 对象发送回客户端
}
// 定义了 doGet 方法,该方法用于处理来自客户端的 GET 请求
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 设置响应的内容类型为 JSON
response.setContentType("application/json");
ObjectMapper mapper = new ObjectMapper();
/* 这行代码创建了 Jackson 库中的 ObjectMapper 类的一个新实例,并将其赋值给名为 mapper 的变量。ObjectMapper 是 Jackson 提供的主要类,用于在 Java 对象与 JSON 数据之间进行转换。new ObjectMapper() 初始化了 ObjectMapper 的一个新实例。ObjectMapper mapper 定义了一个类型为 ObjectMapper 的变量 mapper。通过使用 mapper,您可以将 Java 对象序列化为 JSON 字符串,或将 JSON 字符串反序列化为 Java 对象
*/
Customer customer = new Customer();//然后创建一个 Customer 对象
customer.setEmail("test@gmail.com"); // 为其设置属性值, 并将其转换为 JSON 字符串
customer.setFirstName("John");
customer.setLastName("Doe");
customer.setEnabled(true);
// 然后将这个 JSON 字符串发送回客户端
response.getWriter().print(mapper.writeValueAsString(customer));
}
// 这是 Servlet 生命周期中的一个方法。当 Servlet 从服务中移除时,它会被调用。在这个例子中,它没有做任何事情
public void destroy() {
}
}
Notice how Jackson turned the objects into JSON strings? It can also turn JSON strings into model objects automatically.
Now build and run the Tomcat server by clicking the green button. Use http://localhost:8080/hello-servlet and you'll see the JSON response: