JavaEE 环境与程序设计 · 交互课程
技术演进接力链(M1 → M9)
  1. 1WEB 基础
    HTTP / Browser / WebServer
    静态页面写死,人人看到一样
  2. 2Servlet
    手写 if-else 分发不可扩展,无生命周期/并发管理
  3. 3JSP:参数散字符串
    参数散成一堆 String,取值靠字段名硬记
  4. 3JSP:scriptlet 塞逻辑
    scriptlet 把逻辑塞进页面,代码与 HTML 糊在一起
  5. 3JavaBean:数据对象化
    对象全靠 new、层层硬依赖
  6. 4Spring IoC
    Web 路由仍手写 web.xml/if-else、手动取参绑定,展示/业务/数据访问没分开
  7. 5MVC · SpringMVC
    数据在库里,访问是重复样板 + 拼串有注入风险
  8. 6MySQL
    Java 连库要手写五步,连接与资源管理全手动
  9. 7JDBC
    SQL 与 Java 混写、结果集逐行手映射、没有动态 SQL
  10. 8MyBatis
    四份配置 + web.xml 装配繁琐,部署要外部 Tomcat
  11. 9SpringBoot
    链尾 · 不考核

进阶拓展(可选)

承接上一章的死结
主线(M1→M11)已完结。本章为可选进阶,串起 SSM 之后常见的五块技术:Spring Data / Thymeleaf / EL·JSTL / 反射·注解 / 泛型。

进阶拓展(可选):SSM 之后的技术版图

定位:本页是可选进阶拓展,不占核心课时、不并入期中/期末范围。学完 SSM 主线后,用一节课的时间把「SSM 之后」常见的五块技术串起来——Spring Data、模板引擎(Thymeleaf)、EL·JSTL、反射与注解、泛型——理解它们各自解决什么问题、和前面学的东西怎么衔接。(Spring Boot 已单列模块9,见该章。)能真实编译运行的纯 Java 片段用 CodePlayground,泛型擦除用 GenericEraseDemo 交互演示。

模块9 指针:Spring Boot(约定大于配置、starter 依赖、自动配置、内嵌 Tomcat + java -jar)已独立成**模块9「SpringBoot」**章,本章不再重复,只把它作为「SSM 之后的第一站」顺带一提。

一、Spring Data:把 Mapper 的样板再省一层

模块8 MyBatis 里,每个实体都要写「Mapper 接口 + XML 映射文件」。Spring Data 更激进——连接口都不用你写全:

对比一下三层持久化技术要写多少东西:

技术持久层要写什么
原生 JDBCConnection / Statement / ResultSet 全手写
MyBatis(模块8)Mapper 接口 + XML(SQL 自己写,显式可控)
Spring Data JPA只写 Repository 接口,SQL 由框架生成

取舍:MyBatis 的 SQL 显式、可控、调优友好;Spring Data 更「约定化」、上手快,但复杂查询仍需下沉到 JPQL / 原生 SQL。生产里两者常见混用。

二、模板引擎(Thymeleaf):JSP 的现代替代

模块3 学的 JSP 把 Java 代码和 HTML 混在一起,维护性差。现代 SpringMVC / Spring Boot 项目普遍用服务端模板引擎,Thymeleaf 最典型:

<!-- Thymeleaf:在 HTML 属性里用 th:* 前缀,未渲染时也是合法 HTML -->
<tr th:each="dept : ${depts}">
    <td th:text="${dept.deptName}"></td>
</tr>

三、EL 与 JSTL:JSP 里的表达式与标签库

如果还在维护 JSP(模块3),EL 表达式和 JSTL 标签库是绕不开的:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<c:forEach var="w" items="${requestScope.words}">
    <li><c:out value="${w.word}"/></li>
</c:forEach>

衔接:EL/JSTL 是「JSP 时代」的写法;Thymeleaf 是「框架时代」的等价能力。两者都在解决同一件事——把取值 / 循环 / 条件用声明式语法表达,而不是在页面里嵌 Java 代码。

