Skip to content

JavaEE 从零到生产级掌握

JavaEE 不能只学成“会写 Servlet”。即使现在大多数项目都用 Spring Boot,底层也绕不开 Servlet 容器、Filter、Listener、Session/Cookie、JDBC、连接池和事务。Spring MVC 的入口是 DispatcherServlet,Spring Security 的核心是过滤器链,数据库访问最终也要落到连接、SQL、事务和资源释放。

一句话建立主线:

JavaEE 是 Java Web 应用的底层规范体系,负责让 Java 程序接收 HTTP 请求、维护会话、访问数据库,并在 Web 容器中运行。

学习目标

学完这一页,你要能做到:

  1. 解释浏览器请求如何进入 Tomcat,再进入 Servlet 或 Spring MVC。
  2. 解释 Servlet 生命周期、单例多线程模型和线程安全问题。
  3. 解释 Request、Response、ServletContext、HttpSession 的作用。
  4. 解释 Filter 为什么能做登录、鉴权、跨域、日志、编码和限流。
  5. 解释 Listener 能监听哪些生命周期,适合做什么,不适合做什么。
  6. 解释 Cookie 和 Session 如何配合维持登录态,多实例下为什么会丢 Session。
  7. 解释 JDBC 执行 SQL 的完整流程,为什么必须关闭资源。
  8. 解释连接池为什么必要,连接泄漏、连接耗尽怎么排查。
  9. 解释事务的提交、回滚、隔离级别和自动提交。
  10. 解释 JavaEE 和 Spring MVC、Spring Boot、Spring Security 的关系。

学习路线

mermaid
flowchart TD
    A["HTTP 基础"] --> B["Web 容器<br/>Tomcat/Jetty/Undertow"]
    B --> C["Servlet<br/>请求响应入口"]
    C --> D["Filter<br/>请求前后拦截"]
    D --> E["Session/Cookie<br/>登录状态"]
    E --> F["Listener<br/>生命周期监听"]
    F --> G["JDBC<br/>访问数据库"]
    G --> H["连接池和事务"]
    H --> I["Spring MVC/Spring Boot 关系"]
    I --> J["生产排查"]

这条路线的关键是:先理解一次请求从浏览器到 Java 代码的链路,再理解统一拦截、会话状态和数据库访问。

如果你希望把这些知识放进真实项目里理解,可以配合学习:JavaEE 商业场景训练营。它用登录过滤、Session 多实例、JDBC 事务、连接池耗尽和接口排查,把规范知识转成项目能力。

第一步:HTTP 请求如何进入 Java 应用

用户在浏览器访问:

text
GET http://example.com/assets?id=1001

服务端不是直接进入 Controller。真实链路是:

mermaid
flowchart TD
    A["浏览器发送 HTTP 请求"] --> B["操作系统接收 TCP 连接"]
    B --> C["Tomcat Connector 读取请求"]
    C --> D["解析 HTTP 请求行、请求头、请求体"]
    D --> E["创建 Request 和 Response"]
    E --> F["匹配 Web 应用和 Servlet"]
    F --> G["执行 Filter 链"]
    G --> H["调用 Servlet service 方法"]
    H --> I["业务代码写 Response"]
    I --> J["Tomcat 返回 HTTP 响应"]

Spring Boot 项目里也是这条链,只是 Servlet 是 Spring MVC 的 DispatcherServlet

为什么要理解这条链:

线上问题可能位置
请求 404Web 应用路径、Servlet 映射、Controller 路由
登录失效Cookie、Session、Filter、Spring Security
跨域失败CORS Filter、响应头
中文乱码请求编码、响应编码、数据库字符集
接口超时Tomcat 线程、业务线程、数据库连接、外部接口

第二步:Web 容器做什么

Tomcat、Jetty、Undertow 都属于 Web 容器或 Servlet 容器。它们负责:

