Optimizing Web Applications with Servlet Annotations and Template Method Pattern
(Thirteen) Optimizing Single Table Operations
Corresponding Video:
33-Annotation-based Development in Servlets
34-Using Template Method Design Pattern to Solve Class Explosion
How to Perform Resource Redirection in a Web Application?
In a web application, resource redirection can be accomplished through two methods:
- Forwarding
- Redirecting
What are the differences between forwarding and redirecting?
Code Differences:
- Forwarding:
// Get the request dispatcher object
RequestDispatcher dispatcher = request.getRequestDispatcher("/dept/list");
// Call the forward method of the dispatcher object to complete forwarding
dispatcher.forward(request, response);
// Combined into one line of code
request.getRequestDispatcher("/dept/list").forward(request, response);
// Forwarding is a single request, regardless of how many times you forward.
// AServlet forwards to BServlet, then to CServlet, then to DServlet. No matter how many times you forward, it's all within the same request.
// This is because when calling the forward method, the current request and response objects are passed to the next Servlet.
- Redirecting:
// Redirect
// The path for redirect needs to include the context path.
// The response object sends the request.getContextPath()+"/b" path back to the browser.
// During this process, the browser sends another request: http://localhost:8080/request.getContextPath()/b
// Finally, the address bar in the browser changes to display the path of the last request.
response.sendRedirect(request.getContextPath()+"/b");
Formal Differences:
- Forwarding is a single request.
- Redirecting is two requests.
Essential Differences:
- Forwarding: Controlled by the web server. The resource jump from A to B is completed internally by the Tomcat server.
- Redirecting: Completed by the browser. The browser determines which resource to jump to.
Illustrative Example Describing Forwarding and Redirecting:
- Borrowing money (Forwarding: one request sent)
- Teacher Du is out of money and asks Zhang San for a loan. Actually, Zhang San doesn't have money, but he's loyal and borrows from Li Si, then gives the money to Teacher Du. Teacher Du doesn't know the money is from Li Si and thinks it's Zhang San's.
- Borrowing money (Redirecting: two requests sent)
- Teacher Du is out of money and asks Zheng San for a loan. Zhang San doesn't have money, but he has a rich friend named Li Si. Zhang San gives Li Si's address to Teacher Du, who then goes to find Li Si and borrows money from him. Clearly, Teacher Du asked two people in this process and knows the money ultimately came from Li Si.
When to use forwarding vs. redirecting?
- If you've bound data to the request scope in the previous Servlet and want to retrieve it in the next Servlet, use forwarding.
- For all other requests, use redirecting. (Redirecting is used more frequently.)
Are there requirements for the next resource being redirected to? Must it be a Servlet?
- Not necessarily. The redirected resource can be any valid internal server resource, including: Servlets, JSPs, HTML files, etc.
- Forwarding may cause browser refresh issues (which can be avoided using redirecting).
Servlet Annotations for Simplified Configuration
- Analyzing the web.xml file in the OA project
- Currently, we only have CRUD operations for a single table with no complex business logic, just simple functionality. Yet the web.xml file contains so much configuration information. If we continue this way for a large project, the web.xml file could become enormous, potentially reaching tens of megabytes.
- Configuring servlet information in the web.xml file is clearly inefficient, requiring configuration for each servlet.
- Could this configuration information be written directly into Java classes? Yes.
- After Servlet 3.0, various servlets introduced annotation-based development. What are the advantages?
- Higher development efficiency, no need to write extensive configuration information. Simply annotate on Java classes.
- The web.xml file becomes smaller.
- This doesn't mean web.xml is unnecessary after annotations:
- Some information that needs to change should still be configured in web.xml. The general approach is annotation + configuration file development.
- Configuration that doesn't change frequently should use annotations. Configuration that might be modified should be in the configuration file.
- Our first annotation: jakarta.servlet.annotation.WebServlet
- Use on Servlet classes: @WebServlet. What attributes does the WebServlet annotation have?
- name attribute: Specifies the servlet name, equivalent to
- urlPatterns attribute: Specifies servlet mapping paths, can specify multiple strings, equivalent to
- loadOnStartUp attribute: Specifies whether to load the servlet during server startup, equivalent to
- value attribute: When the annotation attribute name is value, it can be omitted when using the annotation.
- Note: Not all attributes must be included, only provide what's needed.
- Note: The attribute is a array. If there's only one element, the braces can be omitted when using the annotation.
- Format for using annotation objects:
- @AnnotationName(attributeName=attributeValue, attributeName=attributeValue, ...)
Accessing Annotation Information Through Reflection:
package com.zwm.javaweb.servlet;
import jakarta.servlet.annotation.WebServlet;
/**
* @author Zhu Wuming
* @date 2023/7/21 23:59
* description:
*/
public class Test {
public static void main(String[] args) throws Exception{
// Get class through reflection
Class<?> helloServletClass = Class.forName("com.zwm.javaweb.servlet.HelloServlet");
// Get annotation object on the class
// First check if there's an annotation on this class, if so, get it.
// boolean annotationPresent = helloServletClass.isAnnotationPresent(WebServlet.class);
// System.out.println(annotationPresent); //true
if(helloServletClass.isAnnotationPresent(WebServlet.class)){
// Get the annotation object on this class
WebServlet annotation = helloServletClass.getAnnotation(WebServlet.class);
// Get the value attribute of the annotation
String[] value = annotation.value();
for (String s : value) {
System.out.println(s);
}
}
}
}
Optimizing the OA Project Using Template Method Design Pattern
The above annotations solved the configuration file issue, but the current OA project still has a bloated problem.
- A single table CRUD operation requires 7 Servlet classes. For a complex business system, this development approach would clearly lead to class explosion (too many classes).
- How to solve the class explosion problem? Use the template method design pattern.
How to solve the class explosion problem?
- Previous design: one request, one Servlet class. 1000 requests correspond to 1000 Servlet classes, leading to class explosion.
- New approach: one request corresponds to one method. One business corresponds to one Servlet class.
- DeptServlet handles department-related business. UserServlet handles user-related business. CardServlet handles card-related business.
Updated Code:
package com.zwm.javaweb.action;
import com.zwm.javaweb.utils.DBUtil;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.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;
/**
* @author Zhu Wuming
* @date 2023/7/22 00:20
* description:
*/
// Base class
@WebServlet({"/dept/list","/dept/update","/dept/JDBCupdate","/dept/detail","/dept/delete","/dept/add","/dept/JDBCadd"})
public class DeptServlet extends HttpServlet {
// Template method
// Override service method
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String servletPath = request.getServletPath();
if("/dept/list".equals(servletPath)){
listDepartments(request, response);
} else if("/dept/detail".equals(servletPath)){
showDepartmentDetails(request, response);
} else if("/dept/delete".equals(servletPath)){
removeDepartment(request, response);
} else if("/dept/update".equals(servletPath)){
displayUpdateForm(request, response);
} else if("/dept/JDBCupdate".equals(servletPath)){
updateDepartment(request, response);
} else if("/dept/add".equals(servletPath)){
showAddForm(request, response);
} else if("/dept/JDBCadd".equals(servletPath)){
addDepartment(request, response);
}
}
// Department list
private void listDepartments(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Get application root path
String contextPath = request.getContextPath();
// Set response content type and character encoding to prevent Chinese garbling
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
// Fixed in HTML page
out.print("");
out.print("<html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>Department List Page</title>");
out.print(" <script type='text/javascript'>");
out.print(" function del(dno){");
out.print(" if(window.confirm('Are you sure you want to delete? This cannot be undone!')){");
out.print(" document.location.href = '"+contextPath+"/dept/delete?deptno=' + dno;");
out.print(" }");
out.print(" }");
out.print(" </script>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1 align='center'>Department List</h1>");
out.print(" <hr>");
out.print(" | Number | Department ID | Department Name | Actions |
|---|---|---|---|
| "+(++number)+" | "+deptno+" | "+dname+" | "); out.print(" [Delete](javascript:void(0))"); out.print(" [Edit]("+contextPath+"/dept/update?deptno="+deptno+"&dname="+dname+"&loc="+loc+")"); out.print(" [Details]("+contextPath+"/dept/detail?deptno="+deptno+")"); out.print(" |");
out.print(" ");
out.print(" <hr>");
out.print(" <a href='"+contextPath+"/dept/add'>Add Department</a>");
out.print(" </body>");
out.print("</html>");
}
// Show department details
private void showDepartmentDetails(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
// Set response content type and character encoding to prevent Chinese garbling
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print("");
out.print("<html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>Department Details</title>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1>Department Details</h1>");
out.print(" <hr>");
// Chinese logic (source: What do you want to do? Goal: View department details.)
// Step 1: Get department ID
String deptno = request.getParameter("deptno");
// Step 2: Query database based on department ID to get department information
// Database connection variables
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
// Get prepared statement
String sql = "select deptno,dname,loc from dept where deptno = ?";
ps=conn.prepareStatement(sql);
ps.setString(1,deptno);
// Execute SQL
rs = ps.executeQuery();
if(rs.next()){
String dname = rs.getString("dname");
String loc = rs.getString("loc");
out.print(" Department ID: "+deptno+" <br>");
out.print(" Department Name: "+dname+" <br>");
out.print(" Department Location: "+loc+" <br>");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
// Release resources
DBUtil.close(conn,ps,rs);
}
out.print(" <input type='button' value='Back' onclick='window.history.back()'/>");
out.print(" ");
out.print(" </body>");
out.print("</html>");
}
// Delete department
private void removeDepartment(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Set response content type and character encoding to prevent Chinese garbling
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String deptno = request.getParameter("deptno");
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
String sql = "DELETE FROM dept WHERE deptno = ?";
ps=conn.prepareStatement(sql);
ps.setString(1,deptno);
// Execute SQL
// Return value represents how many rows affected in the database
int rowsAffected = ps.executeUpdate();
if (rowsAffected > 0) {
System.out.println("Deletion successful");
// Redirect back to original page after deletion
response.sendRedirect(request.getContextPath()+"/dept/list");
} else {
System.out.println("Deletion failed");
response.sendRedirect(request.getContextPath()+"/dept/list");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
// Release resources
DBUtil.close(conn,ps,rs);
}
}
// Add department
private void showAddForm(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
// Get application root path
String contextPath = request.getContextPath();
// Set response content type and character encoding to prevent Chinese garbling
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.print(" ");
out.print(" <html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>Add Department</title>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1>Add Department</h1>");
out.print(" <hr>");
out.print(" <form action='"+contextPath+"/dept/JDBCadd' method='get'>");
out.print(" Department ID: <input type='text' name='deptno'/><br>");
out.print(" Department Name: <input type='text' name='deptname'/><br>");
out.print(" Department Location: <input type='text' name='loc'/><br>");
out.print(" <input type='submit' value='Submit'/><br>");
out.print(" </form>");
out.print(" </body>");
out.print(" </html>");
}
private void addDepartment(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
// Set response content type and character encoding to prevent Chinese garbling
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String deptno = request.getParameter("deptno");
String deptname = request.getParameter("deptname");
String loc = request.getParameter("loc");
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
String sql = "INSERT INTO dept (deptno, dname, loc) VALUES (?, ?, ?)";
ps=conn.prepareStatement(sql);
ps.setString(1,deptno);
ps.setString(2,deptname);
ps.setString(3,loc);
// Execute SQL
// Return value represents how many rows affected in the database
int rowsAffected = ps.executeUpdate();
if (rowsAffected > 0) {
System.out.println(rowsAffected + " rows inserted");
response.sendRedirect(request.getContextPath()+"/dept/list");
} else {
System.out.println("No data inserted");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
// Release resources
DBUtil.close(conn,ps,rs);
}
}
// Update department
private void displayUpdateForm(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
// Get application root path
String contextPath = request.getContextPath();
// Set response content type and character encoding to prevent Chinese garbling
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String deptno = request.getParameter("deptno");
String dname = request.getParameter("dname");
String loc = request.getParameter("loc");
out.print(" ");
out.print(" <html>");
out.print(" <head>");
out.print(" <meta charset='utf-8'>");
out.print(" <title>Update Department</title>");
out.print(" </head>");
out.print(" <body>");
out.print(" <h1>Update Department</h1>");
out.print(" <hr>");
out.print(" <form action='"+contextPath+"/dept/JDBCupdate' method='get'>");
out.print(" Department ID: <input type='text' name='deptno' value='"+deptno+"' readonly/><br>");
out.print(" Department Name: <input type='text' name='deptname' value='"+dname+"'/><br>");
out.print(" Department Location: <input type='text' name='loc' value='"+loc+"'/><br>");
out.print(" <input type='submit' value='Update'/><br>");
out.print(" </form>");
out.print(" </body>");
out.print(" </html>");
}
private void updateDepartment(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException{
// Set response content type and character encoding to prevent Chinese garbling
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
String deptno = request.getParameter("deptno");
String deptname = request.getParameter("deptname");
String loc = request.getParameter("loc");
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DBUtil.getConnection();
String sql = "UPDATE dept SET dname = ?, loc = ? WHERE deptno = ?";
ps=conn.prepareStatement(sql);
ps.setString(1,deptname);
ps.setString(2,loc);
ps.setString(3,deptno);
// Execute SQL
// Return value represents how many rows affected in the database
int rowsAffected = ps.executeUpdate();
if (rowsAffected > 0) {
System.out.println("Update successful");
response.sendRedirect(request.getContextPath()+"/dept/list");
} else {
System.out.println("No matching data found");
response.sendRedirect(request.getContextPath()+"/dept/list");
}
} catch (SQLException e) {
throw new RuntimeException(e);
}finally {
// Release resources
DBUtil.close(conn,ps,rs);
}
}
}
Analyzing Drawbacks of Pure Servlet Development
- Writing HTML/CSS/JavaScript in Servlets. What problems exist?
- Writing frontend code in Java programs is difficult, errors in frontend code aren't visible, troublesome.
- Writing frontend code in Java programs results in high coupling.
- Writing frontend code in Java programs makes the code unattractive.
- Writing frontend code in Java programs has high maintenance costs. (Very difficult to maintain)
Modifying even a small frontend code requires recompiling Java code, generating new class files, creating a new war package, and redeploying.
- How to solve this problem?
- Can the above Servlet (Java program) be automatically generated by a machine? Programmers only need to write the "frontend code" part of this Servlet, then let the machine automatically translate the "frontend code" into "Servlet Java programs", then automatically compile the "Java" program into "class" files, and finally use JVM to call methods in this class.
- Corresponding technology: JSP.