简介:郝玉龙编著的《J2EE编程技术源代码》是一份聚焦Java企业级开发的系统源码合集,面向初学J2EE或希望深入理解Servlet、JSP、EJB、JMS、JTA等核心技术的开发者。内容覆盖企业应用常见模块,源码层次清晰,便于对照学习分布式多层架构的设计与实现。整包为RAR压缩格式,共429个文件、12.3MB,包含Java源文件91个、JSP页面83个、XML配置97个、编译后Class文件92个,另含properties属性配置、TLD标签库描述等辅助文件,可支撑动态页面、业务组件、消息服务与事务管理等场景的阅读与实践。目前已有186人学习下载,适合作为课程配套资料或项目开发参考。通过这套源码,读者可直观看到Servlet与JSP如何协作生成动态网页,EJB如何封装可复用业务逻辑,JMS与JTA如何支撑异步通信和分布式事务;结合不同模块的实例,有助于快速建立企业级应用的完整认知,并将相关技术整合到实际项目中。
1. 拿到J2EE编程技术源代码包:先想清楚你想从它身上得到什么
如果你买过一本 J2EE 教材,或者从课程配套资源里下载过一套 J2EE 编程技术源代码,大概率经历过同一个场景:源码导入 Eclipse 后满屏红叉,Tomcat 一启动就报 ClassNotFoundException,折腾一晚上还没看到登录页面长什么样。这套经典教材配套源代码,涵盖的正是 JSP、Servlet、JavaBean 和 EJB 这几个 J2EE 核心组件,结构上属于典型的早期分层教学项目。它解决的是两件事:第一,让你直观看到 J2EE 项目在容器里怎么组织代码;第二,让你在本地把整个请求链路跑通,而不是停留在“看书以为自己会了”的阶段。适合三类人:刚开始学 J2EE 的学生、刚接手老旧 Java Web 项目的开发者、准备把老代码迁移到 Spring Boot 但又怕漏业务的人。读完这篇,你不仅能让它跑起来,还能知道每一层代码为什么那样写、参数怎么调、哪些坑值得绕开。
2. 把源码包在本地跑起来:JDK 版本、中间件选择与最小启动命令
老 J2EE 项目和今天的主流 Java Web 工程有个明显差异:它依赖 Java EE 规范里 Servlet、JSP、EJB 这些组件,而规范的版本与容器的版本是一一对应的。很多人拿到代码后直接装最新版 JDK,再去官网下最新的 Tomcat,结果启动日志里刷出一堆和 javax 有关的报错。这不是源码坏了,是版本对不上。所以把源码跑起来之前,先把两个版本定死:JDK 版本和容器版本。
2.1 先确认 JDK 与容器版本:J2EE 1.4 不是随便装个 Tomcat 10 就能跑
这套源码对应的技术栈基本处在 J2EE 1.4 到 Java EE 5 之间,典型特征是 javax.servlet、javax.ejb 这些包名,以及 web.xml 里带http://java.sun.com/xml/ns/j2ee的命名空间。这类项目在 Tomcat 5.5 到 Tomcat 8.5 上都能正常部署,但到了 Tomcat 10,Servlet API 的包名从 javax 改成了 jakarta,老代码里的 import 语句全部失效,除非做包名替换,否则没法直接运行。
我一般会先看两个地方判断项目属于哪个版本:一是web.xml头部声明里的 schema 地址和版本号,二是lib目录下的 servlet-api.jar 是哪一代。常见的对应关系可以参考下面这张表:
| J2EE / Java EE 版本 | Servlet API | 适配的 Tomcat 版本 | JDK 建议 |
|---|---|---|---|
| J2EE 1.3 / 1.4 | Servlet 2.3 / 2.4 | Tomcat 5.5 / 6.0 | JDK 1.4 / 5 |
| Java EE 5 | Servlet 2.5 | Tomcat 6.0 / 7.0 | JDK 5 / 6 |
| Java EE 6 | Servlet 3.0 / 3.1 | Tomcat 7.0 / 8.5 | JDK 6 / 7 / 8 |
实际操作中,最省事的组合是 JDK 8 配 Tomcat 8.5。JDK 8 既能编译老代码的语法,又不会像更新版本那样把 javax.xml.bind 这类包直接移除;Tomcat 8.5 向下兼容 Servlet 2.4 的 web.xml,能省掉大量改命名空间的时间。先跑一个命令确认当前 JDK 情况:
java -version javac -version如果输出里版本号高于 8,建议先找一个 JDK 8 装好,再设置JAVA_HOME:
export JAVA_HOME=/path/to/jdk1.8.0_202 export PATH=$JAVA_HOME/bin:$PATH参数说明:JAVA_HOME指向 JDK 安装目录,而不是 JRE 目录;Tomcat 启动脚本依赖这个变量来定位编译器和运行时。PATH里把 JDK 的 bin 放在前面,避免系统里其他版本的 java 抢先被调用。这一步做完,用java -version再确认一次输出里出现的是 1.8,再继续往下走。
2.2 用 Tomcat 8.5 把 WAR 包跑起来:最小启动命令
源码包通常有两种形态:一种是直接给你整个 Eclipse 工程目录,目录里带.project和.classpath;另一种是打包好的 WAR 文件,直接丢进 Tomcat 的 webapps 就能发布。如果是前者,常见做法是在 Eclipse 里通过 Export 打成一个 WAR,或者用命令手工编译后再复制目录。我一般图省事,直接在命令行里用 jar 命令打包:
cd /path/to/project jar -cvf myapp.war .这条命令会把当前目录下的所有内容打进myapp.war,c表示创建新包,v打印打包过程,f指定输出文件名。打包完成后,把 war 复制到 Tomcat 的 webapps 目录:
cp myapp.war /path/to/tomcat8.5/webapps/ cd /path/to/tomcat8.5/bin ./startup.shTomcat 启动后会自动解压 war 文件,生成同名的目录。这时用 curl 验证首页能不能访问:
curl -I http://localhost:8080/myapp/index.jsp命令行参数说明:-I表示只请求响应头,如果返回HTTP/1.1 200 OK,说明项目已经正常发布。如果返回 404,先看 context root。默认情况下 context root 就是 war 包名,也就是/myapp,访问路径拼错是最常见的启动成功但页面打不开的原因。
这里有个容易踩的细节:老项目的文件编码很可能是 GBK 或 GB2312,打包前最好用file命令看清编码:
file src/com/example/LoginServlet.java如果显示为 ISO-8859 或 GBK,而你的 Tomcat 默认使用 UTF-8 读取,JSP 页面会出现乱码。建议先把源码文件统一转成 UTF-8 再打包,这一步会在后面章节详细展开。
2.3 启动失败的三个高频信号与三分钟定位法
Tomcat 启动失败时,不要只看控制台最后几行,logs/catalina.out才是完整日志。常见的失败信号有三个,对应三种完全不同的排查方向,我分别说一下排查路径。
第一个是UnsupportedClassVersionError,报错内容类似unsupported class file version 52.0。这说明 class 文件是用高版本 JDK 编译的,而 Tomcat 运行的 JRE 版本更老。处理方式要么把 Tomcat 的启动 JRE 切到高版本 JDK,要么把项目编译级别降到较低版本。Eclipse 里右键项目选择 Properties,在 Java Compiler 里把 Compiler compliance level 调低,重新编译再导出。
第二个是端口被占用,日志里能看到Port 8080 required by Tomcat v8.5 Server is already in use。先查谁占用了端口:
lsof -i :8080找到占用进程的 PID,确认是残留的 Tomcat 实例还是其他服务,然后结束对应进程,或者把 Tomcat 的 server.xml 里的 Connector port 改成 8081。改端口时注意,代码里如果有写死 8080 的跳转地址,也要一起改。
第三个是NoClassDefFoundError,通常集中出现javax.servlet、org.apache.commons.logging这类包。这类错误表示项目编译时能引用到的 jar,运行时不在 WEB-INF/lib 下。老项目经常把依赖 jar 放在外部 lib 目录,发布时没有一起打进去;检查方式是解压 war 看WEB-INF/lib下有没有对应的 jar,没有就把依赖补进去重新打包。
jar -tf myapp.war | grep servlet-api-t列出 war 包内容,f指定文件,grep过滤关键字。如果 servlet-api.jar 在列表里,通常不会缺这个类;如果不在,说明打的是“瘦包”,运行时必须依赖 Tomcat 自带的类库。看到这里你应该能理解,启动问题绝大多数是版本和路径两件事,先把这两点对齐,代码基本不用改就能跑起来。
3. 看懂分层架构:一次登录请求在 Bean、Servlet、JSP 之间怎样流动
把源码跑起来只是第一步,接下来要回答“这套代码到底在写什么”。J2EE 教学源码的核心价值,在于它用最简单的样例展示了一个分层模型:JSP 负责展示页面,Servlet 负责接收请求和控制流程,JavaBean 负责业务数据和逻辑。如果只看单个文件会觉得代码很简单,但把整个请求串起来,才能理解为什么当年要这么设计。
3.1 一次登录请求的完整流转:从 login.jsp 到数据库
假设这套源码里有一个用户登录功能,页面流程是这样的:用户在 login.jsp 输入用户名和密码,点击提交按钮,表单数据交给 LoginServlet,LoginServlet 调用 UserBean 里的方法去和数据库里的用户表比对,最后把结果返回给 welcome.jsp 或错误提示页。
对应的完整调用路径可以分成六步:
- 浏览器发起 POST 请求,地址是
http://localhost:8080/myapp/login.do - Tomcat 根据 web.xml 中的映射配置,把请求转给 LoginServlet 的 doPost 方法
- LoginServlet 调用
request.getParameter("username")拿到表单里的用户名 - LoginServlet 创建 UserBean 对象,调用
user.checkLogin(username, password) - UserBean 内部通过 JDBC 连接数据库,执行 SELECT 语句,返回 boolean 结果
- LoginServlet 根据结果选择 forward 到 welcome.jsp,或者重定向回 login.jsp
这个流程看起来直接,但有个关键点:JSP 页面里不应该出现数据库操作代码,Servlet 里也不应该出现 HTML 标签。每一层只做自己职责范围内的事,这就是 J2EE 分层最核心的约束。你可能觉得这没什么,但当你看到很多“简化版”代码把 SQL 写在 JSP 里时,就知道这种约束对可维护性有多重要。
3.2 UserBean 与 LoginServlet 的可复用片段:参数与返回值怎么约定
典型的 JavaBean 写法是私有属性加 getter/setter,再配一个或多个业务方法。这里以一个 UserBean 的登录校验方法为例,代码结构是 J2EE 时代最常见的样式:
public class UserBean { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean checkLogin(String user, String pwd) { // 这里通常调用 DBUtil 获取连接,执行 SQL 查询 // 实际项目中不要在这里写死 SQL,而应从配置读取 String sql = "SELECT COUNT(*) FROM user WHERE username = ? AND password = ?"; // 使用 PreparedStatement 防止 SQL 注入 return queryCount(sql, user, pwd) > 0; } }逻辑说明:这个 Bean 的checkLogin方法接收两个字符串参数,返回一个 boolean 值。它没有直接暴露数据库连接细节,而是通过私有方法queryCount去执行查询。这样做的好处是,Servlet 层只需要关心“登录成功了吗”,不需要知道数据库表结构。参数设计上,用户名和密码作为方法参数传入,避免把 request 对象传进 Bean,保持业务逻辑与 Web 容器解耦。
配套的 LoginServlet 常见写法如下:
public class LoginServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); UserBean user = new UserBean(); boolean ok = user.checkLogin(username, password); if (ok) { request.getSession().setAttribute("loginUser", username); request.getRequestDispatcher("/welcome.jsp").forward(request, response); } else { response.sendRedirect("login.jsp?error=1"); } } }参数说明:setCharacterEncoding("UTF-8")要放在getParameter之前,否则 POST 请求里的中文参数会乱码。getRequestDispatcher(...).forward(request, response)是服务器内部跳转,浏览器地址栏不变,适合带 request 属性到页面展示;sendRedirect是浏览器二次请求,地址栏会变成login.jsp?error=1,适合登录失败后的场景。两者最大的区别在于:forward 可以传递 request 作用域数据,redirect 则拿不到。这个知识点是 J2EE 面试里出现频率很高的点,也是调试时最容易困惑的地方。
3.3 JSP 与 Servlet 的分工:为什么页面里尽量不写业务代码
老教材为了讲解方便,经常在 JSP 里写 Java 片段,也就是<% %>。但一套结构良好的源码,页面里应该只有显示逻辑,业务判断都在 Servlet 或 Bean 里完成。一个值得参考的 JSP 片段是这样的:
<%@ page contentType="text/html; charset=UTF-8" language="java" %> <jsp:useBean id="userBean" class="com.example.UserBean" scope="page" /> <jsp:setProperty name="userBean" property="username" param="username" /> <html> <body> <h3>欢迎您:${userBean.username}</h3> <p>登录成功,您可以通过下方链接进入主页。</p> <a href="/myapp/index.do">进入主页</a> </body> </html>逻辑说明:jsp:useBean负责创建或获取指定作用域里的 Bean 实例,scope="page"表示在当前页面内有效;jsp:setProperty把请求参数自动赋值给 Bean 的同名属性,param="username"表示从 request 参数里取值。后面的${userBean.username}是 EL 表达式,等价于调用userBean.getUsername()。
这种写法的好处是页面里不再有 Java 代码,后端改逻辑时不用动 JSP。不过它也带来一个小坑:jsp:setProperty自动匹配同名参数,如果表单字段名和 JavaBean 属性名不一致,会赋不进去。所以约定参数名和属性名保持一致,是一个值得养成的习惯。你也看到了,J2EE 分层思想本身没什么高深的,真正高深的是在写代码时始终守住边界:Servlet 不拼 HTML,JSP 不写 SQL,JavaBean 不依赖 HttpServletRequest。守住这三点,老代码即便结构陈旧,也仍然能被安全地拆解和复用。
4. 三个必调的配置文件:web.xml、数据源与 JSP 编码
J2EE 项目和普通 Java 工程最大的区别之一,就是大量行为靠配置文件驱动。改动一个 XML 标签,可能比改十行 Java 代码影响面还大。而在这套源码里,最需要关注的配置文件就三个:web.xml决定请求怎么路由,数据源配置决定数据库能不能连上,JSP 编码决定页面显示的是中文还是乱码。把这三个文件吃透,整个项目的可维护性就掌握了大半。
4.1 web.xml:Servlet 映射、欢迎页与版本声明都要改
web.xml是整个 Web 应用的入口配置。一个典型的 J2EE 项目版本,头部声明里通常带http://java.sun.com/xml/ns/j2ee,中间包含若干<servlet>和<servlet-mapping>标签。下面是一段可以直接对照修改的示例:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <servlet> <servlet-name>login</servlet-name> <servlet-class>com.example.servlet.LoginServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>login</servlet-name> <url-pattern>/login.do</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>login.jsp</welcome-file> </welcome-file-list> </web-app>参数说明:load-on-startup的值表示容器启动时加载 Servlet 的顺序,数字越小优先级越高,写 1 表示应用一启动就实例化这个 Servlet,适合需要提前初始化资源的场景。如果不需要,可以删掉这一行,容器会在第一次请求时再创建实例。url-pattern是请求匹配规则,/login.do表示只有访问这个路径才会进入 LoginServlet。老代码里常见的*.do后缀不是规范强制要求的,只是一种约定,匹配所有以.do结尾的请求。welcome-file-list决定访问根路径时默认打开哪个页面,这个配置和访问路径组合起来,经常是 404 问题的根源。
一个值得注意的点:web-app version="2.4"配的是老式命名空间,如果用 Tomcat 8.5 部署,这个配置完全兼容。但如果新写项目,建议直接用 Jakarta EE 的新命名空间和版本号,不要再抄老格式。学习老代码时看懂即可,动手的时候还是按新规范来。
4.2 数据源配置:把连接串从代码里抽出来,改密码只动一处
老项目的数据库连接最常见的写法是在代码里直接写Class.forName("com.mysql.jdbc.Driver"),然后DriverManager.getConnection(url, user, password)。这段代码单独看没有问题,但密码散落在多个类里,换一次数据库密码要改好几个 Java 文件,改漏一个就会导致某个功能凌晨报错。更好的做法是通过 Tomcat 的 JNDI 数据源来管理连接,应用只需从容器里取一个 DataSource,改配置只动一个文件。
在 Tomcat 8.5 里,可以在META-INF/context.xml中这样配置:
<?xml version="1.0" encoding="UTF-8"?> <Context> <Resource name="jdbc/userdb" auth="Container" type="javax.sql.DataSource" maxTotal="20" maxIdle="8" username="root" password="yourpassword" driverClassName="com.mysql.cj.jdbc.Driver" url="jdbc:mysql://localhost:3306/userdb?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8"/> </Context>参数说明:name是 JNDI 名称,代码里通过lookup("java:comp/env/jdbc/userdb")获取。maxTotal是最大连接数,maxIdle是空闲连接数,这两个值决定数据库连接池的伸缩范围,一般开发环境分别设 20 和 8 就够。driverClassName在 MySQL 8 以上的驱动里要写成com.mysql.cj.jdbc.Driver,老代码里的com.mysql.jdbc.Driver在新版驱动里已经被标记为过时。url里的useSSL=false针对本地开发环境,避免 MySQL 8 默认要求 SSL 握手导致的链接警告;serverTimezone=Asia/Shanghai解决时区不一致导致的时间字段偏差;characterEncoding=UTF-8是数据库连接层面的编码兜底。
配套的代码侧获取方式是这样:
import javax.naming.InitialContext; import javax.sql.DataSource; public class DBUtil { public static Connection getConnection() throws Exception { InitialContext ctx = new InitialContext(); DataSource ds = (DataSource) ctx.lookup("java:comp/env/jdbc/userdb"); return ds.getConnection(); } }逻辑说明:这段代码不需要关心数据库地址和密码,全部信息都在 context.xml 里。以后换数据库环境,只改 XML,不动 Java。你可能会问:过渡到 Spring Boot 后不是有更简单的配置方式吗?确实,Spring Boot 用application.properties里的spring.datasource.*就够了,但理解 JNDI 数据源能帮你读懂老代码为什么那样设计,以及迁移时如何识别连接参数。这也是这套源码里少数“学了不过时”的知识点。
4.3 中文乱码三件套:JSP、Servlet、连接串缺一不可
老项目里中文乱码几乎和“Hello World”一样常见,而乱码的原因通常不是单点的,而是链接路上三个环节编码不一致。常见的现状是:页面能打开,但表单提交的中文到数据库后变成问号,或者从数据库读出来再显示就是乱码。排查时按下面三个位置逐一检查。
第一处是 JSP 页面头的编码声明:
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>contentType里的charset告诉浏览器以 UTF-8 渲染,pageEncoding告诉容器这个 JSP 文件本身用什么编码读取。两个都要写,只写一个会出现“文件是 UTF-8,但容器按 ISO-8859-1 解析”的错位。
第二处是 Servlet 里接 request 参数前设置编码:
request.setCharacterEncoding("UTF-8");注意:这行必须放在第一个getParameter()之前,放在之后就没有效果了。Tomcat 8 之后,GET 请求的默认 URI 编码仍然是 ISO-8859-1,如果 GET 参数里带中文,需要在server.xml的 Connector 上加上URIEncoding="UTF-8"。POST 请求则主要靠setCharacterEncoding来控制。
第三处是数据库连接串和表结构。连接串里的characterEncoding=UTF-8刚才已经提过;同时要确认数据库表字段的字符集是 utf8mb4,而不是 latin1。排查时可以执行:
SHOW CREATE TABLE user;重点看DEFAULT CHARSET那一行。如果显示 latin1,即使 Java 端全改成 UTF-8,存储层还是会丢字符。解决办法是改表字符集:
ALTER TABLE user CONVERT TO CHARACTER SET utf8mb4;执行完之后,重新跑一次写入和读取,数据里的中文就能正常存取了。很多项目改了十几处代码还是乱码,最后发现是表结构的问题。我的习惯是,遇到乱码先看数据库字符集,再回头查代码,顺序不对很容易白折腾。
5. 避坑手册:这套 J2EE 旧源码最常见的五个翻车现场
老代码最折磨人的地方不是业务逻辑有多难,而是那些看似无关紧要的环境差异。下面五条全部来自实际交付过程中反复出现的现象,每一条我都按“现象、原因、解决”的顺序拆开写。
5.1 现象:导入 Eclipse 后项目上全是红叉
原因:缺servlet-api.jar、jsp-api.jar这类容器专属依赖。源码包里通常不会把这些 jar 塞进WEB-INF/lib,因为它期望运行时由 Tomcat 提供。Eclipse 里没有关联 Tomcat 运行环境,编译器就找不到javax.servlet.http.HttpServlet等类,于是每个 import 都报错。
解决:在 Eclipse 里给项目设置 Target Runtime。右键项目 Properties,选择 Targeted Runtimes,勾选已经配置好的 Tomcat 8.5,点 Apply。完成后让 Eclipse 重新校验项目,红叉应该基本消失。如果还缺别的第三方包,检查项目的lib目录是否在 Build Path 里,不在就通过 Configure Build Path 手动添加。这一步解决不了的话,还有一个退路:确认WEB-INF/lib下需要的 jar 是否完整,比如 MySQL 驱动、日志包。
5.2 现象:EJB 相关的类找不到,或者启动时报Cannot find Home interface
原因:老教材的源码里可能包含 EJB 模块,但 EJB 容器在 Tomcat 里是残缺的,Tomcat 本身不提供完整 EJB 容器。如果你的源码包里有 EJB 代码,又在普通 Tomcat 里部署,就会遇到 Home 接口、JNDI 绑定相关的错误。
解决:判断这套源码里的 EJB 是重点还是附带。如果整本教材主要讲 JSP/Servlet,EJB 部分通常只做简介,那么在本地验证时直接把 EJB 相关的类从构建路径里移除,或者注释掉对应调用。如果确实需要跑通 EJB,常见做法是换用 JBoss 或 WildFly 这类完整 Java EE 容器,但这会显著增加环境搭建成本,不太适合只想快速学习的学生。我的建议是:初学者先跳 EJB,把 Servlet 与 JSP 链路跑通,EJB 概念等用到时再回头看。
5.3 现象:Tomcat 能正常启动,但访问页面一直 404
原因:绝大多数情况是 context root 和你访问的 URL 对不上。比如 war 包名为 myapp,但访问时写成了http://localhost:8080/myapp/login.jsp中某个路径写错,或者在 Eclipse 里部署时的 context root 被改成了别的名字。另一个隐藏原因是welcome-file-list里写的文件本身不存在。
解决:先确认应用名,再确认请求路径。直接访问http://localhost:8080/看 Tomcat 首页,再访问http://localhost:8080/myapp/,如果返回 Tomcat 的 404 页面并提示“The requested resource is not available”,说明 context root 不对。打开 Tomcat 的webapps目录,看解压出的文件夹名字,访问路径完全以它为准。在老项目里,还要检查server.xml的<Context>配置有没有单独设置docBase和path,如果配置了<Context path="/myapp" docBase="...">,那么路径由path属性决定,而不是文件夹名。把这两处对齐,404 基本能解决。
5.4 现象:表单提交的中文到 Servlet 里打印出来是乱码
原因:三层编码不一致。JSP 页面用 GBK 保存,pageEncoding却写了 UTF-8;或者 Servlet 没有在取参数前设置request.setCharacterEncoding("UTF-8");又或者浏览器提交的请求头里没带 charset,容器按默认编码解析。
解决:按前面“中文乱码三件套”的思路逐一排查。先用file命令确认 JSP 文件的实际编码,再确认页面指令里的pageEncoding和它一致。确认 Servlet 里setCharacterEncoding放在getParameter之前。最后在 web.xml 里加一个全局过滤器,统一处理编码:
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>这里用的是 Spring 提供的过滤器,如果项目里没引 Spring,可以自己写一个实现Filter接口的类,逻辑就是强制 request 和 response 的编码为 UTF-8,再链式调用chain.doFilter。三步处理完再测一次,乱码应该消失。
5.5 现象:能编译能部署,但页面一访问数据库就报Communications link failure或ClassNotFoundException: com.mysql.jdbc.Driver
原因:老代码里写死了老驱动类和老连接串。com.mysql.jdbc.Driver在 MySQL 5.x 时代是标准写法,但 MySQL 8+ 的官方驱动把它改名成com.mysql.cj.jdbc.Driver,并且要求连接串里带上时区参数。两个问题叠一起,就是数据库完全没有连上。
解决:把代码和 XML 里的驱动类统一改成新驱动,连接串加上useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true。具体可以参考前面 context.xml 里的写法。同时确认WEB-INF/lib下放的是新版本的 mysql-connector-j jar,而不是被拷贝了多次的旧包。如果项目里同时存在多个版本的驱动 jar,建议只保留最新版,避免类冲突导致驱动加载到旧类。改完之后重启 Tomcat 再测试读取,注意连接池里可能有缓存连接,改完配置最好重启容器而不是只热发布。
6. 进阶:把 J2EE 老代码迁到 Spring Boot,按模块复用的三个技巧
如果这套源码你已经能稳定跑通,下一步值得做的事是把它迁移到一个现代工程里。迁移不是为了炫技,而是为了让你真正理解 J2EE 和 Spring Boot 在分层思想上的一脉相承。老代码里的 JavaBean 和 DAO 逻辑,几乎可以原样搬进 Service 层;Servlet 里的控制逻辑,替换成 Controller 里的方法;JSP 则可以保留一段时间,等你熟悉 Thymeleaf 之后再替换。
第一个技巧是先把数据访问层从 Servlet 里剥离。老代码里经常直接在 Servlet 里写数据库操作,迁移时要统一搬到 Service 或 DAO 类里。比如前面那个UserBean,迁移后可以改成一个UserService,方法签名保持不变,调用方从 LoginServlet 换成 LoginController:
@Service public class UserService { public boolean checkLogin(String username, String password) { // 使用 MyBatis 或 Spring Data JPA 实现同一段查询逻辑 return userRepository.countByUsernameAndPassword(username, password) > 0; } }第二个技巧是 Servlet 映射改成 Controller 注解。Tomcat 里的<servlet-mapping>配置对应的是 Spring MVC 里的@PostMapping:
@Controller public class LoginController { @Autowired private UserService userService; @PostMapping("/login.do") public String login(String username, String password, HttpSession session, Model model) { if (userService.checkLogin(username, password)) { session.setAttribute("loginUser", username); return "redirect:/welcome.jsp"; } else { model.addAttribute("error", "用户名或密码错误"); return "login"; } } }关键点在于/login.do延续了原来的访问路径,页面里的表单不用改动。redirect:对应原来的sendRedirect,返回视图名对应原来的forward。
第三个技巧是数据源配置合并到 application.properties。迁移后,context.xml 里的 JNDI 数据源不再需要,直接写:
spring.datasource.url=jdbc:mysql://localhost:3306/userdb?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8 spring.datasource.username=root spring.datasource.password=yourpassword spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.hikari.maximum-pool-size=20迁移验证的方法是:建一个空壳 Spring Boot 工程,配置好数据源和依赖,先把不带界面的 Service 层迁过去,写一个简单的单元测试调用登录校验,确认返回结果一致;再迁 Controller,保留 JSP 视图;最后删除旧的 war 部署,用 Spring Boot 内嵌 Tomcat 启动,重新回归一遍登录流程。注意迁移 JSP 时,Spring Boot 需要额外引入 JSP 解析依赖并在application.properties里配置视图前缀和后缀,否则页面会 404。
几年前我第一次动手迁移这种老项目时,犯过最蠢的错误是把整个 web.xml 复制进 Spring Boot,结果容器把老的过滤器加载了两遍,应用启动直接抛异常。从那以后我养成了一个习惯:迁移时每个配置文件都重新检查一遍,不直接复制旧文件,只借用里面的参数。这也是我希望你在这套源码上能练出来的能力——读懂的代码,才值得被复用。希望帮到你。
本文还有配套的精品资源,点击获取