资讯动态

J2EE老教材源码实战:从Servlet到Maven改造的JavaWeb部署指南

发布时间:2026/10/16 20:54:25 来源:尧图企业网站定制
简介J2EE编程技术源代码由郝玉龙编著是一套面向Java企业级应用开发学习者的配套代码资源覆盖Servlet、JSP、EJB、JMS、JTA等J2EE核心组件实例适合从入门到进阶的开发者对照研习。资源共429个文件压缩包约12.3MB以java源文件91个、jsp页面83个、class文件92个及xml配置文件97个为主体另含properties配置、tld标签库描述与jar依赖包等完整呈现了从Web层到业务层再到数据持久层的典型企业级项目结构。目前已有186人学习下载。通过研读这些源代码可直观理解Servlet与JSP协同构建动态Web应用的流程掌握EJB会话Bean、实体Bean及消息驱动Bean的编写与部署方式并学习JMS异步通信与JTA分布式事务在实际项目中的运用是一份兼顾原理与编码实践的J2EE学习资料。1. J2EE 源码包到底能干什么老教材是最快的 JavaEE 入门路线接手一个维护了两年的老系统想改一处登录逻辑发现项目里全是 Spring 的注解和拦截器改一行要顺着调用链翻半天。翻出教材配套的《J2EE编程技术》源码才看明白——Servlet 处理请求、JSP 渲染页面、JDBC 读写数据框架不过是把这几步包了层皮。这份源码不是教学 demo 拼盘而是从数据库建表、JDBC 连接到 Servlet 接收请求、JSP 回显页面的完整可跑通工程链路。适合三类人教材学完脑子里没整幅画面的在校生、面试前要手推 JavaWeb 请求链路的求职者、被老系统维护逼到墙角的一线开发。2. 源码包结构与环境准备先把编译和部署跑通再谈架构拿到源码包大部分人第一反应是用 IDE 直接打开然后被一堆红叉劝退。常见做法是先手动把它跑起来确认“这代码真的能编译、能部署、能访问页面”再进 IDE 慢慢改。所以这一章先把目录结构、JDK/Tomcat/MySQL 版本组合定下来后面拆代码才有参照系。2.1 解压后先核对目录WebRoot 才是 Tomcat 加载的根目录老式 JavaWeb 工程和现在 Maven 工程的目录差别很大源码包解压后通常是这样的J2EESource/ ├── src/ # Java 源码 │ └── com/... │ ├── bean/ # 实体类 │ ├── dao/ # JDBC 数据访问层 │ ├── servlet/ # 控制器 │ └── util/ # 数据库工具类 ├── WebRoot/ # Web 根目录部署时对应 Tomcat 应用根 │ ├── WEB-INF/ │ │ ├── web.xml # 中央配置文件 │ │ └── classes/ # 编译后的 class 文件 │ ├── css/ │ ├── images/ │ └── jsp/ # 视图页面 └── lib/ # 第三方依赖 jar这段目录结构里有几个关键点。src是源码存放目录对应现代 Maven 工程里的src/main/javaWebRoot对应src/main/webappTomcat 部署时加载的是 WebRoot不是 src。很多人在这一步翻车编译时把 class 输出到了WebRoot/WEB-INF/classes这个位置不能错因为 Tomcat 的类加载器只认WEB-INF/classes多一层目录或者输出到别处运行时就报ClassNotFoundException。lib目录里的 jar 在部署时要放到WEB-INF/lib下它与 classes 目录组成了 Web 应用的类路径。拿到包之后我的习惯是先核对三样东西web.xml 是否放在WEB-INF下、编译输出目录是否为WEB-INF/classes、lib 下是否有驱动 jar。三样都齐再考虑版本兼容问题。提示如果源码包是以.war形式给你的可以直接放进 Tomcat 的webapps目录启动如果是以工程目录形式给的按 WebRoot 路径手动整理后再部署。这里给出一个不依赖 IDE 的编译命令先把代码编过一遍# 在源码根目录执行先确认 JDK 已安装 java -version # 编译全部源码输出到 WebRoot/WEB-INF/classes javac -encoding UTF-8 -sourcepath src \ -cp lib/* \ -d WebRoot/WEB-INF/classes \ $(find src -name *.java)这段命令的逻辑是-encoding UTF-8指定源码编码避免中文注释和字符串变成乱码-sourcepath src让 javac 从 src 下找源码-cp lib/*把 lib 下所有 jar 加进编译类路径包括 servlet-api.jar 和 mysql-connector-java.jar-d WebRoot/WEB-INF/classes指定 class 输出目录最后用find src -name *.java展开所有 Java 文件省得手动罗列包路径。需要注意java -version如果输出 1.8 以上版本没问题如果显示 1.6 或更老建议换 JDK 8老版本 JDK 对 UTF-8 编码的支持比较差。JDK 版本敲定后下一步是确认编译产物能部署。把整理好的目录复制到 Tomcat 的webapps下启动即可# 假设 Tomcat 装在 /opt/tomcat rm -rf /opt/tomcat/webapps/ROOT cp -r WebRoot /opt/tomcat/webapps/ROOT cd /opt/tomcat/bin ./startup.sh tail -f ../logs/catalina.outROOT是 Tomcat 的默认应用根访问http://localhost:8080/时直接命中它如果你不想占用根路径也可以把 WebRoot 改名成j2ee复制过去访问http://localhost:8080/j2ee/。先rm -rf旧的 ROOT 再复制是为了避免残留旧文件混入新代码。启动后立刻看catalina.out报错、异常、端口占用都会打印在这里比在浏览器里等 404 高效得多。2.2 版本组合是第一道门槛JDK 8 Tomcat 8.5 MySQL 5.7 最稳教材源码大多基于 JDK 1.4/1.5 时代Servlet 规范在 2.3/2.4那会儿还不流行 Maven也没有 Spring Boot。硬拿 JDK 17 Tomcat 10 去跑大概率会因为javax.*到jakarta.*的包名迁移直接编译失败。我一般用下面这套组合组件教材默认推荐稳定组合说明JDK1.4 / 1.5JDK 8老代码编译选项兼容也不会触发模块化限制Tomcat5.x / 6.xTomcat 8.5同时支持旧 web.xml 头声明和新的 Servlet 规范MySQL5.0 / 5.1MySQL 5.7默认认证插件兼容老驱动乱码问题少数据库驱动mysql-connector-java 3.x5.1.498.x 驱动需要改类名和连接参数这套组合的意义在于同时保留了老代码的“原生态”又让日常开发不那么痛苦。JDK 8 是分水岭往上 JDK 9 开始模块化老代码里rt.jar相关写法会出问题Tomcat 8.5 能解析老式web.xml的sun.com/dtd/web-app_2_3.dtd头声明Tomcat 9/10 就严格得多了。MySQL 5.7 的默认认证插件是mysql_native_password老驱动认识它MySQL 8 默认换成caching_sha2_password老驱动直接连不上。如果你只有 JDK 11 或更高版本也不是完全不能跑但要做好两个准备第一编译时加一行参数放宽访问权限第二运行时报ClassNotFoundException: javax.xml.bind.JAXBException这类缺失时手动往WEB-INF/lib补 jar。相比之下直接装一个 JDK 8 省心得多这也是我每次给老项目排障时的默认建议——不要和版本兼容性较劲换一个已知稳的组合半小时能解决的问题别耗半天。版本组合确认后数据库这边要把 SQL 脚本先执行一遍。源码包一般带sql目录或直接在文档里附建库脚本拿到先看这张表对不对口CREATE DATABASE IF NOT EXISTS j2ee DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE j2ee; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(50) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;CREATE DATABASE ... DEFAULT CHARACTER SET utf8mb4是现在推荐的编码设定老教材里常写latin1或utf8建议统一改成utf8mb4否则后面中文字段存储和查询会埋雷。username和password字段长度别学教材里写VARCHAR(10)现代注册场景 50 个字符都未必够用顺手放宽不碍事。建完表之后用SHOW TABLES或者图形化客户端确认表存在再进入下一步把源码里的数据库连接参数改成本机配置。3. 核心模块拆解从 JDBC 工具类到 MVC 主线的完整调用链环境跑通之后源码才真正值得读。教材源码最值钱的部分不是它用了多新潮的技术而是把 JavaEE 每个角色拆得很清楚谁负责连接数据库谁负责处理请求谁负责显示页面之间通过什么方法调用。我拆这类源码的习惯是“三步走”先看工具类再看一个完整模块的 Servlet JSP最后过一遍 web.xml 里的映射关系。这里以最常见的用户注册登录模块为例。3.1 数据库连接工具类改密码、换驱动都只动一个文件老教材几乎都会在util包放一个 JDBC 工具类名字通常是DBUtil或DatabaseConnection。它的作用是把建立连接的重复代码收拢起来业务层不需要每次都写Class.forName。简化后的核心逻辑长这样package com.example.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.ResourceBundle; public class DBUtil { // 从类路径下的 db config 文件读取配置 private static final String DRIVER ResourceBundle .getBundle(db).getString(driver); private static final String URL ResourceBundle .getBundle(db).getString(url); private static final String USERNAME ResourceBundle .getBundle(db).getString(username); private static final String PASSWORD ResourceBundle .getBundle(db).getString(password); static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } public static void close(Connection conn) { if (conn ! null) { try { conn.close(); } catch (SQLException e) { // 关闭失败通常不影响主流程但建议打印日志 } } } }这段代码的逻辑是ResourceBundle.getBundle(db)读取类路径下的db.properties文件把驱动类名、URL、用户名、密码一次性读进静态常量Class.forName(DRIVER)在静态代码块里加载 JDBC 驱动只需要执行一次getConnection()是全局唯一的获取连接入口所有 DAO 都通过它拿连接。这样设计的直接收益是改数据库密码、换 MySQL 驱动时只编辑db.properties不需要碰任何 Java 代码。对应的db.properties长这样drivercom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/j2ee?useUnicodetruecharacterEncodingutf8useSSLfalse usernameroot password123456参数说明driver在 MySQL 5.7 老驱动时写com.mysql.jdbc.Driver如果驱动升级到 8.x必须改成com.mysql.cj.jdbc.Driver。url里的useUnicodetruecharacterEncodingutf8保证中文按 UTF-8 传输useSSLfalse是为了避免老驱动连本地 MySQL 时自动启用 SSL 报证书警告。characterEncoding的值要和建库时的字符集一致这里如果错配查询出来的中文大概率是问号。这个文件必须放在类路径根目录也就是WEB-INF/classes下或者放在src根目录让它编译进去。说起这个工具类“玄学”最多的地方在于连接关闭。老教材代码里经常只写conn.close()结果在连接池场景下把物理连接关掉池子失效。后来我个人的习惯是在 DAO 里用 try-with-resources连接、语句、结果集都放到 try 块里自动关闭省去手工close的顺序问题。JDK 8 完全支持这种写法老代码里大量冗余的 finally 块可以直接删掉。3.2 Servlet 控制器 JSP 视图一个登录模块的数据流全解工具类解决“怎么连数据库”接下来看“请求怎么进来、页面怎么出去”。以登录为例教材里的 LoginServlet 通常完成三层职责接收参数、调 DAO、转发页面。核心代码package com.example.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.example.dao.UserDao; import com.example.bean.User; public class LoginServlet extends HttpServlet { private final UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 接收客户端传来的用户名和密码 String username request.getParameter(username); String password request.getParameter(password); // 2. 交到 DAO 层验证 User user userDao.findByUsernameAndPassword(username, password); // 3. 根据结果决定跳转路径 if (user ! null) { request.getSession().setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }这一段把 MVC 里的 C 角色写得明明白白。第一步getParameter拿到的是浏览器表单提交到服务器的字符串类型永远是 String所以后面如果要对年龄之类的字段做运算得自己Integer.parseInt第二步调UserDaoDAO 里的方法名findByUsernameAndPassword直接反映 SQL 意图这种命名习惯现在看也不过时第三步两个分支覆盖了成功和失败两种路径成则重定向跳首页败则回登录页并在 request 里带一个错误提示。这里要注意sendRedirect和forward的本质区别重定向是浏览器发起第二次请求地址栏会变request 里的属性会丢转发是服务器内部跳转地址栏不变request 属性保留。教材里经常混用这两个方法新手照着写时会发现“登录成功了但首页拿不到用户名”十有八九就是误用了sendRedirect传数据。配套的 web.xml 映射是 Servlet 与 URL 之间的桥servlet servlet-nameLoginServlet/servlet-name servlet-classcom.example.servlet.LoginServlet/servlet-class /servlet servlet-mapping servlet-nameLoginServlet/servlet-name url-pattern/login/url-pattern /servlet-mappingservlet-class必须是带包名的完整类名三个字母写错都会在运行时抛ClassNotFoundExceptionurl-pattern是浏览器地址栏输入的路径这里配置/login那么表单的actionlogin就命中它。这段配置最大的好处是解耦——类名可以随便改URL 对外不变不用通知前端改链接。教材配套源码里如果用了 Servlet 3.0 以后的注解方式WebServlet(/login)web.xml 里就不用重复声明但源码包通常两者都有注意别配双份导致映射冲突。再补一个 JSP 侧的核心片段看页面怎么写% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 % html body h2用户登录/h2 % if (request.getAttribute(errorMsg) ! null) { % p stylecolor:red% request.getAttribute(errorMsg) %/p % } % form actionlogin methodpost input typetext nameusername placeholder用户名/ input typepassword namepassword placeholder密码/ button typesubmit登录/button /form /body /html这段 JSP 的重点不是页面样式而是三个动作pageEncoding声明页面本身的编码errorMsg从 request 里取错误提示并输出表单的action指向 web.xml 里映射的/login。% request.getAttribute(errorMsg) %是 JSP 表达式等价于out.print(request.getAttribute(errorMsg))但它直接拼在 HTML 里如果值是null会自动输出空字符串不会报错。如果你想看更多细节教材后面章节还会引入 JSTL 标签库本质上是把这种 scriptlet 写法换成c:if但底层数据流完全一样。3.3 EJB 与业务接口的取舍哪些章节可以跳过源码包后半部分通常是 EJB 相关章节Session Bean、Entity Bean 的年代产物。本地开发会遇到两个现实问题一是需要应用服务器支持Tomcat 默认不带 EJB 容器要往 lib 里塞 openjdk 之类的实现配置繁琐二是源码里的 EJB 接口大多依赖容器注入直接new一个实现类跑不通。我的建议是EJB 部分不用强跑重点读它的接口定义与实现分离思想——业务接口归接口、实现归实现这个设计在今天的 Spring 里被发扬光大。你只要在代码里看到Remote、Stateless这类注解知道它是一个 EJB 组件看到Home接口知道那是老版本 EJB 的工厂模式写法就够了。真正对面试和工程有帮助的是前面两节的内容EJB 部分理解概念遇到时能认出关键字不会手足无措即可。4. 部署避坑与常见问题排查五个当场翻车的典型案例源码包本身没问题但“能跑”和“跑起来”之间隔着一堆环境问题。这些坑分散在不同机器上我按出现频率整理了五个每一条都是当时卡了半小时以上的真实经历。4.1 编译报错程序包 javax.servlet 不存在现象按前面命令执行javac控制台立刻报程序包 javax.servlet 不存在Servlet、HttpServlet 全部找不到。原因-cp没带servlet-api.jar或者 lib 目录里压根没有这个 jar。老教材的 lib 目录经常只放了 MySQL 驱动Servlet API 依赖 Tomcat 自带的lib/servlet-api.jar但编译时代码里直接引用必须显式加入类路径。解决三选一把 Tomcat 安装目录lib/servlet-api.jar复制到源码 lib 目录或者在编译命令的-cp里追加:/opt/tomcat/lib/servlet-api.jar再稳妥一点直接下载 Servlet 3.1 规范对应的 api jar 放进 lib。复制进 lib 之后记得重新编译别只用 IDE 的自动编译。如果连HttpServlet都识别不了还有一个隐蔽原因IDE 建的是普通 Java 工程而不是 Web 工程没有引入 Tomcat 运行时。把工程类型改成 Web 工程或手动把 Tomcat 的 lib 加进 Build Path问题消失。4.2 Tomcat 启动失败端口占用和 web.xml 解析错误现象./startup.sh执行后马上退出catalina.out里同时出现Address already in use: JVM_Bind或者Error configuring application listener。原因8080 端口被其他进程占用或 web.xml 里的servlet-class写错。两者都会让 Tomcat 启动中断但日志位置不同——端口问题在启动前几行web.xml 问题在应用部署阶段。解决端口冲突用lsof -i:8080Linux/macOS或netstat -ano | findstr 8080Windows查占用进程杀掉或改 Tomcat 的server.xml里 Connector 的port属性。web.xml 的问题重点复核servlet-class是否含完整包名、以及类名的大小写com.example.servlet.LoginServlet是三层包写漏任何一层都是同样的错误提示。这类问题排查顺序我习惯是先看catalina.out尾部 50 行再按关键字搜Caused by最后才改配置重启。4.3 数据库连不上Communications link failure现象页面能打开但一点登录Tomcat 日志里出现Communications link failure或Access denied for user。原因两层。Communications link failure最常见是 URL 里主机端口不对或 MySQL 服务没启动Access denied则是用户名密码错误或者 MySQL 8 的认证插件与老驱动不兼容。教材里写jdbc:mysql://localhost:3306/j2ee本身没错但驱动是 5.x 时代产物时连 MySQL 8 会触发认证协议不匹配。解决先重启 MySQL确认端口在听。再分两路走如果库是 MySQL 5.7保持com.mysql.jdbc.Driver不变只检查账户密码如果库是 MySQL 8把驱动换成com.mysql.cj.jdbc.DriverURL 里补useSSLfalseserverTimezoneAsia/Shanghai再执行下面 SQL 把账号认证改为老驱动认识的方式ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 123456; FLUSH PRIVILEGES;改完驱动记得清一下WEB-INF/classes下的旧 class避免 Tomcat 缓存。4.4 中文乱码页面、请求、数据库三层编码不一致现象登录成功后首页用户名显示正常但往数据库里保存的中文变成了??或者 JSP 页面上直接显示乱码。原因老教材源码里JSP 的pageEncoding写GBK数据库连接参数写characterEncodingGB2312而 Tomcat 请求默认按ISO-8859-1解码三层不一致叠加起来中文大概率坏掉。解决统一到 UTF-8文件层面 JSP 改成pageEncodingUTF-8db.properties的 URL 里characterEncodingUTF-8数据库建库时用utf8mb4请求层面在 web.xml 里配置一个编码过滤器或者直接在doPost开头执行request.setCharacterEncoding(UTF-8)。比较省事的方案是加一个过滤器类package com.example.filter; import javax.servlet.*; import java.io.IOException; public class CharacterEncodingFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(UTF-8); response.setCharacterEncoding(UTF-8); chain.doFilter(request, response); } }这个过滤器的逻辑非常简单任何请求进入 Servlet 之前先经过setCharacterEncoding(UTF-8)把 POST 表单参数按 UTF-8 解码chain.doFilter再放行到目标处理逻辑。配套的 web.xml 配置里url-pattern/*/url-pattern表示拦截所有请求但要注意这个过滤器对 GET 请求的 URL 中文无效Tomcat 对 GET 的 URI 解码取决于server.xml里的URIEncodingUTF-8属性我一般顺手一起加上。4.5 Eclipse 工程导入 IDE 后直接红叉现象双击打开源码包Eclipse 或 IDEA 里一堆红叉Run on Server不可用。原因老源码是 Eclipse 老版本工程.classpath和.project文件格式与现代 IDE 不完全兼容lib 目录没有被识别为依赖。解决常见做法是新建一个 Dynamic Web Project把src拷到新工程的src把WebRoot里的内容拷到新的WebContent或webapp再把lib下的 jar 全部加入 Build Path。用 IDEA 的话新建 Java Enterprise 模块并选择 Web Application然后Project Structure → Libraries里添加 lib 目录。这条路走完红叉基本清零。如果还有个别包名标红多半是 JDK 版本不对回到第 2 章推荐的 JDK 8 再看一遍通常能解决。5. 把教材源码改造成 Maven 工程老代码也能拥有现代构建体验5.1 新建 Maven 工程并迁移目录依赖管理是最直接的收益跑通源码只是第一步真要拿它二次开发我建议做一次 Maven 化改造。改造的收益是依赖管理、构建命令、部署流程全部标准化后续加个 JSON 库或切换数据库连接池都只需要改pom.xml。步骤如下先新建一个空 Maven Web 工程把 src 下 Java 源码和 WebRoot 下的 webapp 内容对应拷入然后在pom.xml里声明依赖dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency /dependencies两个依赖的作用不同javax.servlet-api的scope设成provided表示编译时需要、运行时不打包进 war因为 Tomcat 自带mysql-connector-java不加 scope默认compile会打进WEB-INF/lib保证部署机器上没有安装驱动也能连数据库。版本特意选了 5.1.49就是为了避开驱动 8.x 对 MySQL 5.7 也要求改类名的问题。5.2 验证改造与连接池收尾改造不是终点能复用才是改造完成后原来手写javac的老流程不再需要mvn package一条命令完成编译、打包。验证改造是否成功就把生成的 war 部署到 Tomcat访问一个涉及数据库查询的页面把第 3 章的登录流程完整走一遍。通过后再顺手把 DBUtil 里的DriverManager换成连接池初始化逻辑单测时能明显感到页面响应变快。说句实话我最早从这套教材源码里得到的不是技术而是一个习惯拿到任何老工程先不问为什么先把编译、部署、访问全链路跑通再动手改代码。有一次客户现场数据库版本突然升级密码、驱动全变了我当时就是靠着把所有配置收拢到db.properties这一条经验十分钟定位到问题避免了整个项目组陪我熬夜。从那以后我每拿到一个 JavaWeb 源码包都强制自己先走一遍“编译 → 部署 → 访问 → 改配置 → 再部署”的五步闭环确认链路通了我才敢往里加新逻辑。希望这套方法和这份源码一样帮你在 J2EE 这条路上少踩几个坑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