1. JavaWeb项目实战概述
作为一名JavaWeb开发者,我经常被问到如何将学习到的理论知识转化为实际项目能力。这个实战笔记系列就是基于我多年开发经验总结而成,旨在帮助开发者快速掌握JavaWeb项目开发的核心要点。JavaWeb开发不仅仅是学习Servlet和JSP,更重要的是理解如何将这些技术组合起来构建一个完整的Web应用系统。
在真实的项目开发中,我们需要考虑的问题远比课堂示例复杂得多:如何设计合理的MVC架构?如何处理并发访问?如何优化数据库操作?这些都是在实际开发中必须面对的挑战。本系列笔记将从零开始,带你构建一个完整的JavaWeb项目,涵盖从环境搭建到部署上线的全过程。
2. 开发环境准备与项目初始化
2.1 开发工具选择与配置
我强烈推荐使用IntelliJ IDEA作为开发工具,它是目前Java开发领域最强大的IDE。在IDEA中创建Maven项目时,建议选择以下配置:
- GroupId: com.yourcompany
- ArtifactId: javaweb-demo
- Packaging: war
- Java版本: 1.8或更高
pom.xml中需要添加的关键依赖包括:
<dependencies> <!-- Servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- JSTL --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- MySQL Connector --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.25</version> </dependency> </dependencies>2.2 项目目录结构规范
一个规范的JavaWeb项目目录结构对后期维护至关重要。我通常采用以下结构:
src/ ├── main/ │ ├── java/ # Java源代码 │ │ └── com/ │ │ └── yourcompany/ │ │ ├── controller/ # 控制器 │ │ ├── service/ # 业务逻辑 │ │ ├── dao/ # 数据访问 │ │ └── model/ # 数据模型 │ ├── resources/ # 配置文件 │ │ ├── db.properties │ │ └── log4j2.xml │ └── webapp/ # Web资源 │ ├── WEB-INF/ │ │ ├── views/ # JSP页面 │ │ └── web.xml # 部署描述符 │ └── static/ # 静态资源 └── test/ # 测试代码 ├── java/ └── resources/提示:在IDEA中,确保将webapp目录标记为Web资源根目录(右键目录 → Mark Directory as → Web Resource Directory)
3. 数据库设计与数据访问层实现
3.1 数据库表设计
以用户管理系统为例,我们先设计users表:
CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(100) NOT NULL, `email` varchar(100) NOT NULL, `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `username_UNIQUE` (`username`), UNIQUE KEY `email_UNIQUE` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;3.2 DAO层实现
我推荐使用JDBC Template而不是原生JDBC,它能显著减少样板代码。首先创建数据库连接工具类:
public class DBUtil { private static DataSource dataSource; static { try { Properties props = new Properties(); props.load(DBUtil.class.getClassLoader().getResourceAsStream("db.properties")); HikariConfig config = new HikariConfig(); config.setJdbcUrl(props.getProperty("db.url")); config.setUsername(props.getProperty("db.username")); config.setPassword(props.getProperty("db.password")); config.setMaximumPoolSize(20); dataSource = new HikariDataSource(config); } catch (IOException e) { throw new RuntimeException("Failed to initialize database connection", e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static JdbcTemplate getJdbcTemplate() { return new JdbcTemplate(dataSource); } }然后实现UserDAO:
public class UserDAO { private JdbcTemplate jdbcTemplate = DBUtil.getJdbcTemplate(); public User findById(int id) { String sql = "SELECT * FROM users WHERE id = ?"; return jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<>(User.class)); } public List<User> findAll() { String sql = "SELECT * FROM users"; return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class)); } public int save(User user) { String sql = "INSERT INTO users(username, password, email) VALUES(?, ?, ?)"; return jdbcTemplate.update(sql, user.getUsername(), user.getPassword(), user.getEmail()); } // 其他CRUD方法... }4. 业务逻辑层与服务设计
4.1 服务层接口设计
服务层应该面向接口编程,这有利于单元测试和后期扩展:
public interface UserService { User register(User user) throws BusinessException; User login(String username, String password) throws BusinessException; void updateProfile(User user) throws BusinessException; void changePassword(int userId, String oldPassword, String newPassword) throws BusinessException; // 其他业务方法... }4.2 服务实现与事务管理
服务实现类需要处理业务逻辑和事务:
public class UserServiceImpl implements UserService { private UserDAO userDAO = new UserDAO(); @Override public User register(User user) throws BusinessException { // 检查用户名是否已存在 if (userDAO.existsByUsername(user.getUsername())) { throw new BusinessException("用户名已存在"); } // 密码加密 String encryptedPassword = PasswordUtil.encrypt(user.getPassword()); user.setPassword(encryptedPassword); // 保存用户 int affectedRows = userDAO.save(user); if (affectedRows == 0) { throw new BusinessException("注册失败,请稍后重试"); } return user; } // 其他方法实现... }注意:在实际项目中,应该使用Spring等框架的事务管理,这里为了演示简化了事务处理
5. Web层与控制器实现
5.1 Servlet控制器设计
我推荐使用前端控制器模式,统一处理请求分发:
@WebServlet("/") public class DispatcherServlet extends HttpServlet { private Map<String, Controller> controllers = new HashMap<>(); @Override public void init() throws ServletException { // 注册控制器 controllers.put("user", new UserController()); controllers.put("auth", new AuthController()); // 其他控制器... } @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String path = req.getPathInfo(); if (path == null || path.equals("/")) { path = "/index"; } String[] parts = path.substring(1).split("/"); if (parts.length < 1) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } String controllerKey = parts[0]; Controller controller = controllers.get(controllerKey); if (controller == null) { resp.sendError(HttpServletResponse.SC_NOT_FOUND); return; } try { String viewName = controller.handleRequest(req, resp, parts); if (viewName != null && !viewName.startsWith("redirect:")) { req.getRequestDispatcher("/WEB-INF/views/" + viewName + ".jsp").forward(req, resp); } else if (viewName != null) { resp.sendRedirect(viewName.substring("redirect:".length())); } } catch (Exception e) { throw new ServletException("Controller processing failed", e); } } }5.2 用户控制器实现示例
public class UserController implements Controller { private UserService userService = new UserServiceImpl(); @Override public String handleRequest(HttpServletRequest req, HttpServletResponse resp, String[] pathParts) throws Exception { String method = req.getMethod(); if (pathParts.length == 1) { if ("GET".equals(method)) { return listUsers(req, resp); } else if ("POST".equals(method)) { return createUser(req, resp); } } else if (pathParts.length == 2) { int userId = Integer.parseInt(pathParts[1]); if ("GET".equals(method)) { return showUser(req, resp, userId); } else if ("PUT".equals(method)) { return updateUser(req, resp, userId); } else if ("DELETE".equals(method)) { return deleteUser(req, resp, userId); } } throw new UnsupportedOperationException("Unsupported operation"); } private String listUsers(HttpServletRequest req, HttpServletResponse resp) { List<User> users = userService.findAll(); req.setAttribute("users", users); return "user/list"; } // 其他处理方法... }6. 视图层与前端整合
6.1 JSP页面设计
使用JSTL和EL表达式简化JSP开发:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <title>用户列表</title> <link rel="stylesheet" href="${pageContext.request.contextPath}/static/css/style.css"> </head> <body> <div class="container"> <h1>用户列表</h1> <table class="table"> <thead> <tr> <th>ID</th> <th>用户名</th> <th>邮箱</th> <th>操作</th> </tr> </thead> <tbody> <c:forEach items="${users}" var="user"> <tr> <td>${user.id}</td> <td>${user.username}</td> <td>${user.email}</td> <td> <a href="/user/${user.id}" class="btn btn-info">查看</a> </td> </tr> </c:forEach> </tbody> </table> </div> </body> </html>6.2 静态资源处理
在web.xml中配置静态资源处理:
<servlet-mapping> <servlet-name>default</servlet-name> <url-pattern>/static/*</url-pattern> </servlet-mapping>这样所有/static/下的资源都会由默认Servlet处理,不会被我们的DispatcherServlet拦截。
7. 项目部署与性能优化
7.1 Tomcat配置优化
在生产环境部署时,需要对Tomcat进行优化配置。在conf/server.xml中修改Connector配置:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="200" minSpareThreads="10" acceptCount="100" enableLookups="false" compression="on" compressionMinSize="2048" compressableMimeType="text/html,text/xml,text/css,application/javascript"/>7.2 数据库连接池调优
HikariCP是性能最好的连接池之一,生产环境推荐配置:
# db.properties db.url=jdbc:mysql://localhost:3306/javaweb_demo?useSSL=false&serverTimezone=UTC&characterEncoding=utf8 db.username=root db.password=yourpassword # 连接池配置 maximumPoolSize=20 minimumIdle=10 maxLifetime=1800000 connectionTimeout=30000 idleTimeout=6000008. 常见问题排查与调试技巧
8.1 中文乱码问题解决方案
JavaWeb开发中最常见的问题之一就是中文乱码。我总结了一套完整的解决方案:
- JSP页面头部添加:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">- 在Servlet中添加过滤器:
@WebFilter("/*") public class EncodingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); chain.doFilter(request, response); } // 其他方法... }- 数据库连接字符串添加参数:
jdbc:mysql://...?useUnicode=true&characterEncoding=UTF-88.2 性能问题排查
当遇到性能问题时,可以使用以下工具进行排查:
- VisualVM:监控JVM内存、线程和CPU使用情况
- JProfiler:深入分析内存泄漏和性能瓶颈
- MySQL慢查询日志:找出执行缓慢的SQL语句
- Chrome开发者工具:分析前端性能问题
9. 项目安全防护措施
9.1 SQL注入防护
使用PreparedStatement可以有效防止SQL注入:
public User findByUsernameAndPassword(String username, String password) { String sql = "SELECT * FROM users WHERE username = ? AND password = ?"; return jdbcTemplate.queryForObject(sql, new Object[]{username, password}, new BeanPropertyRowMapper<>(User.class)); }9.2 XSS防护
在输出用户输入内容时,使用JSTL的c:out标签或ESAPI进行转义:
<td><c:out value="${user.bio}"/></td>或者在Servlet中使用:
import org.owasp.esapi.ESAPI; String safeOutput = ESAPI.encoder().encodeForHTML(userInput);9.3 CSRF防护
实现CSRF令牌机制:
public class CSRFTokenUtil { public static String generateToken(HttpSession session) { String token = UUID.randomUUID().toString(); session.setAttribute("CSRF_TOKEN", token); return token; } public static boolean isValidToken(HttpServletRequest request) { String sessionToken = (String) request.getSession().getAttribute("CSRF_TOKEN"); String requestToken = request.getParameter("csrfToken"); return sessionToken != null && sessionToken.equals(requestToken); } }10. 项目扩展与进阶方向
完成基础功能后,可以考虑以下扩展方向:
- 引入Spring框架:逐步替换手写的MVC和IoC容器
- 添加Redis缓存:提升系统性能
- 实现分布式会话:使用Spring Session
- 引入消息队列:处理异步任务
- 微服务化改造:将单体应用拆分为微服务
- 前后端分离:使用RESTful API+Vue.js/React
在实际开发中,我经常遇到的一个问题是数据库连接泄漏。通过以下方法可以有效预防:
public class DBUtil { public static void closeQuietly(Connection conn) { if (conn != null) { try { if (!conn.isClosed()) { conn.close(); } } catch (SQLException e) { // 记录日志但不要抛出异常 Logger.error("Failed to close connection", e); } } } // 其他资源关闭方法... }另一个实用技巧是使用Filter统一处理异常:
@WebFilter("/*") public class ExceptionHandlerFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { try { chain.doFilter(request, response); } catch (Exception e) { handleException((HttpServletRequest)request, (HttpServletResponse)response, e); } } private void handleException(HttpServletRequest request, HttpServletResponse response, Exception e) throws IOException { if (e instanceof BusinessException) { request.setAttribute("errorMessage", e.getMessage()); request.getRequestDispatcher("/WEB-INF/views/error/businessError.jsp").forward(request, response); } else { Logger.error("Unexpected error", e); response.sendRedirect("/error/500"); } } }