JSP系统架构从入门到精通构建高性能Web应用的完整指南涵盖MVC模式分层架构及实战案例深度分析与问题解决
1. JSP基础概念与介绍
JavaServer Pages (JSP) 是一种用于开发动态Web内容的技术,它允许开发者在HTML页面中嵌入Java代码。JSP作为Java EE技术栈的重要组成部分,为构建企业级Web应用提供了强大支持。
1.1 JSP工作原理
JSP页面在首次被请求时,服务器会将其转换为Servlet类,然后编译并执行。这个过程如下:
- 客户端请求JSP页面
- JSP容器将JSP页面转换为Servlet源代码
- 编译生成的Servlet源代码
- 加载并实例化Servlet类
- 调用Servlet的
_jspService()
方法处理请求并生成响应
下面是一个简单的JSP示例:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>我的第一个JSP页面</title> </head> <body> <h1>Hello JSP!</h1> <% // Java代码片段 String name = "World"; out.println("<p>Hello, " + name + "!</p>"); %> <p>当前时间: <%= new java.util.Date() %></p> </body> </html>
1.2 JSP基本语法
JSP提供了多种语法元素,包括:
- 脚本片段:
<% Java代码 %>
- 表达式:
<%= Java表达式 %>
- 声明:
<%! Java声明 %>
- 指令:
<%@ 指令 %>
- 动作:
<jsp:动作>
1.3 JSP内置对象
JSP提供了9个内置对象,无需显式声明即可使用:
request
:HttpServletRequest对象response
:HttpServletResponse对象out
:JspWriter对象session
:HttpSession对象application
:ServletContext对象config
:ServletConfig对象pageContext
:PageContext对象page
:当前Servlet实例exception
:异常对象(仅在错误页面中可用)
2. JSP系统架构基础
2.1 Web应用架构演进
Web应用架构经历了从简单到复杂的演进过程:
- 单层架构:所有业务逻辑、数据处理和UI展示都混在一起
- 两层架构:客户端-服务器模式,将UI与业务逻辑分离
- 三层架构:表示层、业务逻辑层、数据访问层
- 多层架构:在三层基础上进一步细化,如引入服务层、领域层等
2.2 JSP在Web架构中的定位
在传统JSP开发中,JSP通常扮演表示层角色,负责生成动态HTML内容。随着架构演进,JSP的职责逐渐聚焦于视图展示,而将业务逻辑交给JavaBean或Servlet处理。
2.3 JSP与Servlet的关系
JSP和Servlet是Java Web开发的两个核心组件,它们的关系如下:
- JSP最终会被转换为Servlet
- Servlet更适合处理控制逻辑,JSP更适合展示数据
- 两者可以配合使用,Servlet处理请求并准备数据,JSP负责展示
3. MVC模式详解
3.1 MVC模式概述
Model-View-Controller (MVC) 是一种软件设计模式,将应用程序分为三个主要部分:
- Model(模型):负责数据和业务逻辑
- View(视图):负责数据显示和用户界面
- Controller(控制器):接收用户输入并协调模型和视图
3.2 JSP中的MVC实现
在JSP中实现MVC模式通常涉及以下组件:
- Model:JavaBean、EJB或POJO
- View:JSP页面
- Controller:Servlet或Struts等框架的控制器
下面是一个简单的MVC示例:
Model (User.java)
public class User { private String username; private String email; // 构造方法 public User(String username, String email) { this.username = username; this.email = email; } // Getter和Setter方法 public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
Controller (UserController.java)
import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @WebServlet("/UserController") public class UserController extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 创建模型对象 User user = new User("JohnDoe", "john@example.com"); // 将模型存储在请求作用域中 request.setAttribute("user", user); // 转发到视图 RequestDispatcher dispatcher = request.getRequestDispatcher("userView.jsp"); dispatcher.forward(request, response); } }
View (userView.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>User Information</title> </head> <body> <h1>User Information</h1> <p>Username: ${user.username}</p> <p>Email: ${user.email}</p> </body> </html>
3.3 MVC模式的优势
采用MVC模式有以下优势:
- 分离关注点:各组件职责明确,降低耦合度
- 提高可维护性:修改视图不影响模型,反之亦然
- 促进并行开发:不同开发人员可同时开发不同组件
- 增强代码复用性:模型和控制器可被多个视图复用
- 便于测试:各组件可独立测试
4. 分层架构设计
4.1 分层架构概述
分层架构是将系统划分为多个层次,每一层都有明确的职责,并只能与相邻层交互。常见的Web应用分层包括:
- 表示层(Presentation Layer):负责用户界面和交互
- 控制层(Controller Layer):处理请求并协调其他层
- 业务逻辑层(Business Logic Layer):实现业务规则和流程
- 数据访问层(Data Access Layer):负责与数据库交互
- 领域模型层(Domain Model Layer):表示业务领域中的实体和关系
4.2 JSP分层架构实现
下面是一个基于JSP的完整分层架构示例:
领域模型层 (User.java)
public class User { private int id; private String username; private String password; private String email; // 构造方法 public User() {} public User(int id, String username, String password, String email) { this.id = id; this.username = username; this.password = password; this.email = email; } // Getter和Setter方法 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } }
数据访问层 (UserDAO.java)
import java.sql.*; import java.util.ArrayList; import java.util.List; public class UserDAO { private Connection connection; public UserDAO(Connection connection) { this.connection = connection; } // 添加用户 public boolean addUser(User user) throws SQLException { String sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)"; try (PreparedStatement statement = connection.prepareStatement(sql)) { statement.setString(1, user.getUsername()); statement.setString(2, user.getPassword()); statement.setString(3, user.getEmail()); return statement.executeUpdate() > 0; } } // 根据ID获取用户 public User getUserById(int id) throws SQLException { String sql = "SELECT * FROM users WHERE id = ?"; try (PreparedStatement statement = connection.prepareStatement(sql)) { statement.setInt(1, id); ResultSet resultSet = statement.executeQuery(); if (resultSet.next()) { return extractUserFromResultSet(resultSet); } } return null; } // 获取所有用户 public List<User> getAllUsers() throws SQLException { List<User> users = new ArrayList<>(); String sql = "SELECT * FROM users"; try (Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql)) { while (resultSet.next()) { users.add(extractUserFromResultSet(resultSet)); } } return users; } // 更新用户 public boolean updateUser(User user) throws SQLException { String sql = "UPDATE users SET username = ?, password = ?, email = ? WHERE id = ?"; try (PreparedStatement statement = connection.prepareStatement(sql)) { statement.setString(1, user.getUsername()); statement.setString(2, user.getPassword()); statement.setString(3, user.getEmail()); statement.setInt(4, user.getId()); return statement.executeUpdate() > 0; } } // 删除用户 public boolean deleteUser(int id) throws SQLException { String sql = "DELETE FROM users WHERE id = ?"; try (PreparedStatement statement = connection.prepareStatement(sql)) { statement.setInt(1, id); return statement.executeUpdate() > 0; } } // 从ResultSet中提取User对象 private User extractUserFromResultSet(ResultSet resultSet) throws SQLException { User user = new User(); user.setId(resultSet.getInt("id")); user.setUsername(resultSet.getString("username")); user.setPassword(resultSet.getString("password")); user.setEmail(resultSet.getString("email")); return user; } }
业务逻辑层 (UserService.java)
import java.sql.SQLException; import java.util.List; public class UserService { private UserDAO userDAO; public UserService(UserDAO userDAO) { this.userDAO = userDAO; } // 注册用户 public boolean registerUser(User user) throws SQLException { // 验证用户数据 if (user.getUsername() == null || user.getUsername().isEmpty() || user.getPassword() == null || user.getPassword().isEmpty() || user.getEmail() == null || user.getEmail().isEmpty()) { return false; } // 检查用户名是否已存在 User existingUser = userDAO.getUserByUsername(user.getUsername()); if (existingUser != null) { return false; } // 添加用户 return userDAO.addUser(user); } // 用户登录 public User login(String username, String password) throws SQLException { User user = userDAO.getUserByUsername(username); if (user != null && user.getPassword().equals(password)) { return user; } return null; } // 获取所有用户 public List<User> getAllUsers() throws SQLException { return userDAO.getAllUsers(); } // 获取用户详情 public User getUserDetails(int id) throws SQLException { return userDAO.getUserById(id); } // 更新用户信息 public boolean updateUserProfile(User user) throws SQLException { // 验证用户数据 if (user.getUsername() == null || user.getUsername().isEmpty() || user.getEmail() == null || user.getEmail().isEmpty()) { return false; } return userDAO.updateUser(user); } }
控制层 (UserController.java)
import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; import java.util.List; import javax.servlet.RequestDispatcher; 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 javax.servlet.http.HttpSession; @WebServlet("/UserController") public class UserController extends HttpServlet { private static final long serialVersionUID = 1L; private Connection connection; private UserService userService; @Override public void init() throws ServletException { // 初始化数据库连接 try { connection = DatabaseUtil.getConnection(); UserDAO userDAO = new UserDAO(connection); userService = new UserService(userDAO); } catch (SQLException e) { throw new ServletException("Database connection error", e); } } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); try { switch (action) { case "list": listUsers(request, response); break; case "edit": showEditForm(request, response); break; case "view": viewUser(request, response); break; default: listUsers(request, response); break; } } catch (SQLException ex) { throw new ServletException("Database error", ex); } } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); try { switch (action) { case "register": registerUser(request, response); break; case "login": loginUser(request, response); break; case "update": updateUser(request, response); break; case "delete": deleteUser(request, response); break; default: listUsers(request, response); break; } } catch (SQLException ex) { throw new ServletException("Database error", ex); } } private void listUsers(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { List<User> userList = userService.getAllUsers(); request.setAttribute("userList", userList); RequestDispatcher dispatcher = request.getRequestDispatcher("userList.jsp"); dispatcher.forward(request, response); } private void showEditForm(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { int id = Integer.parseInt(request.getParameter("id")); User existingUser = userService.getUserDetails(id); RequestDispatcher dispatcher = request.getRequestDispatcher("userForm.jsp"); request.setAttribute("user", existingUser); dispatcher.forward(request, response); } private void viewUser(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { int id = Integer.parseInt(request.getParameter("id")); User user = userService.getUserDetails(id); RequestDispatcher dispatcher = request.getRequestDispatcher("userView.jsp"); request.setAttribute("user", user); dispatcher.forward(request, response); } private void registerUser(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); String email = request.getParameter("email"); User user = new User(); user.setUsername(username); user.setPassword(password); user.setEmail(email); boolean success = userService.registerUser(user); if (success) { response.sendRedirect("login.jsp?success=registered"); } else { request.setAttribute("error", "Registration failed"); RequestDispatcher dispatcher = request.getRequestDispatcher("register.jsp"); dispatcher.forward(request, response); } } private void loginUser(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); User user = userService.login(username, password); if (user != null) { HttpSession session = request.getSession(); session.setAttribute("user", user); response.sendRedirect("UserController?action=list"); } else { request.setAttribute("error", "Invalid username or password"); RequestDispatcher dispatcher = request.getRequestDispatcher("login.jsp"); dispatcher.forward(request, response); } } private void updateUser(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { int id = Integer.parseInt(request.getParameter("id")); String username = request.getParameter("username"); String email = request.getParameter("email"); User user = new User(); user.setId(id); user.setUsername(username); user.setEmail(email); boolean success = userService.updateUserProfile(user); if (success) { response.sendRedirect("UserController?action=list"); } else { request.setAttribute("error", "Update failed"); RequestDispatcher dispatcher = request.getRequestDispatcher("userForm.jsp"); dispatcher.forward(request, response); } } private void deleteUser(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { int id = Integer.parseInt(request.getParameter("id")); userService.deleteUser(id); response.sendRedirect("UserController?action=list"); } @Override public void destroy() { // 关闭数据库连接 try { if (connection != null) { connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } }
表示层 (userList.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>User List</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> </head> <body> <div class="container mt-4"> <h2>User List</h2> <c:if test="${not empty sessionScope.user}"> <p>Welcome, ${sessionScope.user.username}! | <a href="logout.jsp">Logout</a></p> </c:if> <c:if test="${empty sessionScope.user}"> <p><a href="login.jsp">Login</a> | <a href="register.jsp">Register</a></p> </c:if> <table class="table table-bordered"> <thead> <tr> <th>ID</th> <th>Username</th> <th>Email</th> <th>Actions</th> </tr> </thead> <tbody> <c:forEach var="user" items="${userList}"> <tr> <td>${user.id}</td> <td>${user.username}</td> <td>${user.email}</td> <td> <a href="UserController?action=view&id=${user.id}">View</a> <c:if test="${not empty sessionScope.user}"> | <a href="UserController?action=edit&id=${user.id}">Edit</a> | <a href="UserController?action=delete&id=${user.id}" onclick="return confirm('Are you sure?')">Delete</a> </c:if> </td> </tr> </c:forEach> </tbody> </table> </div> </body> </html>
4.3 分层架构的优势
采用分层架构有以下优势:
- 关注点分离:每层只关注自己的职责,降低系统复杂度
- 提高可维护性:修改一层不会影响其他层
- 增强可测试性:每层可独立测试
- 促进团队协作:不同团队可并行开发不同层
- 提高代码复用性:业务逻辑层可被多种表示层复用
5. 高性能JSP应用构建策略
5.1 性能优化概述
构建高性能JSP应用需要考虑多个方面,包括代码优化、数据库优化、缓存策略、服务器配置等。下面将详细介绍这些策略。
5.2 JSP页面优化
5.2.1 减少脚本代码
过多的Java脚本代码会影响JSP性能和维护性。应尽量使用JSTL和EL表达式替代脚本代码。
不推荐的做法:
<% List<User> users = (List<User>) request.getAttribute("users"); for (User user : users) { out.println("<tr>"); out.println("<td>" + user.getId() + "</td>"); out.println("<td>" + user.getUsername() + "</td>"); out.println("<td>" + user.getEmail() + "</td>"); out.println("</tr>"); } %>
推荐的做法:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <c:forEach var="user" items="${users}"> <tr> <td>${user.id}</td> <td>${user.username}</td> <td>${user.email}</td> </tr> </c:forEach>
5.2.2 启用JSP预编译
JSP页面在首次访问时会被编译成Servlet,这会导致首次访问较慢。可以通过预编译JSP页面来解决这个问题。
在web.xml中添加以下配置:
<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>development</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>fork</param-name> <param-value>false</param-value> </init-param> <init-param> <param-name>trimSpaces</param-name> <param-value>true</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>
5.2.3 禁用JSP会话
默认情况下,JSP页面会自动创建HTTP会话。如果页面不需要会话,可以禁用以提高性能。
<%@ page session="false" %>
5.3 数据库优化
5.3.1 使用连接池
数据库连接是昂贵的资源,使用连接池可以显著提高性能。以下是基于Tomcat的连接池配置示例:
context.xml
<Context> <Resource name="jdbc/myDB" auth="Container" type="javax.sql.DataSource" maxTotal="100" maxIdle="30" maxWaitMillis="10000" username="dbuser" password="dbpassword" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/mydb"/> </Context>
web.xml
<resource-ref> <description>DB Connection Pool</description> <res-ref-name>jdbc/myDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>
获取连接的Java代码
import javax.naming.Context; import javax.naming.InitialContext; import javax.sql.DataSource; import java.sql.Connection; public class DatabaseUtil { public static Connection getConnection() { Connection conn = null; try { Context initContext = new InitialContext(); Context envContext = (Context) initContext.lookup("java:/comp/env"); DataSource ds = (DataSource) envContext.lookup("jdbc/myDB"); conn = ds.getConnection(); } catch (Exception e) { e.printStackTrace(); } return conn; } }
5.3.2 使用PreparedStatement
使用PreparedStatement可以防止SQL注入,并提高SQL执行效率。
public User getUserById(int id) throws SQLException { String sql = "SELECT * FROM users WHERE id = ?"; try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setInt(1, id); ResultSet rs = stmt.executeQuery(); if (rs.next()) { return extractUserFromResultSet(rs); } } return null; }
5.3.3 批量操作
对于大量数据操作,使用批量操作可以显著提高性能。
public void batchInsertUsers(List<User> users) throws SQLException { String sql = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)"; try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { for (User user : users) { stmt.setString(1, user.getUsername()); stmt.setString(2, user.getPassword()); stmt.setString(3, user.getEmail()); stmt.addBatch(); } stmt.executeBatch(); } }
5.4 缓存策略
5.4.1 应用级缓存
使用缓存可以减少数据库访问,提高应用性能。以下是使用Ehcache的示例:
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd" updateCheck="false"> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> <cache name="userCache" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" /> </ehcache>
使用缓存的Service类
import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; public class UserServiceWithCache { private UserDAO userDAO; private Cache userCache; public UserServiceWithCache(UserDAO userDAO) { this.userDAO = userDAO; CacheManager cacheManager = CacheManager.getInstance(); this.userCache = cacheManager.getCache("userCache"); } public User getUserById(int id) throws SQLException { // 首先尝试从缓存获取 Element element = userCache.get(id); if (element != null) { return (User) element.getObjectValue(); } // 缓存中没有,从数据库获取 User user = userDAO.getUserById(id); if (user != null) { // 将用户放入缓存 userCache.put(new Element(id, user)); } return user; } public void updateUser(User user) throws SQLException { userDAO.updateUser(user); // 更新缓存 userCache.put(new Element(user.getId(), user)); } public void deleteUser(int id) throws SQLException { userDAO.deleteUser(id); // 从缓存中删除 userCache.remove(id); } }
5.4.2 HTTP缓存
利用HTTP缓存机制可以减少服务器负载和带宽使用。以下是在Servlet中设置HTTP缓存的示例:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取资源的最后修改时间 long lastModified = getLastModifiedTime(); // 检查If-Modified-Since头 long ifModifiedSince = request.getDateHeader("If-Modified-Since"); if (ifModifiedSince != -1 && ifModifiedSince >= lastModified) { // 资源未修改,返回304 Not Modified response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } // 设置缓存头 response.setDateHeader("Last-Modified", lastModified); response.setHeader("Cache-Control", "max-age=3600"); // 缓存1小时 // 生成并返回内容 generateContent(response); }
5.5 异步处理
使用异步处理可以提高服务器吞吐量,特别是在处理长时间运行的任务时。
5.5.1 Servlet 3.0异步支持
启用异步处理的Servlet
@WebServlet(urlPatterns = "/asyncServlet", asyncSupported = true) public class AsyncServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 启用异步支持 AsyncContext asyncContext = request.startAsync(); // 设置超时时间 asyncContext.setTimeout(30000); // 执行长时间运行的任务 ExecutorService executor = (ExecutorService) request.getServletContext() .getAttribute("executorService"); executor.submit(() -> { try { // 模拟长时间运行的任务 Thread.sleep(5000); // 获取响应对象 HttpServletResponse asyncResponse = (HttpServletResponse) asyncContext.getResponse(); // 设置响应内容 asyncResponse.setContentType("text/html"); PrintWriter out = asyncResponse.getWriter(); out.println("<html><body>"); out.println("<h2>Async Processing Complete</h2>"); out.println("<p>This response was generated after 5 seconds.</p>"); out.println("</body></html>"); // 完成异步处理 asyncContext.complete(); } catch (Exception e) { e.printStackTrace(); asyncContext.complete(); } }); } }
在ServletContextListener中初始化线程池
import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @WebListener public class AppContextListener implements ServletContextListener { private ExecutorService executorService; @Override public void contextInitialized(ServletContextEvent sce) { // 创建线程池 executorService = Executors.newFixedThreadPool(10); // 将线程池存储在ServletContext中 sce.getServletContext().setAttribute("executorService", executorService); } @Override public void contextDestroyed(ServletContextEvent sce) { // 关闭线程池 if (executorService != null) { executorService.shutdown(); } } }
5.6 前端优化
5.6.1 资源压缩
启用GZIP压缩可以显著减少传输数据量。在web.xml中添加以下配置:
<filter> <filter-name>compressionFilter</filter-name> <filter-class>org.apache.catalina.filters.CompressionFilter</filter-class> <init-param> <param-name>compressionThreshold</param-name> <param-value>2048</param-value> </init-param> <init-param> <param-name>compressableMimeType</param-name> <param-value>text/html,text/xml,text/plain,text/css,text/javascript,application/javascript</param-value> </init-param> </filter> <filter-mapping> <filter-name>compressionFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
5.6.2 资源合并与最小化
合并和最小化CSS和JavaScript文件可以减少HTTP请求和文件大小。可以使用构建工具如Maven或Gradle来实现这一点。
Maven插件配置示例:
<plugin> <groupId>com.samaxes.maven</groupId> <artifactId>minify-maven-plugin</artifactId> <version>1.7.6</version> <executions> <execution> <id>minify</id> <phase>prepare-package</phase> <configuration> <cssSourceDir>css</cssSourceDir> <cssSourceIncludes> <cssSourceInclude>*.css</cssSourceInclude> </cssSourceIncludes> <cssFinalFile>style.min.css</cssFinalFile> <jsSourceDir>js</jsSourceDir> <jsSourceIncludes> <jsSourceInclude>*.js</jsSourceInclude> </jsSourceIncludes> <jsFinalFile>script.min.js</jsFinalFile> <jsEngine>CLOSURE</jsEngine> </configuration> <goals> <goal>minify</goal> </goals> </execution> </executions> </plugin>
6. 实战案例分析
6.1 电子商务网站案例
6.1.1 系统架构设计
一个典型的电子商务网站通常包含以下模块:
- 用户管理模块
- 商品管理模块
- 订单管理模块
- 购物车模块
- 支付模块
- 后台管理模块
下面是一个基于JSP的电子商务网站分层架构设计:
领域模型层
// 用户实体 public class User { private int id; private String username; private String password; private String email; private String phone; private String address; private Date registerDate; // getters and setters } // 商品实体 public class Product { private int id; private String name; private String description; private BigDecimal price; private int stock; private String imageUrl; private int categoryId; // getters and setters } // 订单实体 public class Order { private int id; private String orderNumber; private int userId; private BigDecimal totalAmount; private Date orderDate; private String status; // getters and setters } // 订单项实体 public class OrderItem { private int id; private int orderId; private int productId; private int quantity; private BigDecimal price; // getters and setters } // 购物车项实体 public class CartItem { private int productId; private String productName; private BigDecimal price; private int quantity; private String imageUrl; // getters and setters }
数据访问层
// 用户DAO public interface UserDAO { User getUserById(int id) throws SQLException; User getUserByUsername(String username) throws SQLException; boolean addUser(User user) throws SQLException; boolean updateUser(User user) throws SQLException; boolean deleteUser(int id) throws SQLException; } // 商品DAO public interface ProductDAO { Product getProductById(int id) throws SQLException; List<Product> getAllProducts() throws SQLException; List<Product> getProductsByCategory(int categoryId) throws SQLException; List<Product> searchProducts(String keyword) throws SQLException; boolean addProduct(Product product) throws SQLException; boolean updateProduct(Product product) throws SQLException; boolean deleteProduct(int id) throws SQLException; } // 订单DAO public interface OrderDAO { Order getOrderById(int id) throws SQLException; List<Order> getOrdersByUserId(int userId) throws SQLException; boolean addOrder(Order order) throws SQLException; boolean updateOrderStatus(int id, String status) throws SQLException; List<OrderItem> getOrderItems(int orderId) throws SQLException; boolean addOrderItem(OrderItem orderItem) throws SQLException; }
业务逻辑层
// 用户服务 public interface UserService { User login(String username, String password) throws SQLException; boolean register(User user) throws SQLException; boolean updateUserProfile(User user) throws SQLException; User getUserDetails(int id) throws SQLException; } // 商品服务 public interface ProductService { List<Product> getAllProducts() throws SQLException; Product getProductDetails(int id) throws SQLException; List<Product> getProductsByCategory(int categoryId) throws SQLException; List<Product> searchProducts(String keyword) throws SQLException; boolean addProduct(Product product) throws SQLException; boolean updateProduct(Product product) throws SQLException; boolean deleteProduct(int id) throws SQLException; boolean updateStock(int productId, int quantity) throws SQLException; } // 订单服务 public interface OrderService { Order createOrder(int userId, List<CartItem> cartItems) throws SQLException; Order getOrderDetails(int id) throws SQLException; List<Order> getUserOrders(int userId) throws SQLException; boolean updateOrderStatus(int id, String status) throws SQLException; } // 购物车服务 public interface CartService { void addToCart(HttpSession session, CartItem item); void updateCartItem(HttpSession session, int productId, int quantity); void removeCartItem(HttpSession session, int productId); void clearCart(HttpSession session); List<CartItem> getCartItems(HttpSession session); BigDecimal getCartTotal(HttpSession session); }
控制层
// 用户控制器 @WebServlet("/UserController") public class UserController extends HttpServlet { private UserService userService; @Override public void init() throws ServletException { // 初始化UserService userService = new UserServiceImpl(new UserDAOImpl()); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); try { switch (action) { case "register": register(request, response); break; case "login": login(request, response); break; case "updateProfile": updateProfile(request, response); break; case "logout": logout(request, response); break; } } catch (SQLException e) { throw new ServletException("Database error", e); } } private void register(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { // 获取表单数据 String username = request.getParameter("username"); String password = request.getParameter("password"); String email = request.getParameter("email"); String phone = request.getParameter("phone"); String address = request.getParameter("address"); // 创建用户对象 User user = new User(); user.setUsername(username); user.setPassword(password); user.setEmail(email); user.setPhone(phone); user.setAddress(address); user.setRegisterDate(new Date()); // 注册用户 boolean success = userService.register(user); if (success) { // 注册成功,重定向到登录页面 response.sendRedirect("login.jsp?success=registered"); } else { // 注册失败,返回注册页面 request.setAttribute("error", "Registration failed"); request.getRequestDispatcher("register.jsp").forward(request, response); } } private void login(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); // 用户登录 User user = userService.login(username, password); if (user != null) { // 登录成功,将用户信息存储在session中 HttpSession session = request.getSession(); session.setAttribute("user", user); // 重定向到首页 response.sendRedirect("index.jsp"); } else { // 登录失败,返回登录页面 request.setAttribute("error", "Invalid username or password"); request.getRequestDispatcher("login.jsp").forward(request, response); } } private void updateProfile(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { // 检查用户是否登录 HttpSession session = request.getSession(); User currentUser = (User) session.getAttribute("user"); if (currentUser == null) { response.sendRedirect("login.jsp"); return; } // 获取表单数据 String email = request.getParameter("email"); String phone = request.getParameter("phone"); String address = request.getParameter("address"); // 更新用户信息 currentUser.setEmail(email); currentUser.setPhone(phone); currentUser.setAddress(address); boolean success = userService.updateUserProfile(currentUser); if (success) { // 更新成功,更新session中的用户信息 session.setAttribute("user", currentUser); // 重定向到个人中心 response.sendRedirect("profile.jsp?success=updated"); } else { // 更新失败,返回个人中心页面 request.setAttribute("error", "Update failed"); request.getRequestDispatcher("profile.jsp").forward(request, response); } } private void logout(HttpServletRequest request, HttpServletResponse response) throws IOException { // 销毁session HttpSession session = request.getSession(); session.invalidate(); // 重定向到首页 response.sendRedirect("index.jsp"); } } // 商品控制器 @WebServlet("/ProductController") public class ProductController extends HttpServlet { private ProductService productService; @Override public void init() throws ServletException { // 初始化ProductService productService = new ProductServiceImpl(new ProductDAOImpl()); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); try { switch (action) { case "list": listProducts(request, response); break; case "detail": getProductDetail(request, response); break; case "category": getProductsByCategory(request, response); break; case "search": searchProducts(request, response); break; default: listProducts(request, response); break; } } catch (SQLException e) { throw new ServletException("Database error", e); } } private void listProducts(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { // 获取所有商品 List<Product> products = productService.getAllProducts(); // 将商品列表存储在request中 request.setAttribute("products", products); // 转发到商品列表页面 request.getRequestDispatcher("productList.jsp").forward(request, response); } private void getProductDetail(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { // 获取商品ID int productId = Integer.parseInt(request.getParameter("id")); // 获取商品详情 Product product = productService.getProductDetails(productId); // 将商品信息存储在request中 request.setAttribute("product", product); // 转发到商品详情页面 request.getRequestDispatcher("productDetail.jsp").forward(request, response); } private void getProductsByCategory(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { // 获取分类ID int categoryId = Integer.parseInt(request.getParameter("categoryId")); // 获取分类下的商品 List<Product> products = productService.getProductsByCategory(categoryId); // 将商品列表存储在request中 request.setAttribute("products", products); request.setAttribute("categoryId", categoryId); // 转发到商品列表页面 request.getRequestDispatcher("productList.jsp").forward(request, response); } private void searchProducts(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { // 获取搜索关键词 String keyword = request.getParameter("keyword"); // 搜索商品 List<Product> products = productService.searchProducts(keyword); // 将搜索结果存储在request中 request.setAttribute("products", products); request.setAttribute("keyword", keyword); // 转发到搜索结果页面 request.getRequestDispatcher("searchResult.jsp").forward(request, response); } } // 购物车控制器 @WebServlet("/CartController") public class CartController extends HttpServlet { private CartService cartService; @Override public void init() throws ServletException { // 初始化CartService cartService = new CartServiceImpl(); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); try { switch (action) { case "add": addToCart(request, response); break; case "update": updateCartItem(request, response); break; case "remove": removeCartItem(request, response); break; case "clear": clearCart(request, response); break; } } catch (Exception e) { throw new ServletException("Cart operation error", e); } } private void addToCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取session HttpSession session = request.getSession(); // 获取商品信息 int productId = Integer.parseInt(request.getParameter("productId")); String productName = request.getParameter("productName"); BigDecimal price = new BigDecimal(request.getParameter("price")); int quantity = Integer.parseInt(request.getParameter("quantity")); String imageUrl = request.getParameter("imageUrl"); // 创建购物车项 CartItem item = new CartItem(); item.setProductId(productId); item.setProductName(productName); item.setPrice(price); item.setQuantity(quantity); item.setImageUrl(imageUrl); // 添加到购物车 cartService.addToCart(session, item); // 重定向回商品详情页面或购物车页面 String redirectUrl = request.getParameter("redirect"); if (redirectUrl != null && !redirectUrl.isEmpty()) { response.sendRedirect(redirectUrl); } else { response.sendRedirect("cart.jsp"); } } private void updateCartItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取session HttpSession session = request.getSession(); // 获取商品ID和数量 int productId = Integer.parseInt(request.getParameter("productId")); int quantity = Integer.parseInt(request.getParameter("quantity")); // 更新购物车项 cartService.updateCartItem(session, productId, quantity); // 重定向到购物车页面 response.sendRedirect("cart.jsp"); } private void removeCartItem(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取session HttpSession session = request.getSession(); // 获取商品ID int productId = Integer.parseInt(request.getParameter("productId")); // 从购物车中移除 cartService.removeCartItem(session, productId); // 重定向到购物车页面 response.sendRedirect("cart.jsp"); } private void clearCart(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取session HttpSession session = request.getSession(); // 清空购物车 cartService.clearCart(session); // 重定向到购物车页面 response.sendRedirect("cart.jsp"); } } // 订单控制器 @WebServlet("/OrderController") public class OrderController extends HttpServlet { private OrderService orderService; @Override public void init() throws ServletException { // 初始化OrderService orderService = new OrderServiceImpl(new OrderDAOImpl()); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); try { switch (action) { case "list": listOrders(request, response); break; case "detail": getOrderDetail(request, response); break; default: listOrders(request, response); break; } } catch (SQLException e) { throw new ServletException("Database error", e); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = request.getParameter("action"); try { if ("create".equals(action)) { createOrder(request, response); } } catch (SQLException e) { throw new ServletException("Database error", e); } } private void listOrders(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { // 检查用户是否登录 HttpSession session = request.getSession(); User user = (User) session.getAttribute("user"); if (user == null) { response.sendRedirect("login.jsp"); return; } // 获取用户订单 List<Order> orders = orderService.getUserOrders(user.getId()); // 将订单列表存储在request中 request.setAttribute("orders", orders); // 转发到订单列表页面 request.getRequestDispatcher("orderList.jsp").forward(request, response); } private void getOrderDetail(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { // 检查用户是否登录 HttpSession session = request.getSession(); User user = (User) session.getAttribute("user"); if (user == null) { response.sendRedirect("login.jsp"); return; } // 获取订单ID int orderId = Integer.parseInt(request.getParameter("id")); // 获取订单详情 Order order = orderService.getOrderDetails(orderId); // 检查订单是否属于当前用户 if (order == null || order.getUserId() != user.getId()) { response.sendRedirect("OrderController?action=list"); return; } // 将订单信息存储在request中 request.setAttribute("order", order); // 转发到订单详情页面 request.getRequestDispatcher("orderDetail.jsp").forward(request, response); } private void createOrder(HttpServletRequest request, HttpServletResponse response) throws SQLException, ServletException, IOException { // 检查用户是否登录 HttpSession session = request.getSession(); User user = (User) session.getAttribute("user"); if (user == null) { response.sendRedirect("login.jsp"); return; } // 获取购物车 List<CartItem> cartItems = (List<CartItem>) session.getAttribute("cart"); if (cartItems == null || cartItems.isEmpty()) { response.sendRedirect("cart.jsp"); return; } // 创建订单 Order order = orderService.createOrder(user.getId(), cartItems); if (order != null) { // 订单创建成功,清空购物车 session.removeAttribute("cart"); // 重定向到订单详情页面 response.sendRedirect("OrderController?action=detail&id=" + order.getId()); } else { // 订单创建失败,返回购物车页面 request.setAttribute("error", "Failed to create order"); request.getRequestDispatcher("cart.jsp").forward(request, response); } } }
6.1.2 关键功能实现
购物车功能实现
public class CartServiceImpl implements CartService { @Override public void addToCart(HttpSession session, CartItem item) { // 获取购物车 List<CartItem> cartItems = getCart(session); // 检查商品是否已在购物车中 boolean found = false; for (CartItem cartItem : cartItems) { if (cartItem.getProductId() == item.getProductId()) { // 商品已存在,增加数量 cartItem.setQuantity(cartItem.getQuantity() + item.getQuantity()); found = true; break; } } // 如果商品不在购物车中,添加新项 if (!found) { cartItems.add(item); } // 更新购物车 session.setAttribute("cart", cartItems); } @Override public void updateCartItem(HttpSession session, int productId, int quantity) { // 获取购物车 List<CartItem> cartItems = getCart(session); // 更新商品数量 for (CartItem cartItem : cartItems) { if (cartItem.getProductId() == productId) { cartItem.setQuantity(quantity); break; } } // 更新购物车 session.setAttribute("cart", cartItems); } @Override public void removeCartItem(HttpSession session, int productId) { // 获取购物车 List<CartItem> cartItems = getCart(session); // 移除商品 cartItems.removeIf(item -> item.getProductId() == productId); // 更新购物车 session.setAttribute("cart", cartItems); } @Override public void clearCart(HttpSession session) { // 清空购物车 session.removeAttribute("cart"); } @Override public List<CartItem> getCartItems(HttpSession session) { return getCart(session); } @Override public BigDecimal getCartTotal(HttpSession session) { // 获取购物车 List<CartItem> cartItems = getCart(session); // 计算总价 BigDecimal total = BigDecimal.ZERO; for (CartItem item : cartItems) { total = total.add(item.getPrice().multiply(new BigDecimal(item.getQuantity()))); } return total; } // 辅助方法:获取购物车,如果不存在则创建 @SuppressWarnings("unchecked") private List<CartItem> getCart(HttpSession session) { List<CartItem> cartItems = (List<CartItem>) session.getAttribute("cart"); if (cartItems == null) { cartItems = new ArrayList<>(); session.setAttribute("cart", cartItems); } return cartItems; } }
订单创建功能实现
public class OrderServiceImpl implements OrderService { private OrderDAO orderDAO; private ProductDAO productDAO; public OrderServiceImpl(OrderDAO orderDAO) { this.orderDAO = orderDAO; this.productDAO = new ProductDAOImpl(); } @Override public Order createOrder(int userId, List<CartItem> cartItems) throws SQLException { Connection connection = null; try { // 获取数据库连接 connection = DatabaseUtil.getConnection(); // 开始事务 connection.setAutoCommit(false); // 创建订单对象 Order order = new Order(); order.setUserId(userId); // 计算订单总金额 BigDecimal totalAmount = BigDecimal.ZERO; for (CartItem item : cartItems) { totalAmount = totalAmount.add(item.getPrice().multiply(new BigDecimal(item.getQuantity()))); } order.setTotalAmount(totalAmount); // 生成订单号 String orderNumber = generateOrderNumber(); order.setOrderNumber(orderNumber); // 设置订单状态 order.setStatus("PENDING"); // 设置订单日期 order.setOrderDate(new Date()); // 保存订单 boolean orderSaved = orderDAO.addOrder(connection, order); if (!orderSaved) { connection.rollback(); return null; } // 获取刚创建的订单ID int orderId = orderDAO.getLastInsertId(connection); order.setId(orderId); // 保存订单项 for (CartItem item : cartItems) { OrderItem orderItem = new OrderItem(); orderItem.setOrderId(orderId); orderItem.setProductId(item.getProductId()); orderItem.setQuantity(item.getQuantity()); orderItem.setPrice(item.getPrice()); boolean itemSaved = orderDAO.addOrderItem(connection, orderItem); if (!itemSaved) { connection.rollback(); return null; } // 更新商品库存 boolean stockUpdated = productDAO.updateStock(connection, item.getProductId(), -item.getQuantity()); if (!stockUpdated) { connection.rollback(); return null; } } // 提交事务 connection.commit(); return order; } catch (SQLException e) { // 回滚事务 if (connection != null) { try { connection.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } throw e; } finally { // 关闭连接 if (connection != null) { try { connection.setAutoCommit(true); connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } } // 生成订单号 private String generateOrderNumber() { // 使用时间戳和随机数生成订单号 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); String timestamp = sdf.format(new Date()); String random = String.valueOf((int) (Math.random() * 9000) + 1000); return timestamp + random; } @Override public Order getOrderDetails(int id) throws SQLException { Order order = orderDAO.getOrderById(id); if (order != null) { List<OrderItem> orderItems = orderDAO.getOrderItems(id); // 可以将订单项设置到订单对象中 } return order; } @Override public List<Order> getUserOrders(int userId) throws SQLException { return orderDAO.getOrdersByUserId(userId); } @Override public boolean updateOrderStatus(int id, String status) throws SQLException { return orderDAO.updateOrderStatus(id, status); } }
6.1.3 性能优化措施
实现商品缓存
public class ProductServiceImpl implements ProductService { private ProductDAO productDAO; private Cache productCache; public ProductServiceImpl(ProductDAO productDAO) { this.productDAO = productDAO; // 初始化缓存 CacheManager cacheManager = CacheManager.getInstance(); this.productCache = cacheManager.getCache("productCache"); } @Override public Product getProductDetails(int id) throws SQLException { // 首先尝试从缓存获取 Element element = productCache.get(id); if (element != null) { return (Product) element.getObjectValue(); } // 缓存中没有,从数据库获取 Product product = productDAO.getProductById(id); if (product != null) { // 将商品放入缓存 productCache.put(new Element(id, product)); } return product; } @Override public boolean updateProduct(Product product) throws SQLException { boolean success = productDAO.updateProduct(product); if (success) { // 更新缓存 productCache.put(new Element(product.getId(), product)); } return success; } // 其他方法实现... }
实现分页查询
public class ProductDAOImpl implements ProductDAO { @Override public List<Product> getProductsByPage(int page, int pageSize) throws SQLException { List<Product> products = new ArrayList<>(); String sql = "SELECT * FROM products LIMIT ?, ?"; try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { int offset = (page - 1) * pageSize; stmt.setInt(1, offset); stmt.setInt(2, pageSize); try (ResultSet rs = stmt.executeQuery()) { while (rs.next()) { products.add(extractProductFromResultSet(rs)); } } } return products; } @Override public int getProductCount() throws SQLException { String sql = "SELECT COUNT(*) FROM products"; try (Connection conn = DatabaseUtil.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { if (rs.next()) { return rs.getInt(1); } } return 0; } // 其他方法实现... }
分页控制器
@WebServlet("/ProductPageController") public class ProductPageController extends HttpServlet { private ProductService productService; @Override public void init() throws ServletException { productService = new ProductServiceImpl(new ProductDAOImpl()); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { // 获取页码和每页大小 int page = 1; int pageSize = 10; if (request.getParameter("page") != null) { page = Integer.parseInt(request.getParameter("page")); } if (request.getParameter("pageSize") != null) { pageSize = Integer.parseInt(request.getParameter("pageSize")); } // 获取商品总数 int totalProducts = productService.getProductCount(); // 计算总页数 int totalPages = (int) Math.ceil((double) totalProducts / pageSize); // 获取当前页的商品 List<Product> products = productService.getProductsByPage(page, pageSize); // 设置请求属性 request.setAttribute("products", products); request.setAttribute("currentPage", page); request.setAttribute("pageSize", pageSize); request.setAttribute("totalProducts", totalProducts); request.setAttribute("totalPages", totalPages); // 转发到商品列表页面 request.getRequestDispatcher("productList.jsp").forward(request, response); } catch (SQLException e) { throw new ServletException("Database error", e); } } }
分页JSP页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Product List</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"> </head> <body> <div class="container mt-4"> <h2>Product List</h2> <div class="row"> <c:forEach var="product" items="${products}"> <div class="col-md-4 mb-4"> <div class="card"> <img src="${product.imageUrl}" class="card-img-top" alt="${product.name}"> <div class="card-body"> <h5 class="card-title">${product.name}</h5> <p class="card-text">${product.description}</p> <p class="card-text"><strong>Price: $${product.price}</strong></p> <a href="ProductController?action=detail&id=${product.id}" class="btn btn-primary">View Details</a> </div> </div> </div> </c:forEach> </div> <!-- 分页导航 --> <nav aria-label="Page navigation"> <ul class="pagination justify-content-center"> <c:if test="${currentPage > 1}"> <li class="page-item"> <a class="page-link" href="ProductPageController?page=${currentPage - 1}&pageSize=${pageSize}" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> </c:if> <c:forEach begin="1" end="${totalPages}" var="i"> <c:choose> <c:when test="${currentPage eq i}"> <li class="page-item active"><a class="page-link" href="#">${i}</a></li> </c:when> <c:otherwise> <li class="page-item"><a class="page-link" href="ProductPageController?page=${i}&pageSize=${pageSize}">${i}</a></li> </c:otherwise> </c:choose> </c:forEach> <c:if test="${currentPage lt totalPages}"> <li class="page-item"> <a class="page-link" href="ProductPageController?page=${currentPage + 1}&pageSize=${pageSize}" aria-label="Next"> <span aria-hidden="true">»</span> </a> </li> </c:if> </ul> </nav> </div> </body> </html>
6.2 内容管理系统案例
6.2.1 系统架构设计
内容管理系统(CMS)通常包含以下模块:
- 用户管理模块
- 内容管理模块
- 分类管理模块
- 评论管理模块
- 前台展示模块
下面是一个基于JSP的CMS系统架构设计:
领域模型层
// 用户实体 public class User { private int id; private String username; private String password; private String email; private String role; // ADMIN, EDITOR, AUTHOR private Date createTime; // getters and setters } // 内容实体 public class Content { private int id; private String title; private String summary; private String content; private Date publishDate; private Date updateDate; private int authorId; private int categoryId; private int status; // DRAFT, PUBLISHED, ARCHIVED private int viewCount; // getters and setters } // 分类实体 public class Category { private int id; private String name; private String description; private int parentId; // getters and setters } // 评论实体 public class Comment { private int id; private int contentId; private String username; private String email; private String content; private Date commentDate; private int status; // PENDING, APPROVED, REJECTED // getters and setters }
数据访问层
// 内容DAO public interface ContentDAO { Content getContentById(int id) throws SQLException; List<Content> getAllContents() throws SQLException; List<Content> getContentsByCategory(int categoryId) throws SQLException; List<Content> getContentsByAuthor(int authorId) throws SQLException; List<Content> getPublishedContents() throws SQLException; List<Content> getRecentContents(int limit) throws SQLException; boolean addContent(Content content) throws SQLException; boolean updateContent(Content content) throws SQLException; boolean deleteContent(int id) throws SQLException; boolean updateViewCount(int id) throws SQLException; List<Content> searchContents(String keyword) throws SQLException; } // 分类DAO public interface CategoryDAO { Category getCategoryById(int id) throws SQLException; List<Category> getAllCategories() throws SQLException; List<Category> getTopCategories() throws SQLException; List<Category> getChildCategories(int parentId) throws SQLException; boolean addCategory(Category category) throws SQLException; boolean updateCategory(Category category) throws SQLException; boolean deleteCategory(int id) throws SQLException; } // 评论DAO public interface CommentDAO { Comment getCommentById(int id) throws SQLException; List<Comment> getCommentsByContent(int contentId) throws SQLException; List<Comment> getPendingComments() throws SQLException; boolean addComment(Comment comment) throws SQLException; boolean updateCommentStatus(int id, int status) throws SQLException; boolean deleteComment(int id) throws SQLException; }
业务逻辑层
// 内容服务 public interface ContentService { Content getContentDetail(int id) throws SQLException; List<Content> getAllContents() throws SQLException; List<Content> getContentsByCategory(int categoryId) throws SQLException; List<Content> getPublishedContents() throws SQLException; List<Content> getRecentContents(int limit) throws SQLException; boolean publishContent(Content content) throws SQLException; boolean saveDraft(Content content) throws SQLException; boolean updateContent(Content content) throws SQLException; boolean deleteContent(int id) throws SQLException; List<Content> searchContents(String keyword) throws SQLException; } // 分类服务 public interface CategoryService { List<Category> getAllCategories() throws SQLException; List<Category> getTopCategories() throws SQLException; List<Category> getChildCategories(int parentId) throws SQLException; boolean addCategory(Category category) throws SQLException; boolean updateCategory(Category category) throws SQLException; boolean deleteCategory(int id) throws SQLException; } // 评论服务 public interface CommentService { List<Comment> getCommentsByContent(int contentId) throws SQLException; boolean submitComment(Comment comment) throws SQLException; boolean approveComment(int id) throws SQLException; boolean rejectComment(int id) throws SQLException; boolean deleteComment(int id) throws SQLException; }
6.2.2 关键功能实现
内容发布功能实现
public class ContentServiceImpl implements ContentService { private ContentDAO contentDAO; public ContentServiceImpl(ContentDAO contentDAO) { this.contentDAO = contentDAO; } @Override public boolean publishContent(Content content) throws SQLException { // 设置发布状态 content.setStatus(Content.STATUS_PUBLISHED); // 设置发布日期 if (content.getPublishDate() == null) { content.setPublishDate(new Date()); } // 设置更新日期 content.setUpdateDate(new Date()); // 保存内容 if (content.getId() > 0) { return contentDAO.updateContent(content); } else { return contentDAO.addContent(content); } } @Override public boolean saveDraft(Content content) throws SQLException { // 设置草稿状态 content.setStatus(Content.STATUS_DRAFT); // 设置更新日期 content.setUpdateDate(new Date()); // 保存内容 if (content.getId() > 0) { return contentDAO.updateContent(content); } else { return contentDAO.addContent(content); } } @Override public Content getContentDetail(int id) throws SQLException { // 获取内容 Content content = contentDAO.getContentById(id); if (content != null) { // 增加浏览次数 contentDAO.updateViewCount(id); } return content; } // 其他方法实现... }
评论管理功能实现
public class CommentServiceImpl implements CommentService { private CommentDAO commentDAO; public CommentServiceImpl(CommentDAO commentDAO) { this.commentDAO = commentDAO; } @Override public boolean submitComment(Comment comment) throws SQLException { // 设置评论状态为待审核 comment.setStatus(Comment.STATUS_PENDING); // 设置评论日期 comment.setCommentDate(new Date()); // 保存评论 return commentDAO.addComment(comment); } @Override public boolean approveComment(int id) throws SQLException { return commentDAO.updateCommentStatus(id, Comment.STATUS_APPROVED); } @Override public boolean rejectComment(int id) throws SQLException { return commentDAO.updateCommentStatus(id, Comment.STATUS_REJECTED); } // 其他方法实现... }
6.2.3 性能优化措施
实现内容缓存
public class ContentServiceImpl implements ContentService { private ContentDAO contentDAO; private Cache contentCache; public ContentServiceImpl(ContentDAO contentDAO) { this.contentDAO = contentDAO; // 初始化缓存 CacheManager cacheManager = CacheManager.getInstance(); this.contentCache = cacheManager.getCache("contentCache"); } @Override public Content getContentDetail(int id) throws SQLException { // 首先尝试从缓存获取 Element element = contentCache.get(id); if (element != null) { Content content = (Content) element.getObjectValue(); // 增加浏览次数 contentDAO.updateViewCount(id); return content; } // 缓存中没有,从数据库获取 Content content = contentDAO.getContentById(id); if (content != null) { // 增加浏览次数 contentDAO.updateViewCount(id); // 将内容放入缓存(仅缓存已发布的内容) if (content.getStatus() == Content.STATUS_PUBLISHED) { contentCache.put(new Element(id, content)); } } return content; } @Override public boolean publishContent(Content content) throws SQLException { boolean success = contentDAO.updateContent(content); if (success) { // 更新缓存 contentCache.put(new Element(content.getId(), content)); } return success; } @Override public boolean updateContent(Content content) throws SQLException { boolean success = contentDAO.updateContent(content); if (success) { // 如果是已发布的内容,更新缓存 if (content.getStatus() == Content.STATUS_PUBLISHED) { contentCache.put(new Element(content.getId(), content)); } else { // 如果不是已发布的内容,从缓存中移除 contentCache.remove(content.getId()); } } return success; } @Override public boolean deleteContent(int id) throws SQLException { boolean success = contentDAO.deleteContent(id); if (success) { // 从缓存中移除 contentCache.remove(id); } return success; } // 其他方法实现... }
实现全文搜索
public class ContentSearchService { private ContentDAO contentDAO; private IndexWriter indexWriter; private SearcherManager searcherManager; public ContentSearchService(ContentDAO contentDAO, String indexDir) throws IOException { this.contentDAO = contentDAO; // 初始化Lucene索引 Directory directory = FSDirectory.open(Paths.get(indexDir)); IndexWriterConfig config = new IndexWriterConfig(new StandardAnalyzer()); this.indexWriter = new IndexWriter(directory, config); this.searcherManager = new SearcherManager(directory, null); } // 建立索引 public void buildIndex() throws SQLException, IOException { // 清空现有索引 indexWriter.deleteAll(); // 获取所有已发布的内容 List<Content> contents = contentDAO.getPublishedContents(); // 为每个内容创建文档并添加到索引 for (Content content : contents) { Document doc = new Document(); doc.add(new StringField("id", String.valueOf(content.getId()), Field.Store.YES)); doc.add(new TextField("title", content.getTitle(), Field.Store.YES)); doc.add(new TextField("summary", content.getSummary(), Field.Store.YES)); doc.add(new TextField("content", content.getContent(), Field.Store.YES)); indexWriter.addDocument(doc); } // 提交索引 indexWriter.commit(); // 刷新搜索器 searcherManager.maybeRefresh(); } // 搜索内容 public List<Content> search(String keyword, int maxResults) throws IOException, ParseException { List<Content> result = new ArrayList<>(); // 获取索引搜索器 IndexSearcher searcher = searcherManager.acquire(); try { // 构建查询 QueryParser parser = new QueryParser("content", new StandardAnalyzer()); Query query = parser.parse(keyword); // 执行搜索 TopDocs topDocs = searcher.search(query, maxResults); // 处理搜索结果 for (ScoreDoc scoreDoc : topDocs.scoreDocs) { Document doc = searcher.doc(scoreDoc.doc); int id = Integer.parseInt(doc.get("id")); try { // 从数据库获取完整内容 Content content = contentDAO.getContentById(id); if (content != null) { result.add(content); } } catch (SQLException e) { e.printStackTrace(); } } } finally { // 释放搜索器 searcherManager.release(searcher); } return result; } // 添加内容到索引 public void addToIndex(Content content) throws IOException { Document doc = new Document(); doc.add(new StringField("id", String.valueOf(content.getId()), Field.Store.YES)); doc.add(new TextField("title", content.getTitle(), Field.Store.YES)); doc.add(new TextField("summary", content.getSummary(), Field.Store.YES)); doc.add(new TextField("content", content.getContent(), Field.Store.YES)); indexWriter.addDocument(doc); indexWriter.commit(); // 刷新搜索器 searcherManager.maybeRefresh(); } // 从索引中删除内容 public void deleteFromIndex(int id) throws IOException { indexWriter.deleteDocuments(new Term("id", String.valueOf(id))); indexWriter.commit(); // 刷新搜索器 searcherManager.maybeRefresh(); } // 更新索引中的内容 public void updateIndex(Content content) throws IOException { // 先删除 deleteFromIndex(content.getId()); // 再添加 addToIndex(content); } // 关闭资源 public void close() throws IOException { indexWriter.close(); } }
7. 常见问题与解决方案
7.1 性能问题
7.1.1 JSP页面响应慢
问题表现:
- JSP页面首次访问特别慢
- 高并发情况下响应时间明显增加
可能原因:
- JSP页面首次访问需要编译
- 页面中包含大量Java代码
- 数据库查询效率低
- 没有使用缓存
解决方案:
预编译JSP页面
<servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <init-param> <param-name>development</param-name> <param-value>false</param-value> </init-param> <load-on-startup>3</load-on-startup> </servlet>
减少JSP中的Java代码 “`jsp <%– 不推荐的做法 –%> <% List
users = (List ) request.getAttribute(“users”); for (User user : users) { out.println("<tr>"); out.println("<td>" + user.getUsername() + "</td>"); out.println("<td>" + user.getEmail() + "</td>"); out.println("</tr>");
} %>
<%– 推荐的做法 –%> <%@ taglib uri=”http://java.sun.com/jsp/jstl/core” prefix=“c” %>
<tr> <td>${user.username}</td> <td>${user.email}</td> </tr>
/c:forEach
3. **优化数据库查询** ```java // 不推荐的做法:N+1查询问题 public List<Order> getUserOrders(int userId) throws SQLException { List<Order> orders = new ArrayList<>(); String sql = "SELECT * FROM orders WHERE user_id = ?"; try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setInt(1, userId); ResultSet rs = stmt.executeQuery(); while (rs.next()) { Order order = new Order(); order.setId(rs.getInt("id")); order.setOrderNumber(rs.getString("order_number")); order.setOrderDate(rs.getDate("order_date")); // 获取订单项 List<OrderItem> items = getOrderItems(order.getId()); order.setItems(items); orders.add(order); } } return orders; } // 推荐的做法:使用JOIN查询 public List<Order> getUserOrders(int userId) throws SQLException { Map<Integer, Order> orderMap = new HashMap<>(); String sql = "SELECT o.*, oi.id as item_id, oi.product_id, oi.quantity, oi.price " + "FROM orders o " + "LEFT JOIN order_items oi ON o.id = oi.order_id " + "WHERE o.user_id = ?"; try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setInt(1, userId); ResultSet rs = stmt.executeQuery(); while (rs.next()) { int orderId = rs.getInt("id"); Order order = orderMap.get(orderId); if (order == null) { order = new Order(); order.setId(orderId); order.setOrderNumber(rs.getString("order_number")); order.setOrderDate(rs.getDate("order_date")); order.setItems(new ArrayList<>()); orderMap.put(orderId, order); } // 添加订单项 int itemId = rs.getInt("item_id"); if (itemId > 0) { OrderItem item = new OrderItem(); item.setId(itemId); item.setProductId(rs.getInt("product_id")); item.setQuantity(rs.getInt("quantity")); item.setPrice(rs.getBigDecimal("price")); order.getItems().add(item); } } } return new ArrayList<>(orderMap.values()); }
使用缓存
public class ContentServiceImpl implements ContentService { private ContentDAO contentDAO; private Cache contentCache; public ContentServiceImpl(ContentDAO contentDAO) { this.contentDAO = contentDAO; // 初始化缓存 CacheManager cacheManager = CacheManager.getInstance(); this.contentCache = cacheManager.getCache("contentCache"); } @Override public Content getContentDetail(int id) throws SQLException { // 首先尝试从缓存获取 Element element = contentCache.get(id); if (element != null) { return (Content) element.getObjectValue(); } // 缓存中没有,从数据库获取 Content content = contentDAO.getContentById(id); if (content != null) { // 将内容放入缓存 contentCache.put(new Element(id, content)); } return content; } }
7.1.2 数据库连接泄露
问题表现:
- 应用运行一段时间后变得非常慢
- 数据库服务器连接数达到最大值
- 出现”Too many connections”错误
可能原因:
- 数据库连接未正确关闭
- 异常情况下连接未释放
解决方案:
使用try-with-resources确保连接关闭 “`java // 不推荐的做法 public User getUserById(int id) throws SQLException { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; User user = null;
try {
conn = DatabaseUtil.getConnection(); String sql = "SELECT * FROM users WHERE id = ?"; stmt = conn.prepareStatement(sql); stmt.setInt(1, id); rs = stmt.executeQuery(); if (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); }
} finally {
if (rs != null) rs.close(); if (stmt != null) stmt.close(); if (conn != null) conn.close();
}
return user; }
// 推荐的做法 public User getUserById(int id) throws SQLException {
String sql = "SELECT * FROM users WHERE id = ?"; try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setInt(1, id); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); return user; } } } return null;
}
2. **使用连接池** ```xml <!-- context.xml --> <Context> <Resource name="jdbc/myDB" auth="Container" type="javax.sql.DataSource" maxTotal="100" maxIdle="30" maxWaitMillis="10000" username="dbuser" password="dbpassword" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/mydb"/> </Context> <!-- web.xml --> <resource-ref> <description>DB Connection Pool</description> <res-ref-name>jdbc/myDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref> <!-- 获取连接的工具类 --> public class DatabaseUtil { public static Connection getConnection() { Connection conn = null; try { Context initContext = new InitialContext(); Context envContext = (Context) initContext.lookup("java:/comp/env"); DataSource ds = (DataSource) envContext.lookup("jdbc/myDB"); conn = ds.getConnection(); } catch (Exception e) { e.printStackTrace(); } return conn; } }
使用过滤器管理连接 “`java @WebFilter(”/*“) public class ConnectionFilter implements Filter {
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException { Connection conn = null; try { // 获取数据库连接 conn = DatabaseUtil.getConnection(); // 将连接存储在请求中 request.setAttribute("connection", conn); // 禁用自动提交,以便在事务中控制 conn.setAutoCommit(false); // 继续处理请求 chain.doFilter(request, response); // 提交事务 conn.commit(); } catch (SQLException e) { // 回滚事务 try { if (conn != null) { conn.rollback(); } } catch (SQLException ex) { ex.printStackTrace(); } throw new ServletException("Database error", e); } finally { // 关闭连接 try { if (conn != null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } }
}
// 其他方法… }
// 在Servlet中获取连接 public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 从请求中获取连接 Connection conn = (Connection) request.getAttribute("connection"); // 使用连接进行数据库操作 // ... }
}
### 7.2 安全问题 #### 7.2.1 SQL注入攻击 **问题表现:** - 攻击者可以通过输入特殊的SQL语句来获取未授权的数据 - 可能导致数据泄露或数据损坏 **解决方案:** 1. **使用PreparedStatement** ```java // 不推荐的做法:容易受到SQL注入攻击 public User login(String username, String password) throws SQLException { String sql = "SELECT * FROM users WHERE username = '" + username + "' AND password = '" + password + "'"; try (Connection conn = DatabaseUtil.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); return user; } } return null; } // 推荐的做法:使用PreparedStatement public User login(String username, String password) throws SQLException { String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setString(1, username); stmt.setString(2, password); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); return user; } } } return null; }
输入验证 “`java public boolean validateInput(String input) { // 检查是否包含特殊字符 if (input == null) {
return false;
}
// 使用正则表达式验证输入格式 String pattern = “^[a-zA-Z0-9_@.-]+$”; return input.matches(pattern); }
public User login(String username, String password) throws SQLException {
// 验证输入 if (!validateInput(username) || !validateInput(password)) { throw new IllegalArgumentException("Invalid input"); } String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { stmt.setString(1, username); stmt.setString(2, password); try (ResultSet rs = stmt.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); return user; } } } return null;
}
3. **使用ORM框架** ```java // 使用Hibernate等ORM框架可以进一步减少SQL注入风险 public User login(String username, String password) { Session session = HibernateUtil.getSessionFactory().openSession(); try { Query<User> query = session.createQuery( "FROM User WHERE username = :username AND password = :password", User.class); query.setParameter("username", username); query.setParameter("password", password); return query.uniqueResult(); } finally { session.close(); } }
7.2.2 XSS攻击
问题表现:
- 攻击者可以在网页中注入恶意脚本
- 可能导致用户数据被盗或网页被篡改
解决方案:
- 输出编码 “`jsp <%– 不推荐的做法:直接输出用户输入 –%>
Welcome, <%= request.getParameter(“username”) %>!
<%– 推荐的做法:对输出进行编码 –%> <%@ taglib uri=”http://java.sun.com/jsp/jstl/functions” prefix=“fn” %>
Welcome, ${fn:escapeXml(param.username)}!
2. **使用JSTL标签** ```jsp <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%-- JSTL的c:out标签会自动进行HTML编码 --%> <p>Welcome, <c:out value="${param.username}" />!</p>
输入过滤 “`java public class InputFilter { private static final Pattern[] patterns = new Pattern[] {
// Script fragments Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE), // src='...' Pattern.compile("src[rn]*=[rn]*\'(.*?)\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), Pattern.compile("src[rn]*=[rn]*\"(.*?)\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), // lonely script tags Pattern.compile("</script>", Pattern.CASE_INSENSITIVE), Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), // eval(...) Pattern.compile("eval\((.*?)\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), // expression(...) Pattern.compile("expression\((.*?)\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL), // javascript:... Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE), // vbscript:... Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE), // onload(...)=... Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
};
public static String stripXSS(String value) {
if (value == null) { return null; } String result = value; for (Pattern scriptPattern : patterns) { result = scriptPattern.matcher(result).replaceAll(""); } return result;
} }
// 在Servlet中使用 protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { String comment = InputFilter.stripXSS(request.getParameter("comment")); // 处理过滤后的评论...
}
4. **使用CSP (Content Security Policy)** ```jsp <%-- 在JSP页面头部添加CSP头 --%> <% response.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"); %>
7.2.3 CSRF攻击
问题表现:
- 攻击者可以诱使用户在已登录的状态下执行非预期的操作
- 可能导致数据被篡改或删除
解决方案:
使用同步令牌模式 “`java // 生成令牌的工具类 public class CSRFTokenUtil { public static String generateToken() {
return UUID.randomUUID().toString();
}
public static boolean isValidToken(String sessionToken, String requestToken) {
if (sessionToken == null || requestToken == null) { return false; } return sessionToken.equals(requestToken);
} }
// 在Servlet中生成令牌并存储在session中 protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { // 生成CSRF令牌 String token = CSRFTokenUtil.generateToken(); // 将令牌存储在session中 request.getSession().setAttribute("csrfToken", token); // 将令牌传递给JSP页面 request.setAttribute("csrfToken", token); request.getRequestDispatcher("form.jsp").forward(request, response);
}
// 在JSP页面中使用令牌
// 在处理表单提交的Servlet中验证令牌 protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { // 获取session中的令牌 String sessionToken = (String) request.getSession().getAttribute("csrfToken"); // 获取请求中的令牌 String requestToken = request.getParameter("csrfToken"); // 验证令牌 if (!CSRFTokenUtil.isValidToken(sessionToken, requestToken)) { // 令牌无效,拒绝请求 response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid CSRF token"); return; } // 令牌有效,处理表单提交 // ...
}
2. **使用双重提交Cookie** ```java // 在Servlet中生成令牌并存储在Cookie中 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 生成CSRF令牌 String token = CSRFTokenUtil.generateToken(); // 将令牌存储在Cookie中 Cookie csrfCookie = new Cookie("csrfToken", token); csrfCookie.setHttpOnly(true); csrfCookie.setSecure(request.isSecure()); response.addCookie(csrfCookie); // 将令牌传递给JSP页面 request.setAttribute("csrfToken", token); request.getRequestDispatcher("form.jsp").forward(request, response); } // 在JSP页面中使用令牌 <form action="submit" method="post"> <input type="hidden" name="csrfToken" value="${csrfToken}" /> <!-- 其他表单字段 --> <input type="submit" value="Submit" /> </form> // 获取Cookie值的工具方法 public static String getCookieValue(HttpServletRequest request, String cookieName) { Cookie[] cookies = request.getCookies(); if (cookies != null) { for (Cookie cookie : cookies) { if (cookieName.equals(cookie.getName())) { return cookie.getValue(); } } } return null; } // 在处理表单提交的Servlet中验证令牌 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 获取Cookie中的令牌 String cookieToken = getCookieValue(request, "csrfToken"); // 获取请求中的令牌 String requestToken = request.getParameter("csrfToken"); // 验证令牌 if (!CSRFTokenUtil.isValidToken(cookieToken, requestToken)) { // 令牌无效,拒绝请求 response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid CSRF token"); return; } // 令牌有效,处理表单提交 // ... }
7.3 并发问题
7.3.1 竞态条件
问题表现:
- 多个线程同时访问共享资源时出现不可预期的结果
- 数据可能被损坏或不一致
解决方案:
使用同步块
public class Counter { private int count = 0; // 不推荐的做法:没有同步 public void increment() { count++; } // 推荐的做法:使用同步方法 public synchronized void synchronizedIncrement() { count++; } // 或者使用同步块 public void blockIncrement() { synchronized (this) { count++; } } public int getCount() { return count; } }
使用并发集合 “`java // 不推荐的做法:使用非线程安全的集合 public class ShoppingCart { private List
items = new ArrayList<>(); public void addItem(CartItem item) {
items.add(item);
}
public List
getItems() { return items;
} }
// 推荐的做法:使用线程安全的集合 public class ShoppingCart {
private List<CartItem> items = Collections.synchronizedList(new ArrayList<>()); public void addItem(CartItem item) { items.add(item); } public List<CartItem> getItems() { return items; }
}
// 或者使用并发集合 public class ShoppingCart {
private ConcurrentHashMap<Integer, CartItem> items = new ConcurrentHashMap<>(); public void addItem(CartItem item) { items.put(item.getProductId(), item); } public Collection<CartItem> getItems() { return items.values(); }
}
3. **使用原子类** ```java // 不推荐的做法:使用普通变量 public class Counter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } // 推荐的做法:使用原子类 import java.util.concurrent.atomic.AtomicInteger; public class Counter { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } public int getCount() { return count.get(); } }
7.3.2 死锁
问题表现:
- 应用程序挂起,无法继续执行
- CPU使用率可能很低,但应用没有响应
解决方案:
避免嵌套锁 “`java // 不推荐的做法:可能导致死锁 public class Account { private int balance; private final Object lock = new Object();
public void transfer(Account target, int amount) {
synchronized (this.lock) { synchronized (target.lock) { this.balance -= amount; target.balance += amount; } }
} }
// 推荐的做法:避免嵌套锁 public class Account {
private int balance; private static final Object lock = new Object(); public void transfer(Account target, int amount) { synchronized (lock) { this.balance -= amount; target.balance += amount; } }
}
2. **使用锁超时** ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Account { private int balance; private final Lock lock = new ReentrantLock(); public void transfer(Account target, int amount) { while (true) { // 尝试获取第一个锁 if (this.lock.tryLock()) { try { // 尝试获取第二个锁 if (target.lock.tryLock()) { try { this.balance -= amount; target.balance += amount; break; } finally { target.lock.unlock(); } } } finally { this.lock.unlock(); } } // 短暂休眠,避免忙等待 try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } } }
按顺序获取锁
public class Account { private int balance; private final Object lock = new Object(); private final int id; public Account(int id, int balance) { this.id = id; this.balance = balance; } public void transfer(Account target, int amount) { // 按照账户ID的顺序获取锁,避免死锁 Account first = this.id < target.id ? this : target; Account second = this.id < target.id ? target : this; synchronized (first.lock) { synchronized (second.lock) { this.balance -= amount; target.balance += amount; } } } }
7.4 内存问题
7.4.1 内存泄漏
问题表现:
- 应用程序运行一段时间后变得非常慢
- 最终可能抛出OutOfMemoryError
- 频繁进行垃圾回收
解决方案:
避免在session中存储大量数据 “`java // 不推荐的做法:在session中存储大量数据 protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
List
products = productDAO.getAllProducts(); // 可能有大量数据 HttpSession session = request.getSession(); session.setAttribute(“products”, products); // 存储在session中 response.sendRedirect(“productList.jsp”); }
// 推荐的做法:只存储必要的数据 protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException { // 只存储当前页的数据 int page = Integer.parseInt(request.getParameter("page")); List<Product> products = productDAO.getProductsByPage(page, 10); request.setAttribute("products", products); // 存储在request中 request.setAttribute("currentPage", page); request.getRequestDispatcher("productList.jsp").forward(request, response);
}
2. **及时释放资源** ```java // 不推荐的做法:可能导致资源泄漏 public void processFile(String filePath) throws IOException { FileInputStream fis = new FileInputStream(filePath); // 处理文件... // 如果发生异常,文件流可能不会关闭 } // 推荐的做法:使用try-with-resources public void processFile(String filePath) throws IOException { try (FileInputStream fis = new FileInputStream(filePath)) { // 处理文件... // 文件流会自动关闭 } }
- 使用软引用和弱引用 “`java import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.Map;
public class Cache
private final Map<K, SoftReference<V>> cache = new HashMap<>(); public void put(K key, V value) { cache.put(key, new SoftReference<>(value)); } public V get(K key) { SoftReference<V> ref = cache.get(key); return ref != null ? ref.get() : null; } public void clear() { cache.clear(); }
}
4. **及时移除监听器** ```java // 不推荐的做法:没有移除监听器 public class DataModel { private List<DataListener> listeners = new ArrayList<>(); public void addListener(DataListener listener) { listeners.add(listener); } public void removeListener(DataListener listener) { listeners.remove(listener); } public void fireDataChanged() { for (DataListener listener : listeners) { listener.dataChanged(); } } } // 推荐的做法:使用弱引用存储监听器 import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; public class DataModel { private List<WeakReference<DataListener>> listeners = new ArrayList<>(); public void addListener(DataListener listener) { listeners.add(new WeakReference<>(listener)); } public void removeListener(DataListener listener) { for (int i = listeners.size() - 1; i >= 0; i--) { DataListener l = listeners.get(i).get(); if (l == null || l == listener) { listeners.remove(i); } } } public void fireDataChanged() { for (int i = listeners.size() - 1; i >= 0; i--) { DataListener listener = listeners.get(i).get(); if (listener == null) { listeners.remove(i); } else { listener.dataChanged(); } } } }
7.4.2 内存溢出
问题表现:
- 抛出OutOfMemoryError
- 应用程序崩溃
解决方案:
增加JVM堆内存
# 在Tomcat的setenv.sh或setenv.bat中设置 export CATALINA_OPTS="$CATALINA_OPTS -Xms512m -Xmx1024m"
优化数据加载 “`java // 不推荐的做法:一次性加载大量数据 public List
getAllOrders() throws SQLException { List orders = new ArrayList<>(); String sql = “SELECT * FROM orders”; try (Connection conn = DatabaseUtil.getConnection();
Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { while (rs.next()) { Order order = new Order(); order.setId(rs.getInt("id")); order.setOrderNumber(rs.getString("order_number")); // 设置其他属性... orders.add(order); }
}
return orders; }
// 推荐的做法:分页加载数据 public List
List<Order> orders = new ArrayList<>(); String sql = "SELECT * FROM orders LIMIT ?, ?"; try (Connection conn = DatabaseUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement(sql)) { int offset = (page - 1) * pageSize; stmt.setInt(1, offset); stmt.setInt(2, pageSize); try (ResultSet rs = stmt.executeQuery()) { while (rs.next()) { Order order = new Order(); order.setId(rs.getInt("id")); order.setOrderNumber(rs.getString("order_number")); // 设置其他属性... orders.add(order); } } } return orders;
}
3. **使用流式处理** ```java // 不推荐的做法:一次性加载所有数据到内存 public void exportOrders(HttpServletResponse response) throws SQLException, IOException { List<Order> orders = orderDAO.getAllOrders(); response.setContentType("text/csv"); PrintWriter writer = response.getWriter(); // 写入CSV头 writer.println("ID,Order Number,Date,Total"); // 写入订单数据 for (Order order : orders) { writer.println(order.getId() + "," + order.getOrderNumber() + "," + order.getOrderDate() + "," + order.getTotalAmount()); } } // 推荐的做法:使用流式处理 public void exportOrders(HttpServletResponse response) throws SQLException, IOException { response.setContentType("text/csv"); PrintWriter writer = response.getWriter(); // 写入CSV头 writer.println("ID,Order Number,Date,Total"); // 从数据库流式读取订单数据 String sql = "SELECT * FROM orders"; try (Connection conn = DatabaseUtil.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { while (rs.next()) { writer.println(rs.getInt("id") + "," + rs.getString("order_number") + "," + rs.getDate("order_date") + "," + rs.getBigDecimal("total_amount")); } } }
8. 最佳实践与进阶技巧
8.1 代码组织最佳实践
8.1.1 包结构设计
良好的包结构可以提高代码的可维护性和可读性。以下是一个推荐的JSP应用包结构:
com.example.app ├── config // 配置类 ├── controller // 控制器 ├── dao // 数据访问对象 ├── model // 领域模型 ├── service // 业务逻辑服务 ├── util // 工具类 ├── filter // 过滤器 ├── listener // 监听器 └── exception // 异常类
8.1.2 命名规范
良好的命名规范可以提高代码的可读性和一致性:
- 类名:使用大驼峰命名法(PascalCase),如
UserService
、ProductDAO
- 方法名:使用小驼峰命名法(camelCase),如
getUserById
、updateProduct
- 变量名:使用小驼峰命名法(camelCase),如
userName
、productList
- 常量名:使用全大写,单词间用下划线分隔,如
MAX_CONNECTIONS
- 包名:使用全小写,单词间用点分隔,如
com.example.app.dao
8.1.3 代码注释规范
良好的注释可以帮助其他开发者理解代码:
/** * 用户服务类,提供用户相关的业务逻辑 * * @author John Doe * @version 1.0 * @since 2023-01-01 */ public class UserService { /** * 用户登录验证 * * @param username 用户名 * @param password 密码 * @return 登录成功返回用户对象,失败返回null * @throws SQLException 如果数据库操作出错 */ public User login(String username, String password) throws SQLException { // 方法实现... } }
8.2 设计模式应用
8.2.1 单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点:
public class DatabaseUtil { private static DataSource dataSource; // 私有构造方法,防止外部实例化 private DatabaseUtil() {} // 获取数据源的单例 public static synchronized DataSource getDataSource() { if (dataSource == null) { try { Context initContext = new InitialContext(); Context envContext = (Context) initContext.lookup("java:/comp/env"); dataSource = (DataSource) envContext.lookup("jdbc/myDB"); } catch (NamingException e) { e.printStackTrace(); } } return dataSource; } // 获取数据库连接 public static Connection getConnection() throws SQLException { return getDataSource().getConnection(); } }
8.2.2 工厂模式
工厂模式提供一个创建对象的接口,让子类决定实例化哪个类:
public interface DAOFactory { UserDAO getUserDAO(); ProductDAO getProductDAO(); OrderDAO getOrderDAO(); } public class MySQLDAOFactory implements DAOFactory { @Override public UserDAO getUserDAO() { return new MySQLUserDAO(); } @Override public ProductDAO getProductDAO() { return new MySQLProductDAO(); } @Override public OrderDAO getOrderDAO() { return new MySQLOrderDAO(); } } public class OracleDAOFactory implements DAOFactory { @Override public UserDAO getUserDAO() { return new OracleUserDAO(); } @Override public ProductDAO getProductDAO() { return new OracleProductDAO(); } @Override public OrderDAO getOrderDAO() { return new OracleOrderDAO(); } } // 使用工厂创建DAO public class UserService { private UserDAO userDAO; public UserService(DAOFactory factory) { this.userDAO = factory.getUserDAO(); } // 其他方法... }
8.2.3 观察者模式
观察者模式定义对象间的一种一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新:
// 观察者接口 public interface Observer { void update(Observable observable, Object arg); } // 被观察者接口 public interface Observable { void addObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(Object arg); } // 具体的被观察者 public class Product implements Observable { private List<Observer> observers = new ArrayList<>(); private String name; private BigDecimal price; public Product(String name, BigDecimal price) { this.name = name; this.price = price; } @Override public void addObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { observers.remove(observer); } @Override public void notifyObservers(Object arg) { for (Observer observer : observers) { observer.update(this, arg); } } public void setPrice(BigDecimal price) { BigDecimal oldPrice = this.price; this.price = price; // 价格发生变化,通知观察者 if (!oldPrice.equals(price)) { notifyObservers("Price changed from " + oldPrice + " to " + price); } } // Getter方法... } // 具体的观察者 public class PriceMonitor implements Observer { @Override public void update(Observable observable, Object arg) { if (observable instanceof Product) { Product product = (Product) observable; System.out.println("Price update for " + product.getName() + ": " + arg); } } } // 使用观察者模式 public class Main { public static void main(String[] args) { Product laptop = new Product("Laptop", new BigDecimal("999.99")); // 添加价格监控器 laptop.addObserver(new PriceMonitor()); // 修改价格,会触发通知 laptop.setPrice(new BigDecimal("899.99")); } }
8.3 测试策略
8.3.1 单元测试
单元测试是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作:
import static org.junit.Assert.*; import org.junit.Test; import org.junit.Before; public class UserServiceTest { private UserService userService; private UserDAO mockUserDAO; @Before public void setUp() { // 创建模拟的UserDAO mockUserDAO = new MockUserDAO(); // 创建UserService实例 userService = new UserService(mockUserDAO); } @Test public void testLoginSuccess() throws SQLException { // 测试登录成功的情况 User user = userService.login("john", "password123"); assertNotNull(user); assertEquals("john", user.getUsername()); } @Test public void testLoginFailure() throws SQLException { // 测试登录失败的情况 User user = userService.login("john", "wrongpassword"); assertNull(user); } // 模拟的UserDAO实现 private static class MockUserDAO implements UserDAO { @Override public User getUserByUsername(String username) throws SQLException { if ("john".equals(username)) { User user = new User(); user.setId(1); user.setUsername("john"); user.setPassword("password123"); user.setEmail("john@example.com"); return user; } return null; } // 其他方法实现... } }
8.3.2 集成测试
集成测试是测试多个模块或系统组合在一起时的交互:
import static org.junit.Assert.*; import org.junit.Test; import org.junit.Before; import org.junit.After; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) public class UserIntegrationTest { private UserService userService; private Connection connection; @Before public void setUp() throws SQLException { // 获取真实的数据库连接 connection = DatabaseUtil.getConnection(); // 创建真实的DAO UserDAO userDAO = new MySQLUserDAO(connection); // 创建UserService实例 userService = new UserService(userDAO); // 准备测试数据 prepareTestData(); } @After public void tearDown() throws SQLException { // 清理测试数据 cleanupTestData(); // 关闭数据库连接 if (connection != null) { connection.close(); } } @Test public void testRegisterAndLogin() throws SQLException { // 注册新用户 User newUser = new User(); newUser.setUsername("testuser"); newUser.setPassword("testpass"); newUser.setEmail("test@example.com"); boolean registered = userService.register(newUser); assertTrue(registered); // 使用注册的凭据登录 User loggedInUser = userService.login("testuser", "testpass"); assertNotNull(loggedInUser); assertEquals("testuser", loggedInUser.getUsername()); assertEquals("test@example.com", loggedInUser.getEmail()); } private void prepareTestData() throws SQLException { // 准备测试数据 // ... } private void cleanupTestData() throws SQLException { // 清理测试数据 // ... } }
8.3.3 使用Mock对象
Mock对象可以模拟依赖的行为,使测试更加简单和可控:
import static org.mockito.Mockito.*; import static org.junit.Assert.*; import org.junit.Test; import org.junit.Before; import org.mockito.Mock; import org.mockito.InjectMocks; import org.mockito.MockitoAnnotations; public class UserServiceMockTest { @Mock private UserDAO userDAO; @InjectMocks private UserService userService; @Before public void setUp() { MockitoAnnotations.initMocks(this); } @Test public void testLoginSuccess() throws SQLException { // 准备测试数据 User mockUser = new User(); mockUser.setId(1); mockUser.setUsername("john"); mockUser.setPassword("password123"); mockUser.setEmail("john@example.com"); // 设置Mock行为 when(userDAO.getUserByUsername("john")).thenReturn(mockUser); // 调用测试方法 User result = userService.login("john", "password123"); // 验证结果 assertNotNull(result); assertEquals("john", result.getUsername()); // 验证Mock对象的调用 verify(userDAO).getUserByUsername("john"); } @Test public void testLoginFailure() throws SQLException { // 设置Mock行为 when(userDAO.getUserByUsername("unknown")).thenReturn(null); // 调用测试方法 User result = userService.login("unknown", "password"); // 验证结果 assertNull(result); // 验证Mock对象的调用 verify(userDAO).getUserByUsername("unknown"); } }
8.4 日志管理
8.4.1 使用SLF4J和Logback
SLF4J是一个简单的日志门面,Logback是一个日志实现框架:
<!-- Maven依赖 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
logback.xml配置文件
<?xml version="1.0" encoding="UTF-8"?> <configuration> <!-- 控制台输出 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- 文件输出 --> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/application.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- 错误文件输出 --> <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>logs/error.log</file> <filter class="ch.qos.logback.classic.filter.LevelFilter"> <level>ERROR</level> <onMatch>ACCEPT</onMatch> <onMismatch>DENY</onMismatch> </filter> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>logs/error.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern> </encoder> </appender> <!-- 设置日志级别 --> <root level="INFO"> <appender-ref ref="STDOUT" /> <appender-ref ref="FILE" /> <appender-ref ref="ERROR_FILE" /> </root> <!-- 特定包的日志级别 --> <logger name="com.example.app.dao" level="DEBUG" /> </configuration>
在代码中使用日志
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class UserService { private static final Logger logger = LoggerFactory.getLogger(UserService.class); private UserDAO userDAO; public UserService(UserDAO userDAO) { this.userDAO = userDAO; } public User login(String username, String password) throws SQLException { logger.debug("Attempting login for user: {}", username); try { User user = userDAO.getUserByUsername(username); if (user != null && user.getPassword().equals(password)) { logger.info("User {} logged in successfully", username); return user; } else { logger.warn("Login failed for user: {}", username); return null; } } catch (SQLException e) { logger.error("Database error during login for user: " + username, e); throw e; } } }
8.4.2 日志级别和最佳实践
日志级别从低到高分为:
- TRACE:非常详细的日志,通常只在开发过程中使用
- DEBUG:调试信息,用于开发阶段
- INFO:一般信息,表示应用程序的正常运行
- WARN:警告信息,表示可能的问题
- ERROR:错误信息,表示需要关注的问题
日志最佳实践:
- 使用适当的日志级别 “`java // 使用DEBUG级别记录详细的调试信息 logger.debug(“Processing request with parameters: {}”, params);
// 使用INFO级别记录重要的业务事件 logger.info(“User {} has placed order {}”, username, orderId);
// 使用WARN级别记录潜在的问题 logger.warn(“Database connection pool is running low, {} connections left”, poolSize);
// 使用ERROR级别记录错误和异常 logger.error(“Failed to process payment for order {}”, orderId, e);
2. **避免记录敏感信息** ```java // 不推荐的做法:记录密码 logger.info("User login attempt: username={}, password={}", username, password); // 推荐的做法:不记录敏感信息 logger.info("User login attempt: username={}", username);
- 使用参数化日志 “`java // 不推荐的做法:字符串拼接 logger.debug(“Processing request from ” + user.getIpAddress() + “ for user ” + user.getUsername());
// 推荐的做法:使用参数化 logger.debug(“Processing request from {} for user {}”, user.getIpAddress(), user.getUsername());
4. **在异常处理中记录日志** ```java public User getUserById(int id) { try { return userDAO.getUserById(id); } catch (SQLException e) { logger.error("Failed to get user by id: " + id, e); throw new RuntimeException("Database error", e); } }
8.5 持续集成与部署
8.5.1 使用Maven构建项目
Maven是一个项目管理工具,可以自动化构建过程:
pom.xml示例
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>webapp</artifactId> <version>1.0.0</version> <packaging>war</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- Servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- JSP API --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.3</version> <scope>provided</scope> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> <!-- SLF4J + Logback --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> <!-- JUnit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13</version> <scope>test</scope> </dependency> <!-- Mockito --> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>3.5.13</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>webapp</finalName> <plugins> <!-- Maven编译插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <!-- Maven WAR插件 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>3.3.1</version> </plugin> <!-- Tomcat Maven插件 --> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <path>/webapp</path> <port>8080</port> </configuration> </plugin> <!-- Maven Surefire插件(用于运行单元测试) --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.0.0-M5</version> </plugin> </plugins> </build> </project>
8.5.2 使用Jenkins进行持续集成
Jenkins是一个开源的持续集成工具,可以自动化构建和测试过程:
Jenkinsfile示例
pipeline { agent any environment { MAVEN_HOME = tool 'Maven 3.6.3' JAVA_HOME = tool 'JDK 1.8' } stages { stage('Checkout') { steps { git 'https://github.com/example/webapp.git' } } stage('Build') { steps { sh '${MAVEN_HOME}/bin/mvn clean compile' } } stage('Test') { steps { sh '${MAVEN_HOME}/bin/mvn test' } post { always { junit 'target/surefire-reports/*.xml' } } } stage('Package') { steps { sh '${MAVEN_HOME}/bin/mvn package -DskipTests' } } stage('Deploy to Staging') { steps { sh '${MAVEN_HOME}/bin/mvn tomcat7:deploy -DskipTests' } } } post { always { cleanWs() } success { echo 'Pipeline succeeded!' } failure { echo 'Pipeline failed!' } } }
8.5.3 自动化部署
自动化部署可以减少人为错误,提高部署效率:
使用Ansible进行自动化部署
--- - hosts: webservers become: yes vars: tomcat_home: /opt/tomcat webapp_name: webapp webapp_version: 1.0.0 webapp_war: "{{ webapp_name }}-{{ webapp_version }}.war" tasks: - name: Stop Tomcat systemd: name: tomcat state: stopped - name: Backup existing webapp command: "cp {{ tomcat_home }}/webapps/{{ webapp_name }}.war {{ tomcat_home }}/webapps/{{ webapp_name }}.war.bak" ignore_errors: yes - name: Deploy new webapp copy: src: "target/{{ webapp_war }}" dest: "{{ tomcat_home }}/webapps/{{ webapp_name }}.war" owner: tomcat group: tomcat mode: 0644 - name: Start Tomcat systemd: name: tomcat state: started enabled: yes - name: Wait for Tomcat to start wait_for: port: 8080 delay: 10 timeout: 60 - name: Verify deployment uri: url: "http://localhost:8080/{{ webapp_name }}" method: GET status_code: 200
使用Docker进行容器化部署
Dockerfile
FROM tomcat:9-jre8 # 移除默认应用 RUN rm -rf /usr/local/tomcat/webapps/* # 复制WAR文件 COPY target/webapp.war /usr/local/tomcat/webapps/ROOT.war # 设置环境变量 ENV CATALINA_OPTS="-Xms512m -Xmx1024m" # 暴露端口 EXPOSE 8080 # 启动Tomcat CMD ["catalina.sh", "run"]
docker-compose.yml
version: '3' services: webapp: build: . ports: - "8080:8080" environment: - DB_HOST=db - DB_NAME=webapp - DB_USER=webapp - DB_PASSWORD=webapp123 depends_on: - db networks: - webapp-network db: image: mysql:8.0 environment: - MYSQL_ROOT_PASSWORD=rootpassword - MYSQL_DATABASE=webapp - MYSQL_USER=webapp - MYSQL_PASSWORD=webapp123 volumes: - db-data:/var/lib/mysql networks: - webapp-network networks: webapp-network: volumes: db-data:
总结
JSP系统架构从入门到精通是一个循序渐进的过程,需要掌握从基础的JSP语法到高级的架构设计和性能优化技术。本文详细介绍了JSP系统架构的各个方面,包括:
JSP基础概念:介绍了JSP的工作原理、基本语法和内置对象,为后续学习打下基础。
MVC模式:详细解释了MVC模式的原理和在JSP中的实现,展示了如何通过MVC模式分离关注点,提高代码的可维护性。
分层架构:深入探讨了表示层、控制层、业务逻辑层和数据访问层的设计和实现,提供了完整的代码示例。
高性能构建策略:从JSP页面优化、数据库优化、缓存策略、异步处理和前端优化等多个角度,详细介绍了构建高性能JSP应用的方法和技巧。
实战案例分析:通过电子商务网站和内容管理系统两个典型案例,展示了如何将理论知识应用到实际项目中。
常见问题与解决方案:分析了JSP开发中常见的性能问题、安全问题、并发问题和内存问题,并提供了相应的解决方案。
最佳实践与进阶技巧:介绍了代码组织、设计模式应用、测试策略、日志管理和持续集成与部署等方面的最佳实践,帮助开发者提高开发效率和代码质量。
通过学习和应用本文介绍的知识和技术,开发者可以构建出高性能、高可用、易维护的JSP Web应用。同时,随着技术的发展,JSP也在不断演进,结合现代前端框架和微服务架构,JSP仍然可以发挥重要作用。希望本文能够帮助读者从入门到精通,成为JSP系统架构的专家。