四、反射与注解:IoC 的底层支撑

模块4 Spring IoC 能「扫描 @Component、按 @Autowired 注入」,靠的就是反射 + 注解这对组合拳:

下面用 CodePlayground 在线真实编译运行一段纯 Java 代码(可编辑你的版本,观察反射在运行时读取类与方法信息):

交互实验:浏览器里编译运行 Java(CodePlayground)

未连接

左边写 Java,点「运行」——源码经 WebSocket+STOMP 发给后端(端口 8081),真实javac编译 + java 运行,输出推回你的浏览器。先选一个预置程序试试:

Main.java(可编辑)6 行 · 0.2 KiB / 64 KiB
结果

← 点「运行」看编译/运行结果

💡 最小可运行程序:UTF-8 中文输出 + 表达式求值。

幕后流程:浏览器 →(WebSocket ws://<host>:8081/ws)→ 后端 STOMP /app/run → 沙箱编译运行 → 结果推回你的会话专属队列 /queue/result-<你的id>。每个浏览器标签页只收自己的结果,互不串台。

五、泛型:类型安全的参数化

模块8/10 里 List<String>、Map<String, Object> 大量出现。泛型让集合「声明时指定元素类型」,编译期就能查错;但 Java 的泛型是**擦除(erasure)**实现的——运行时类型信息被去掉。用下面的交互组件逐行看编译器如何擦除 List<String>:

交互实验:泛型声明 vs 擦除后字节码(GenericEraseDemo)

泛型只存在于编译期:javac 把 List<String> 擦成裸的 List,元素按 Object 处理;编译器在「取出」处自动插入强转。 点击任意一行,看编译器对它的处理:

源码(带泛型)
编译后字节码(擦除,教学等价写法)

← 点击任意一行,看编译器做了什么(去掉类型参数 / 插入强转 / 不变)

运行时验证(擦除后 JVM 里能观察到什么)
表达式结果说明
names.getClass().getName()"java.util.ArrayList"类名里没有 <String>——类型参数在运行时根本不存在。
names instanceof ArrayListtrue运行时只能判断具体类(ArrayList),无法判断「装的是什么类型」。
names.getClass() == nums.getClass()truenames 是 List<String>、nums 是 List<Integer>——擦除后两者在 JVM 里是同一个类。
为什么擦除是危险的:原始类型走查(第 1/4 步)
List<String> strings = new ArrayList<>();
strings.add("Tom");
编译通过

正常泛型用法:编译期类型安全,一切如常。

结论:泛型检查只发生在编译期——原始类型(raw type)绕过检查后,错误元素能混进集合,直到「取出」那行才以 ClassCastException 的形式爆炸。

要点:泛型只存在于编译期;List<String> 运行时就是一份裸 List,取出元素时编译器自动插入强转。这也是为什么不能 new T()、不能 instanceof List<String>。

讨论问题

  1. MyBatis 与 Spring Data JPA 各适合什么场景?为什么很多项目两者混用?
  2. Thymeleaf 的「自然模板」相比 JSP 有什么实际好处(预览 / 维护 / 安全)?
  3. 反射 + 注解如何共同支撑 Spring IoC 的「扫描并注入」?没有反射,@Autowired 还能工作吗?
  4. Java 泛型为什么是「擦除」而非「重载」?这带来哪些限制(不能 new T()、不能 instanceof 参数化类型)?

学习测验

学习小测验

Q1

Spring Data JPA 中,定义一个继承 JpaRepository 的接口后,常用 CRUD 方法

Q2

Thymeleaf 相比 JSP 的一个显著优点是

Q3

JSP 中 EL 表达式 ${param.name} 的作用是

Q4

Spring IoC 能扫描 @Component 并按 @Autowired 注入,底层依赖

Q5

Java 泛型 List<String> 在运行时是

链尾 / 收束
全课程到此收官。