能力说明
监听端口接收 TCP/HTTP 请求
解析协议把 HTTP 字节流解析成请求对象
管理 Servlet创建、初始化、调用、销毁
管理 Filter按顺序执行过滤器链
管理 Session保存会话状态和过期时间
线程模型使用线程处理请求
返回响应把 Response 写回客户端

简化模型:

mermaid
flowchart TD
    A["Connector<br/>连接和协议"] --> B["Container<br/>Servlet 管理"]
    B --> C["Filter Chain"]
    C --> D["Servlet"]
    D --> E["Response"]

Tomcat 不是业务框架,它是运行 Java Web 应用的容器。Spring Boot 内嵌 Tomcat,只是把原来外部部署 WAR 的方式变成了应用自己启动容器。

第三步:Servlet 生命周期

Servlet 是 Java 处理 HTTP 请求的基础规范。

生命周期:

mermaid
flowchart TD
    A["容器加载 Servlet"] --> B["创建 Servlet 实例"]
    B --> C["init 初始化一次"]
    C --> D["service 处理多次请求"]
    D --> E["按方法分发到 doGet/doPost"]
    E --> F["destroy 销毁一次"]

方法说明:

方法调用时机次数
init()Servlet 创建后一次
service()每次请求进入多次
doGet() / doPost()根据 HTTP 方法分发多次
destroy()应用卸载或容器关闭一次

最小 Demo:

java
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        response.setContentType("text/plain;charset=UTF-8");
        response.getWriter().write("hello servlet");
    }
}

Servlet 通常是单例多线程。容器不会为每个请求创建一个 Servlet 实例,而是多个线程共享一个实例。

mermaid
flowchart TD
    A["一个 Servlet 实例"] --> B["线程 1 处理用户 A"]
    A --> C["线程 2 处理用户 B"]
    A --> D["线程 3 处理用户 C"]

错误示例:

java
public class UserServlet extends HttpServlet {
    private String currentUser;

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
        currentUser = req.getParameter("user");
    }
}

currentUser 是成员变量,会被多个请求共享,可能出现用户串号。请求级数据应放局部变量、request 或 session。

第四步:Request 和 Response

HttpServletRequest 表示请求,常用来读取:

内容API
请求参数getParameter
请求头getHeader
CookiegetCookies
SessiongetSession
请求路径getRequestURI
请求体getInputStream / getReader

HttpServletResponse 表示响应,常用来设置:

内容API
状态码setStatus
响应头setHeader
CookieaddCookie
响应类型setContentType
响应体getWriter / getOutputStream

示例:

java
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
        throws IOException {
    String id = req.getParameter("id");
    resp.setContentType("application/json;charset=UTF-8");
    resp.getWriter().write("{\"id\":\"" + id + "\"}");
}

生产项目不要手动拼 JSON,Spring MVC 会用 Jackson 等序列化工具处理。但理解 Request/Response 有助于排查 Header、Cookie、编码和响应已提交问题。

第五步:forward 和 redirect

对比forwardredirect
谁发起服务端内部转发服务端通知浏览器重新请求
请求次数一次两次
地址栏不变改变
request 数据可共享不共享
适合内部资源转发登录后跳转、跨站跳转

forward:

java
request.getRequestDispatcher("/WEB-INF/page.jsp")
        .forward(request, response);

redirect:

java
response.sendRedirect("/login");

如果已经写出响应体,再 forward 可能报错,因为响应已经提交,状态码和响应头可能不能再改。

第六步:Filter 原理

Filter 是 Servlet 规范里的请求过滤器。它能在请求进入 Servlet 前、响应返回客户端前执行统一逻辑。

mermaid
flowchart TD
    A["请求进入"] --> B["Filter 1 前置"]
    B --> C["Filter 2 前置"]
    C --> D["Servlet/Controller"]
    D --> E["Filter 2 后置"]
    E --> F["Filter 1 后置"]
    F --> G["响应返回"]

最小 Demo:登录校验。

