常见坑与最佳实践
Java Web 开发的坑高度集中,下面这张表是"入行三年内必踩榜"。遇到诡异问题,先来这里对号入座。
| 坑 | 症状 & 根治办法 |
|---|---|
| 中文乱码 | 请求乱码:POST 在 Filter 里 request.setCharacterEncoding("UTF-8")(GET 乱码是 Tomcat 8+ 默认 UTF-8,一般不用管);响应乱码:response.setContentType("text/html;charset=UTF-8");数据库乱码:建库 utf8mb4,JDBC URL 带 characterEncoding=utf8。三层编码统一,一处都不能漏。 |
| SQL 注入 | 用 Statement 拼 SQL。根治:一切参数用 PreparedStatement 的 ? 占位符。 |
| XSS 跨站脚本 | 把用户留言直接 out.write(content),别人写 <script>偷cookie</script> 就中招。JSP 里用 <c:out value="${...}"/> 自动转义。 |
| Session 固定攻击 | 登录成功后不换 SessionId,攻击者拿预植的 SessionId 就能冒充你。登录成功后立刻 request.changeSessionId()(或旧的 session.invalidate() 后新建)。 |
| 连接泄漏 | JDBC 用完不关。根治:try-with-resources 包住 Connection,加上连接池的泄漏检测。 |
| 密码明文存库 | 数据库一脱库,用户全线裸奔。用 BCrypt / Argon2 哈希加盐,永远不可逆。 |
| JSP 里写 Java | 业务逻辑塞进脚本片段,维护噩梦。规矩:JSP 只展示,业务在 Servlet/Service。 |
| doGet 改数据 | 用 GET 请求删库,爬虫一爬就删光。增删改一律 POST/PUT/DELETE,GET 只读。 |
面试高频速查:一分钟答得上
| 问题 | 一句话答案 |
|---|---|
| GET 和 POST 区别 | GET 参数在 URL、幂等、可缓存;POST 参数在请求体、敏感、用于提交修改。本质都是 HTTP 方法,没有"POST 绝对安全"一说。 |
| 转发和重定向区别 | 转发一次请求、地址栏不变、共享 request;重定向两次请求、地址栏变、不共享。 |
| Session 存哪 | 默认存在服务器内存;SessionId 通过 Cookie 发给浏览器;集群要放 Redis。 |
| Servlet 是单例吗 | 是。一个类只有一个实例,多线程并发访问,所以别用成员变量存请求数据。 |
| Filter 和 Servlet 顺序 | 请求先过所有匹配的 Filter 链,最后才到 Servlet;响应回来时 Filter 再倒着走一遍。 |
| JSP 本质是什么 | 一个会被翻译成 Servlet 的文本模板。 |
中文乱码排查清单:从上到下逐段对
| 出问题的环节 | 检查点 |
|---|---|
| 浏览器 → 服务器(请求) | Filter 里 request.setCharacterEncoding("UTF-8");页面表单 <meta charset="UTF-8">。 |
| 服务器 → 浏览器(响应) | response.setContentType("text/html;charset=UTF-8")。 |
| 服务器 ↔ 数据库 | JDBC URL 带 characterEncoding=utf8;库、表、连接三处都是 utf8mb4。 |
| 文件本身 | IDE 里 .java / .jsp 文件编码存成 UTF-8,别是 GBK。 |
网上搜到的代码一半是 javax.servlet.*(Tomcat 9 及以前),一半是 jakarta.servlet.*(Tomcat 10 及以后)。包名和 Tomcat 大版本强绑定,混着用直接编译报错。本页按 Tomcat 10+ 写 jakarta.*;如果你装的是 Tomcat 9,把代码里的 jakarta 全替换成 javax 即可——别问为什么,这是 Oracle 捐给 Eclipse 时留的历史伤疤。
部署:把你的项目丢到 Tomcat 上
本地写完不算完,知道怎么上线才算闭环。传统方式:① Maven mvn package 打出一个 board.war;② 把 war 包扔进 Tomcat 的 webapps/ 目录;③ 启动 Tomcat,它自动解压部署;④ 浏览器访问 http://localhost:8080/board/login.jsp。现代开发用 IDEA 一键部署到内置 Tomcat,省去手动拷贝。
部署只是"从本地到服务器"这一小步。真正的线上部署还会涉及 Linux、防火墙、Nginx 反向代理、HTTPS 证书——那些是运维和 Docker 的话题,本页不展开。你只要知道:WAR 包 + Tomcat 是这门课的终点,不是职业终点。
1.(概念题)请求转发(forward)和重定向(sendRedirect),浏览器地址栏哪个会变?转发几次 HTTP 请求?
查看答案
答案:重定向地址栏会变,转发不变。转发是一次请求(服务器内部转交),重定向是两次(浏览器再发一次)。所以转发能共享 request 数据,重定向不能。
2.(安全题)为什么拼接 SQL "SELECT * FROM users WHERE name='" + name + "'" 危险?怎么改?
查看答案
答案:用户输入 ' OR '1'='1 会把 SQL 变成恒真条件,登录直接绕过——SQL 注入。改成 PreparedStatement,SQL 写成 ... WHERE name = ?,再 setString(1, name),参数永远被当数据处理。
3.(选择题)用户登录成功后,为了防止 Session 固定攻击,应该立刻做什么?A. 延长 Session 时间 B. 更换 SessionId C. 把密码写进 Session
查看答案
答案:B。登录成功后调 request.changeSessionId()(或销毁旧 Session 新建),让攻击者事先种好的 SessionId 失效。
4.(排错题)表单提交后,数据库里存的中文全是问号?可能哪几层编码没对齐?
查看答案
答案:三处之一没统一 UTF-8:① 请求没设 request.setCharacterEncoding("UTF-8");② JDBC URL 没带 characterEncoding=utf8;③ 数据库/表不是 utf8mb4。三处全对齐才不丢字。
5.(思考题)为什么生产环境没人直接用 Session 存登录状态,而用 JWT / Redis?
查看答案
答案:Session 存在单机内存里,集群部署时另一台机器不认识这个用户。把 Session 集中放 Redis,所有 Tomcat 共享;或者用无状态的 JWT(登录信息加密签名后存浏览器),服务器不存任何东西,横向扩展最省心。
① 这一页讲的是裸 Servlet/JSP 时代的 Web 底层。现实中没人手搓了,下一步直接学 Spring Boot——它把 Filter、Session、JDBC、连接池全包好了,你会发现"原来这些零件我都见过"。
② 把第 9 章留言板项目跑起来,再用 Spring Boot 重写一遍,对比代码量,你会瞬间理解"框架为什么存在"。
③ 想学数据库本身:MySQL 索引、事务隔离级别、慢查询优化,是后端面试第二高频(第一是并发)。