Skip to content

JavaEE 商业场景训练营

JavaEE 现在很少作为“手写 Servlet 项目”出现,但它仍然是 Spring MVC、Spring Boot、Spring Security、JDBC、连接池和 Web 容器排查的底层地基。本页用商业系统里最常见的登录、鉴权、请求链路、数据库访问和连接池问题,把 JavaEE 知识串成可落地能力。

一句话主线:

JavaEE 训练的不是背规范,而是理解一次 HTTP 请求如何进入容器、如何被 Filter 统一处理、如何通过 Servlet/Spring MVC 执行业务、如何安全访问数据库并在生产排查中定位问题。

训练目标

学完本页,你要能做到:

  1. 画出浏览器请求到 Tomcat、Filter、Servlet、业务代码、JDBC、响应返回的完整链路。
  2. 写出登录过滤器 Demo,理解 Filter 为什么能做鉴权、日志、跨域和编码。
  3. 解释 Servlet 单例多线程为什么不能用成员变量保存请求数据。
  4. 解释 Cookie、Session、SessionId 的配合方式,以及多实例为什么会丢 Session。
  5. 写出 JDBC 事务 Demo,理解连接、自动提交、提交、回滚和资源释放。
  6. 排查 404、401、跨域、乱码、连接池耗尽、接口超时。
  7. 面试时能把 JavaEE 和 Spring Boot、Spring MVC、Spring Security 关系讲清楚。

商业场景一:后台系统登录过滤

后台管理系统常见要求:除登录接口外,其他接口必须登录才能访问。这个能力非常适合放在 Filter,因为 Filter 在请求进入 Servlet 或 Spring MVC Controller 之前执行。

mermaid
flowchart TD
    A["浏览器请求接口"] --> B["Tomcat 创建 Request/Response"]
    B --> C["AuthFilter"]
    C --> D{"是否登录或白名单"}
    D -- "是" --> E["放行到 Servlet/DispatcherServlet"]
    D -- "否" --> F["返回 401"]
    E --> G["业务 Controller"]
    G --> H["写 Response"]

Demo:

java
@WebFilter("/*")
public class AuthFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        String path = req.getRequestURI();
        if (path.endsWith("/login")) {
            chain.doFilter(request, response);
            return;
        }

        HttpSession session = req.getSession(false);
        if (session == null || session.getAttribute("userId") == null) {
            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            resp.setContentType("application/json;charset=UTF-8");
            resp.getWriter().write("{\"message\":\"未登录\"}");
            return;
        }

        chain.doFilter(request, response);
    }
}

为什么这样写:

写法原因
@WebFilter("/*")拦截所有请求,由代码判断白名单
getSession(false)没有 Session 时不创建新 Session
未登录直接 return不再继续进入业务代码
chain.doFilter明确放行到后续 Filter 或 Servlet

如果忘记调用 chain.doFilter,请求会停在当前 Filter,Controller 永远不会执行。如果未登录后没有 return,可能先写 401 又继续放行业务,产生安全问题。

商业场景二:Session 登录态和多实例问题

HTTP 本身无状态。用户第一次登录成功后,服务端创建 Session,并通过 Cookie 把 SessionId 交给浏览器。后续请求浏览器带上 Cookie,服务端根据 SessionId 找到登录态。

mermaid
flowchart TD
    A["登录请求"] --> B["服务端校验账号密码"]
    B --> C["创建 HttpSession"]
    C --> D["写入 userId"]
    D --> E["响应 Set-Cookie: JSESSIONID"]
    F["后续请求"] --> G["浏览器携带 JSESSIONID"]
    G --> H["服务端查找 Session"]
    H --> I{"Session 是否存在"}
    I -- "存在" --> J["认为已登录"]
    I -- "不存在" --> K["返回未登录"]

单机部署时 Session 保存在当前 JVM 内存里。多实例部署时会出现问题:

mermaid
flowchart TD
    A["用户登录"] --> B["实例 A 保存 Session"]
    C["下一次请求"] --> D["负载均衡转到实例 B"]
    D --> E["实例 B 找不到 Session"]
    E --> F["用户被判定未登录"]

解决方案:

方案原理适合场景风险
粘性会话同一用户固定打到同一实例小规模系统实例故障会丢会话
集中 SessionSession 存 Redis传统后台系统Redis 可用性和序列化
JWT/Token服务端不保存 Session前后端分离、微服务Token 失效和权限变更要设计
Spring Session封装集中 SessionSpring 项目仍依赖外部存储

商业场景三:JDBC 事务和资源释放

JDBC 是 Java 访问数据库的基础。即使使用 MyBatis、JPA,底层也离不开连接、SQL、事务和结果集。

业务:创建数据资产,并写入审计日志。两个操作必须同时成功或同时失败。

mermaid
flowchart TD
    A["获取数据库连接"] --> B["关闭自动提交"]
    B --> C["插入资产表"]
    C --> D["插入审计表"]
    D --> E{"是否全部成功"}
    E -- "是" --> F["commit"]
    E -- "否" --> G["rollback"]
    F --> H["关闭 Statement/Connection"]
    G --> H

Demo:

java
public void createAsset(DataSource dataSource, String code, String name) throws SQLException {
    String insertAsset = "insert into data_asset(asset_code, name) values(?, ?)";
    String insertAudit = "insert into asset_audit(asset_code, action) values(?, ?)";

    try (Connection conn = dataSource.getConnection()) {
        conn.setAutoCommit(false);

        try (PreparedStatement assetStmt = conn.prepareStatement(insertAsset);
             PreparedStatement auditStmt = conn.prepareStatement(insertAudit)) {

            assetStmt.setString(1, code);
            assetStmt.setString(2, name);
            assetStmt.executeUpdate();

            auditStmt.setString(1, code);
            auditStmt.setString(2, "CREATE");
            auditStmt.executeUpdate();

            conn.commit();
        } catch (Exception e) {
            conn.rollback();
            throw e;
        }
    }
}

关键点:

为什么
setAutoCommit(false)多条 SQL 才能组成一个事务
PreparedStatement防 SQL 注入,也利于数据库复用执行计划
commit明确提交事务
rollback任一步失败撤销已经执行的 SQL
try-with-resources自动关闭资源,把连接还给连接池

如果不关闭连接,连接不会回到连接池;请求量一上来,连接池会被耗尽,后续请求等待连接直到超时。

商业场景四:连接池耗尽排查

连接池耗尽常见表现:

  1. 接口大量超时。
  2. 应用日志出现等待连接超时。
  3. 数据库连接数接近上限。
  4. 线程堆栈卡在获取连接。

排查流程:

mermaid
flowchart TD
    A["接口开始超时"] --> B["看连接池 active/idle/waiting"]
    B --> C{"active 是否接近 max"}
    C -- "是" --> D["检查慢 SQL 和连接泄漏"]
    C -- "否" --> E["检查外部接口、锁、Tomcat 线程"]
    D --> F{"是否有连接未关闭"}
    F -- "是" --> G["修复 finally 或 try-with-resources"]
    F -- "否" --> H["优化慢 SQL、索引、事务范围"]

常见原因:

原因说明处理
连接未关闭代码异常路径没有释放连接try-with-resources
慢 SQLSQL 长时间占用连接优化 SQL 和索引
事务过长事务里调用外部接口或处理大文件缩短事务范围
连接池太小并发超过池容量根据服务实例数和数据库上限估算
连接池太大打爆数据库控制总连接数

连接池不是越大越好。总连接数 = 应用实例数 * 每个实例最大连接数。比如 10 个实例,每个最大 100,就是 1000 条数据库连接,可能直接压垮数据库。

商业场景五:请求链路排查

当用户说“接口不通”,不要只看 Controller。

mermaid
flowchart TD
    A["接口异常"] --> B{"状态码是什么"}
    B -- "404" --> C["检查路径、上下文、Servlet/Controller 映射"]
    B -- "401/403" --> D["检查 Filter、Session、Token、权限"]
    B -- "跨域" --> E["检查 CORS Filter 和响应头"]
    B -- "500" --> F["看异常堆栈和业务日志"]
    B -- "超时" --> G["拆 Tomcat 线程、DB 连接、慢 SQL、外部接口"]
    C --> H["按请求链路逐段定位"]
    D --> H
    E --> H
    F --> H
    G --> H

排查表:

现象重点位置
404URL、context path、Servlet mapping、Controller mapping
401登录 Filter、Session/Cookie、Token
403权限模型、角色、资源授权
跨域OPTIONS 预检、Access-Control-Allow-*
乱码请求编码、响应编码、数据库字符集
响应已提交代码先写响应又 forward/redirect
超时Tomcat 线程、连接池、慢 SQL、外部接口

和 Spring 体系的关系

JavaEE 基础Spring/Spring Boot 中的体现
ServletSpring MVC 的 DispatcherServlet
FilterSpring Security 过滤器链、CORS Filter、日志 Filter
ListenerServlet 容器生命周期、Spring 上下文初始化
Session/Cookie登录态、Spring Session、RememberMe
JDBCDataSource、JdbcTemplate、MyBatis、JPA 底层
Web 容器Spring Boot 内嵌 Tomcat/Jetty/Undertow

理解 JavaEE 后,再看 Spring MVC 请求流程、Spring Security 过滤器链、Spring Boot 内嵌 Tomcat,就不是黑盒了。

面试标准回答

JavaEE 请求完整链路是什么?

浏览器请求先到 Web 容器,容器接收连接并解析 HTTP,创建 Request 和 Response,然后按顺序执行 Filter 链。Filter 放行后进入 Servlet;Spring MVC 项目里 Servlet 通常是 DispatcherServlet,再分发到 Controller。业务访问数据库时通过连接池拿 Connection,执行 SQL,事务提交或回滚,最后写 Response 返回。

Filter 为什么能做登录校验?

Filter 位于 Servlet 之前,可以读取请求头、Cookie、Session,也可以决定是否继续调用 chain.doFilter。所以它适合做登录校验、日志、编码、跨域、限流等统一逻辑。如果不放行,请求不会进入业务代码。

Session 多实例为什么会丢?

默认 Session 存在单个应用实例内存中。用户登录请求如果打到实例 A,后续请求被负载均衡到实例 B,B 找不到 A 内存里的 Session,就会认为用户未登录。解决方式包括粘性会话、Redis 集中 Session、Spring Session 或无状态 Token。

连接池耗尽怎么排查?

先看连接池 active、idle、waiting 指标和等待连接超时日志;再查慢 SQL、事务过长、连接泄漏和实例总连接数。代码层面用 try-with-resources 确保连接释放,事务里不要做慢外部调用。

关联知识点

知识点继续学习
JavaEE 主线JavaEE 从零到生产级掌握
入门JavaEE 入门
ServletServlet
Filter/ListenerFilter 与 Listener
Session/CookieSession 与 Cookie
JDBCJDBC
面试JavaEE 面试题