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。