java
@WebFilter("/api/*")
public class LoginFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
                         FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;

        HttpSession session = req.getSession(false);
        if (session == null || session.getAttribute("userId") == null) {
            resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            resp.getWriter().write("not login");
            return;
        }

        chain.doFilter(request, response);
    }
}

chain.doFilter 是关键:

  • 调用它,请求继续往后走。
  • 不调用它,请求被拦截。
  • 调用前是前置逻辑。
  • 调用后是响应返回阶段逻辑。

Filter 常见用途:

用途为什么适合 Filter
登录鉴权请求进入业务前判断
CORS设置跨域响应头
编码统一设置 UTF-8
日志 traceId请求前生成,请求后记录耗时
限流在业务前拒绝过量请求
XSS/安全头统一安全处理

Filter 和 Spring MVC Interceptor 区别:

对比FilterInterceptor
来源Servlet 规范Spring MVC
位置Servlet 前后Controller 前后
能否拦静态资源可以通常依赖 MVC 映射
是否知道 Handler不知道知道 Controller 方法
典型用途编码、跨域、安全入口业务权限、Controller 日志

第七步:Listener 原理

Listener 用于监听 Web 应用、请求、Session 生命周期。

常见 Listener:

类型监听什么场景
ServletContextListener应用启动和关闭初始化资源、关闭资源
HttpSessionListenerSession 创建和销毁在线人数、会话清理
ServletRequestListener请求创建和销毁请求级监控
HttpSessionAttributeListenerSession 属性变化登录状态变化

示例:应用启动监听。

java
@WebListener
public class AppListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("web app started");
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("web app stopped");
    }
}

Listener 适合做生命周期相关工作,不适合写复杂业务逻辑。复杂业务放 Service,避免启动和销毁阶段出问题影响应用可用性。

HTTP 是无状态协议。服务端处理完一次请求后,不天然记得下次请求是谁。

Cookie 保存在浏览器,Session 保存在服务端。

典型登录流程:

mermaid
flowchart TD
    A["用户提交账号密码"] --> B["服务端校验成功"]
    B --> C["创建 Session 保存 userId"]
    C --> D["响应 Set-Cookie: JSESSIONID"]
    D --> E["浏览器保存 Cookie"]
    E --> F["后续请求携带 JSESSIONID"]
    F --> G["服务端根据 ID 找 Session"]

代码:

java
HttpSession session = request.getSession();
session.setAttribute("userId", user.getId());

读取:

java
HttpSession session = request.getSession(false);
if (session == null) {
    response.setStatus(401);
    return;
}
Long userId = (Long) session.getAttribute("userId");

多实例问题:

mermaid
flowchart TD
    A["浏览器带 JSESSIONID"] --> B["负载均衡"]
    B --> C["实例 1 有 Session"]
    B --> D["实例 2 没有 Session"]
    D --> E["用户被认为未登录"]

解决方案:

方案说明适用
粘性会话同一用户固定打到同实例简单但实例故障体验差
Session 复制实例间复制 Session集群小、状态不多
集中 SessionRedis/Spring Session常用
无状态 TokenJWT/自定义 Token前后端分离、网关鉴权

Cookie 安全属性:

属性作用
HttpOnlyJS 不能读取,降低 XSS 窃取风险
Secure只在 HTTPS 发送
SameSite降低 CSRF 风险
Max-Age过期时间
Path/Domain限定发送范围

第九步:JDBC 完整流程

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

完整流程:

mermaid
flowchart TD
    A["获取 Connection"] --> B["创建 PreparedStatement"]
    B --> C["绑定参数"]
    C --> D["执行 SQL"]
    D --> E["处理 ResultSet"]
    E --> F["提交或回滚事务"]
    F --> G["关闭 ResultSet/Statement/Connection"]

示例:

java
String sql = "select id, name from asset where id = ?";
try (Connection conn = dataSource.getConnection();
     PreparedStatement ps = conn.prepareStatement(sql)) {
    ps.setLong(1, 1001L);
    try (ResultSet rs = ps.executeQuery()) {
        while (rs.next()) {
            long id = rs.getLong("id");
            String name = rs.getString("name");
            System.out.println(id + ":" + name);
        }
    }
}

为什么用 PreparedStatement

  • 参数和 SQL 结构分离,降低 SQL 注入风险。
  • 数据库可复用执行计划。
  • 参数类型处理更可靠。

错误示例:

java
String sql = "select * from user where name = '" + name + "'";

如果 name 是恶意输入,就可能 SQL 注入。

第十步:连接池为什么必要

数据库连接不是普通对象,创建连接需要 TCP 握手、认证、初始化会话等成本。如果每次请求都新建连接,性能会很差。

连接池的作用:

mermaid
flowchart TD
    A["应用启动"] --> B["连接池创建一定数量连接"]
    C["业务请求"] --> D["从连接池借连接"]
    D --> E["执行 SQL"]
    E --> F["归还连接"]
    F --> G["连接继续复用"]

连接池能:

能力说明
复用连接减少频繁创建销毁
控制并发最大连接数保护数据库
等待队列连接不足时等待
泄漏检测连接长时间未归还报警
健康检查剔除失效连接

连接泄漏是什么:

text
业务借了连接,但异常路径没有 close,连接一直不归还。

后果:

  • 活跃连接越来越多。
  • 等待连接线程越来越多。
  • 接口变慢或超时。
  • 数据库连接数被打满。

排查:

现象排查方向
连接池 active 接近 maxSQL 慢、连接泄漏、并发过高
wait count 上升连接不够或归还太慢
数据库连接数满应用连接池配置过大或泄漏
只在某接口发生看该接口 SQL 和事务边界

第十一步:事务

JDBC 默认可能是自动提交模式。每条 SQL 执行后自动提交。

手动事务:

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

    updateOrder(conn);
    updateStock(conn);

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

事务解决的是一组操作要么都成功,要么都失败。

mermaid
flowchart TD
    A["关闭自动提交"] --> B["执行 SQL 1"]
    B --> C["执行 SQL 2"]
    C --> D{"是否全部成功"}
    D -- "是" --> E["commit"]
    D -- "否" --> F["rollback"]
    E --> G["释放连接"]
    F --> G

事务和连接强相关:同一个事务里的 SQL 必须使用同一个数据库连接。Spring 事务底层也是把连接绑定到当前线程,再在方法结束时提交或回滚。

常见坑:

后果
忘记 rollback异常后事务状态不清
忘记 close连接泄漏
自动提交没关多条 SQL 无法整体回滚
事务太大锁时间长,连接占用久
catch 异常吞掉上层以为成功

第十二步:JavaEE 和 Spring 生态的关系

JavaEE 能力Spring 中的体现
ServletDispatcherServlet
FilterSpring Security Filter Chain、CORS Filter
ListenerWeb 初始化、Spring 上下文监听
SessionSpring Session、登录态
JDBCJdbcTemplate、MyBatis、JPA 底层
事务Spring PlatformTransactionManager
Web 容器Spring Boot 内嵌 Tomcat

Spring Boot 启动 Web 应用时,会启动内嵌 Tomcat,注册 DispatcherServlet、Filter、Listener、数据源等组件。它降低配置成本,但不是绕开 JavaEE。

商业项目场景

登录鉴权

text
浏览器 Cookie -> Filter/Security 读取 -> 校验 Session/Token -> 放行 Controller

关键点:

  • Cookie 设置 HttpOnly、Secure、SameSite。
  • 多实例用 Redis Session 或 Token。
  • 鉴权失败在 Filter 阶段拦截,不进入业务。

医疗数据采集接口

text
医院系统请求 -> Tomcat -> Filter 记录 traceId -> Controller -> JDBC/ORM 写库

关键点:

  • 请求日志记录 traceId、医院编码、批次号。
  • 统一设置超时和连接池。
  • 失败要记录批次状态,便于补偿。

后台管理系统

text
前端请求 -> CORS Filter -> 登录 Filter -> Controller -> Service -> DB

关键点:

  • CORS 不要放得过宽。
  • Session 过期返回统一错误码。
  • JDBC/连接池监控要接入告警。

生产排查总流程

请求没有进入 Controller

mermaid
flowchart TD
    A["请求没有进入 Controller"] --> B["确认 URL 和端口"]
    B --> C["确认 Tomcat 是否收到请求"]
    C --> D["确认 ContextPath"]
    D --> E["确认 Filter 是否拦截"]
    E --> F["确认 DispatcherServlet/Servlet 映射"]
    F --> G["确认 Controller 路由"]

登录态丢失

mermaid
flowchart TD
    A["登录态丢失"] --> B["浏览器是否携带 Cookie"]
    B --> C["Cookie Domain/Path/SameSite 是否正确"]
    C --> D["服务端 Session 是否存在"]
    D --> E["是否多实例本地 Session 不共享"]
    E --> F["是否 Session 过期或被销毁"]

数据库连接耗尽

mermaid
flowchart TD
    A["连接池耗尽"] --> B["看 active/max/wait 指标"]
    B --> C["是否慢 SQL"]
    C --> D["是否事务太长"]
    D --> E["是否连接泄漏"]
    E --> F["是否并发超过数据库容量"]
    F --> G["优化 SQL、事务和连接池配置"]

常见坑

后果正确做法
Servlet 成员变量保存用户数据用户串号用局部变量/request/session
Filter 忘记 chain.doFilter请求被意外拦截明确放行条件
Filter 顺序错误鉴权、跨域、日志异常明确 order
Cookie SameSite 配错跨站登录失败按前后端部署方式配置
本地 Session 多实例部署登录状态随机丢失Redis Session 或 Token
JDBC 不关闭资源连接泄漏try-with-resources
SQL 字符串拼接参数SQL 注入PreparedStatement
事务范围太大锁时间长,连接占用久缩小事务边界

面试标准回答

JavaEE 请求链路怎么回答

浏览器发起 HTTP 请求后,Tomcat 等 Web 容器接收 TCP 连接并解析 HTTP,请求会先进入 Filter 链,Filter 可以做编码、跨域、日志、鉴权等统一处理;放行后进入 Servlet。Spring MVC 中这个 Servlet 就是 DispatcherServlet,它再查找 Controller 并调用业务方法。业务中如果访问数据库,会通过 JDBC、连接池和事务完成 SQL 执行,最后写入 Response 返回浏览器。

Servlet 为什么不是线程安全的

Servlet 通常是单例多线程模型,一个 Servlet 实例会被多个请求线程同时调用。局部变量在线程栈里是安全的,但成员变量被所有请求共享,如果保存用户请求数据就可能串号。所以 Servlet 成员变量只能放无状态、线程安全或只读对象。

Filter 和 Interceptor 区别

Filter 是 Servlet 规范的一部分,作用在 Servlet 前后,位置更靠前,适合编码、跨域、登录入口、日志、限流等通用处理。Interceptor 是 Spring MVC 的组件,作用在 Controller 前后,能拿到 Handler 信息,适合更贴近业务的权限、日志和参数检查。

为什么要用连接池

数据库连接创建成本高,而且数据库能承受的连接数有限。连接池通过复用连接减少创建销毁成本,通过最大连接数限制并发保护数据库,并提供等待队列、健康检查和泄漏检测。线上连接池耗尽时,要看慢 SQL、长事务、连接泄漏和并发是否超过容量。

关联知识点

本章小结

JavaEE 是现代 Java Web 的地基。你可以不在项目里手写大量 Servlet 和 JDBC,但必须理解请求如何进入容器、Filter 如何拦截、Session/Cookie 如何维持登录、JDBC/连接池/事务如何访问数据库。这样排查登录失效、请求没进 Controller、跨域失败、连接池耗尽、事务异常时,才不会只盯着 Controller 和 Service。