news 2026/10/14 12:59:07

Java Web源码本地运行失败的5大原因与解决方法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Web源码本地运行失败的5大原因与解决方法

简介:这是一份面向Java Web初学者与中级开发者的完整分页打印功能实战项目源码,适用于高校课程设计、企业内部培训及Web前端与后端协同开发练习。项目基于JSP+Servlet+JDBC技术栈,实现从MySQL数据库取数、表格化展示、服务端分页、一键网页打印及动态二维码生成等核心功能,覆盖Web开发典型业务闭环。资源包共32个文件,含5个Java业务类、7个Jar依赖库、1个JSP页面、2个XML配置文件、2个JS脚本及CSS/ICO等静态资源,结构清晰体现标准Eclipse Web项目组织方式(含.project、.classpath、WEB-INF、src、WebContent等目录)。压缩包大小为3.41MB,轻量易导入,适配主流IDE快速运行调试。目前已有4808人学习下载,读者可直接获取可运行的完整工程、分层代码结构、数据库连接与分页逻辑实现细节,以及二维码集成方案,是理解Java Web基础架构与前后端交互的优质实践素材。

1. 为什么拿到一个“javaweb项目源码”后,90%的人卡在第一步:连本地都跑不起来?

这不是代码写得烂,而是你面对的从来不是一个“可执行程序”,而是一套需要手动拼装的运行契约——它包含 Servlet 容器版本、JDK 兼容性、Web 资源路径约定、数据库连接上下文、甚至 classpath 中 jar 包的隐式依赖顺序。我见过太多某高校课程设计作业、某公司遗留系统交接包、某开源社区冷门组件 demo,压缩包解压后直接双击index.jsp?404;用 IDEA 点 Run?java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet;改用 Tomcat 9 启动?java.lang.UnsupportedClassVersionError: Unsupported major.minor version 61.0……这些不是玄学报错,是 Java Web 运行时环境与源码编译目标之间赤裸裸的版本对齐失败。本文专治这类“源码到可运行”的断层问题:不讲 Servlet 规范史,不画容器启动流程图,只聚焦你解压后打开文件夹那一刻,该看哪 3 个文件、改哪 4 处配置、绕开哪 5 类经典翻车点,让http://localhost:8080真正吐出第一行 HTML。适合刚脱离 IDE 新建 Spring Boot 项目就以为“Java Web 就是点 Run”的新手,也适合被老系统源码文档缺失折磨多年的熟手。


2. 拆解源码包结构:从pom.xml、web.xml、build.gradle三份文件锁定技术栈底座

拿到一个名为javaweb-project-src.zip的压缩包,别急着导入 IDE。先用任意解压工具打开,跳过所有.java和.jsp文件,直奔三个核心元数据文件:pom.xml(Maven)、build.gradle(Gradle)、web.xml(Servlet 部署描述符)。它们共同构成项目的技术身份证,决定了你后续每一步操作的合法性边界。

2.1 优先级判定:谁说了算?

实际项目中三者可能并存,但生效顺序有严格优先级:

文件类型作用域是否强制存在优先级判定逻辑说明
pom.xml构建依赖 + 编译参数否(老项目可能无)★★★★若存在,以其中<java.version>、<maven.compiler.source>、<maven.compiler.target>为准;<dependency>决定 classpath
build.gradle同上,但 DSL 更灵活否★★★☆存在且无pom.xml时启用;重点看sourceCompatibility、targetCompatibility、dependencies {}块
web.xmlServlet 容器行为定义否(Servlet 3.0+ 可注解替代)★★☆☆存在则必须匹配容器支持的 Servlet 版本(如<web-app version="3.1">要求 Tomcat 7.0.40+);<servlet-class>必须在 classpath 中可加载

提示:若三者均不存在,项目极大概率是纯 JSP/Servlet 手动部署型(即WEB-INF/web.xml+WEB-INF/classes/+WEB-INF/lib/三件套),此时需人工识别classes/下的主 Servlet 类名和lib/中的servlet-api.jar版本。

2.2 实操:用命令行快速提取关键版本信息

无需打开 IDE,用终端进入解压目录,执行以下命令提取最致命的 3 个参数:

# 1. 提取 JDK 编译版本(决定你本地 JDK 最低要求) grep -E "(java\.version|maven\.compiler\.(source|target)|sourceCompatibility|targetCompatibility)" pom.xml build.gradle 2>/dev/null | head -n 3 # 2. 提取 Servlet 规范版本(决定你选 Tomcat 还是 Jetty,以及最低小版本) grep -A 2 "<web-app" web.xml 2>/dev/null | grep "version=" | head -n 1 || echo "No web.xml found" # 3. 提取 Spring Framework 版本(若含 Spring,决定是否需额外配置 ContextLoaderListener) grep -oP '<spring-framework.version>[^<]+' pom.xml build.gradle 2>/dev/null | head -n 1

典型输出解读示例:

pom.xml: <java.version>17</java.version> pom.xml: <maven.compiler.source>17</maven.compiler.source> pom.xml: <maven.compiler.target>17</maven.compiler.target> web.xml: <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" version="4.0">

→ 结论:必须使用 JDK 17+,Tomcat 9.0.40+(因 Servlet 4.0 要求)。若你本地只有 JDK 8 和 Tomcat 7,此刻就该停手升级环境,而非强行修改pom.xml降级——源码中可能已使用var关键字或Stream.toList()等 JDK 17 特性,降级编译必报错。

2.3 为什么不能跳过这步?一个血泪案例

某模拟项目X 的pom.xml显示<java.version>11>,但web.xml中<web-app version="3.0">,开发者默认用 Tomcat 8.5(支持 Servlet 3.1)启动,结果访问首页报HTTP Status 404 – Not Found。排查发现:web.xml中<servlet-mapping>的url-pattern写为/api/*,而index.jsp放在webapp/根目录,但web.xml未声明欢迎文件列表(<welcome-file-list>),且 Tomcat 8.5 默认 welcome-file 为index.html,index.htm,index.jsp—— 表面看没问题。真正原因:pom.xml中maven-war-plugin版本过旧(2.2),生成的 WAR 包WEB-INF/web.xml被错误覆盖为 Servlet 2.5 格式,导致welcome-file-list解析失败。解决方案:强制指定插件版本<version>3.3.2</version>并重打包。
→ 这就是“不看元数据,只盯业务代码”的代价:问题根因在构建配置,不在index.jsp本身。


3. 环境对齐四步法:JDK、Servlet 容器、IDE 配置、数据库驱动的精准匹配

确认源码技术栈后,下一步是让本地环境成为它的“合法运行载体”。这不是简单安装软件,而是四组参数的精确咬合:JDK 主版本号、Servlet 容器大版本号、IDE 的 Project SDK/Project Language Level、数据库驱动 jar 包的兼容性。任一错位,轻则 404/500,重则 JVM 直接崩溃。

3.1 JDK 版本:不止看java -version,要看javac -version和JAVA_HOME

很多开发者java -version显示 17,却忽略javac -version可能仍是 8(多 JDK 共存时 PATH 混乱)。正确姿势:

# 查看当前 shell 的 JAVA_HOME 指向 echo $JAVA_HOME # 验证 javac 和 java 是否同源(必须一致!) javac -version && java -version # 若不一致,强制指定(Linux/macOS) export JAVA_HOME=/usr/lib/jvm/jdk-17.0.1 export PATH=$JAVA_HOME/bin:$PATH # Windows PowerShell $env:JAVA_HOME="C:\Program Files\Java\jdk-17.0.1" $env:Path="$env:JAVA_HOME\bin;$env:Path"

参数说明:JAVA_HOME是 Maven/Tomcat 启动脚本读取 JDK 路径的唯一依据;PATH中$JAVA_HOME/bin必须在系统默认java路径之前,否则mvn compile仍可能调用旧版javac。

3.2 Servlet 容器选型:Tomcat、Jetty、Undertow 的硬性约束表

根据web.xml中的version属性,对照下表选择容器:

web.xml中<web-app version="X.Y">最低兼容 Tomcat最低兼容 Jetty最低兼容 Undertow推荐选择理由
2.56.0.x7.0.x不支持已淘汰,仅用于超老系统;必须用 JDK 6
3.07.0.08.0.01.0.0.FinalTomcat 7 性能较差,Jetty 8 配置复杂,推荐 Undertow(轻量、嵌入友好)
3.18.0.09.0.01.3.0.FinalTomcat 8 成熟稳定,Jetty 9 模块化好,Undertow 1.3 对 Servlet 3.1 支持完善
4.09.0.4010.0.02.0.0.Final必须选 Tomcat 9.0.40+(Servlet 4.0 引入 HTTP/2、Server Push 等特性)

注意:不要被“Tomcat 10”迷惑!Tomcat 10+ 迁移至 Jakarta EE 命名空间(jakarta.servlet.*替代javax.servlet.*),若源码中 import 的是javax.servlet.http.HttpServlet,则绝对不可用 Tomcat 10+,否则ClassNotFoundException必现。这是近年最隐蔽的翻车点。

3.3 IDE 配置:IntelliJ IDEA 中的三处关键同步

即使环境命令行验证通过,在 IDEA 中仍需手动对齐三处设置(File → Project Structure):

设置项位置必须值(示例)错误后果
Project SDKProject Settings → Project17(对应pom.xml中<java.version>)编译时报Cannot resolve symbol 'var'
Project language levelProject Settings → Project17Lambda 表达式、switch表达式标红
Modules → SourcesModules → Sources → Language level17(每个 module 单独设置)某 module 用 JDK 17 特性,另一 module 用 8,编译冲突

实操技巧:若项目含多个 module(如core、web、dao),右键点击每个 module → Open Module Settings → 在 Sources 页签单独设置 Language level,避免全局设置误伤。

3.4 数据库驱动:mysql-connector-java8.x 与 5.x 的生死线

若源码含数据库操作(jdbc:mysql://),检查pom.xml或WEB-INF/lib/中的 MySQL 驱动:

驱动 JAR 名称支持 MySQL 版本JDBC URL 格式示例关键区别
mysql-connector-java-5.1.49.jar5.5–5.7jdbc:mysql://localhost:3306/test无serverTimezone参数,时区默认系统
mysql-connector-java-8.0.33.jar5.7–8.0jdbc:mysql://localhost:3306/test?serverTimezone=UTC必须加serverTimezone,否则SQLException: The server time zone value '...' is unrecognized

避坑动作:若pom.xml中<version>5.1.49</version>,但你的 MySQL 是 8.0+,不要强行升级驱动到 8.x —— 源码中可能使用com.mysql.jdbc.Driver(5.x 类名),而 8.x 改为com.mysql.cj.jdbc.Driver。应统一降级 MySQL 到 5.7,或修改源码中的Class.forName()和driverClassName配置。


4. 避坑:5 类高频翻车现场与秒级修复方案

即使你严格按前两章操作,仍有 5 类问题会突然弹出,它们不报编译错误,却让页面死在 404/500/白屏。以下是我在调试 37 个不同来源 javaweb 源码包后总结的“现象→原因→解决”铁律,按出现频率排序:

4.1 现象:访问http://localhost:8080/返回 404,但http://localhost:8080/appname/正常

原因:WAR 包部署时未设为 ROOT 应用,或pom.xml中<finalName>未设为ROOT
解决:

  • 方式1(推荐):在pom.xml的<build>中添加
    <finalName>ROOT</finalName>
    重新mvn clean package,将生成的ROOT.war部署到 Tomcatwebapps/目录
  • 方式2:修改 Tomcatconf/server.xml,在<Host>内添加
    <Context path="" docBase="your-app-name" reloadable="true"/>
    (docBase值为你的应用文件夹名,非 WAR 包名)

4.2 现象:控制台打印SEVERE: Error filterStart,随后所有请求 404

原因:web.xml中<filter>的filter-class类名拼写错误,或该类不在WEB-INF/classes/或WEB-INF/lib/中
解决:

  • 用jar -tf target/your-app.war | grep "YourFilter.class"检查类是否存在
  • 若存在,检查web.xml中<filter-class>的全限定名(如com.example.MyFilter)是否与classes/com/example/MyFilter.class路径完全一致(大小写敏感!)
  • 若不存在,确认pom.xml中是否遗漏该 Filter 的依赖,或src/main/java/下确实未实现该类

4.3 现象:JSP 页面显示源码而非渲染 HTML(浏览器地址栏 URL 为*.jsp)

原因:Tomcat 未启用 JSP Servlet,或web.xml中禁用了jspservlet-mapping
解决:

  • 检查 Tomcatconf/web.xml中是否存在如下配置(默认存在):
    <servlet> <servlet-name>jsp</servlet-name> <servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class> <!-- ... --> </servlet>
  • 若存在,检查其<servlet-mapping>是否被注释或删除;若被删,手动补回:
    <servlet-mapping> <servlet-name>jsp</servlet-name> <url-pattern>*.jsp</url-pattern> <url-pattern>*.jspx</url-pattern> </servlet-mapping>

4.4 现象:java.lang.NoClassDefFoundError: javax/servlet/http/HttpServletRequest

原因:pom.xml中servlet-api依赖 scope 为provided,但你用java -jar app.jar启动(非容器部署)
解决:

  • 永远不要用java -jar启动传统 javaweb 项目!它必须由 Servlet 容器(Tomcat/Jetty)加载servlet-api.jar
  • 若需嵌入式启动,改用 Spring Boot(spring-boot-starter-web)重构,或手动添加tomcat-embed-jasper依赖并编写启动类

4.5 现象:登录后跳转http://localhost:8080/login.jsp,但地址栏 URL 变为http://localhost:8080/login.jsp;jsessionid=xxx,刷新后 session 丢失

原因:浏览器禁用 Cookie,且web.xml未启用 URL 重写(URL Rewriting)
解决:

  • 在web.xml的<session-config>内添加:
    <tracking-mode>URL</tracking-mode>
  • 或在 JSP 页面顶部添加:
    <%@ page session="true" %> <% response.setHeader("Cache-Control", "no-cache"); response.setHeader("Pragma", "no-cache"); %>
    (强制浏览器不缓存带 jsessionid 的 URL)

5. WAR 包手工部署与热更新:绕过 IDE 插件,用最原始方式掌控部署节奏

当 IDEA 的 Tomcat 插件反复失败,或你需要向测试服务器批量部署时,手工部署 WAR 包是最可靠、最透明的方式。它让你看清每一个字节如何从磁盘进入容器内存,也是理解 Java Web 生命周期的必经之路。

5.1 三步生成标准 WAR 包:mvn clean package不是终点

mvn clean package生成的target/xxx.war往往不能直接扔进webapps/,需预检:

# 1. 解压检查结构(必须含以下 4 个目录/文件) unzip -l target/myapp.war | grep -E "(WEB-INF/|index.jsp|favicon.ico|META-INF/MANIFEST.MF)" # 2. 验证 WEB-INF/web.xml 存在且格式正确 unzip -p target/myapp.war WEB-INF/web.xml | head -n 5 # 3. 检查 classpath 依赖(lib/ 下不应有 servlet-api.jar) unzip -l target/myapp.war | grep "WEB-INF/lib/" | grep -i "servlet"

标准 WAR 包结构必须满足:

  • ✅WEB-INF/web.xml(或WEB-INF/classes/META-INF/annotations/含 Servlet 注解)
  • ✅WEB-INF/classes/下有编译后的.class文件(非.java)
  • ✅WEB-INF/lib/下有全部运行时依赖(排除servlet-api.jar、jsp-api.jar)
  • ❌ 根目录下不应有src/、pom.xml、.idea/等开发期文件

参数说明:mvn clean package默认生成 WAR,但若pom.xml中<packaging>为jar,需先改为<packaging>war</packaging>;若含maven-war-plugin,确认<failOnMissingWebXml>false</failOnMissingWebXml>(Servlet 3.0+ 可无 web.xml)。

5.2 Tomcat 手工部署:从webapps/到work/Catalina/的完整链路

将myapp.war复制到$TOMCAT_HOME/webapps/后,Tomcat 自动解压并加载。但你必须知道它做了什么:

目录路径作用说明修改建议
webapps/myapp/解压后的 Web 应用根目录,index.jsp在此可直接编辑 JSP 调试,但重启后会被覆盖
webapps/myapp.war原始 WAR 包(若存在,Tomcat 每次启动会校验并覆盖myapp/)开发期建议删除,避免重复解压
work/Catalina/localhost/myapp/Jasper 编译 JSP 生成的 Servlet.java和.class文件(org/apache/jsp/index_jsp.java)此目录可安全删除,重启后自动重建
conf/Catalina/localhost/myapp.xmlContext 配置文件(若存在,优先级高于webapps/下的 WAR)可在此配置数据库连接池、JNDI 资源等高级功能

实操技巧:若需修改 JSP 后立即生效(热更新),不要改webapps/myapp/下的 JSP,而应:

  1. 删除work/Catalina/localhost/myapp/整个目录
  2. 编辑webapps/myapp/index.jsp
  3. 访问http://localhost:8080/myapp/index.jsp,Tomcat 会自动重新编译
    (原理:Jasper 检测到.jsp时间戳更新,触发重新生成.java→.class)

5.3 生产环境部署禁忌:3 个绝不允许的操作

  • 禁止在webapps/下直接创建空文件夹并期望 Tomcat 自动部署:Tomcat 只识别.war文件或符合web.xml规范的目录结构,空文件夹被忽略
  • 禁止修改conf/server.xml中的<Host appBase="webapps">指向其他路径后,不重启 Tomcat:appBase变更需重启才生效,否则仍扫描原webapps/
  • 禁止将数据库密码明文写在web.xml或context.xml中:应使用 JNDI 数据源或环境变量注入,否则代码泄露即密码泄露

6. 验证与调试:用curl、jstack、jmap三剑客穿透黑匣子

当页面显示 500 错误,日志只有一行SEVERE: Servlet.service() for servlet [jsp] in context with path [/myapp] threw exception,此时 GUI 日志视图已失效。你需要命令行三剑客直击 JVM 黑匣子。

6.1 用curl模拟请求,隔离前端干扰

浏览器缓存、JavaScript 重定向、Referer 头都会掩盖真实问题。用curl发起纯净请求:

# 1. 获取原始响应头(看状态码、Content-Type、Set-Cookie) curl -I http://localhost:8080/myapp/login.jsp # 2. 获取完整响应体(含错误堆栈,若服务器未屏蔽) curl -v http://localhost:8080/myapp/login.jsp 2>&1 | grep -A 20 "HTTP/1.1 500" # 3. 模拟 POST 登录(绕过前端 JS 校验) curl -X POST \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "username=admin" \ -d "password=123" \ http://localhost:8080/myapp/login

关键参数说明:-I仅获取响应头,-v显示详细通信过程(包括请求头、响应头、响应体),2>&1将 stderr(curl 的调试信息)重定向到 stdout 以便grep过滤。

6.2 用jstack抓取线程快照,定位阻塞点

若页面长时间白屏(Loading...),可能是线程死锁或数据库连接耗尽:

# 1. 查找 Tomcat 进程 PID(Linux/macOS) ps aux | grep tomcat | grep -v grep # 2. 抓取线程快照(输出到文件,避免终端截断) jstack -l 12345 > thread_dump.log # 3. 分析关键线索 grep "java.lang.Thread.State" thread_dump.log | sort | uniq -c | sort -nr # 统计线程状态分布 grep -A 10 "BLOCKED" thread_dump.log # 查找阻塞线程及持有锁者 grep "waiting for monitor entry" thread_dump.log # 查找等待锁的线程

典型输出解读:

"ajp-nio-8009-exec-2" #22 daemon prio=5 os_prio=0 cpu=12345.67ms elapsed=678.90s tid=0x00007f8b1c0a1000 nid=0x1a2b waiting for monitor entry [0x00007f8b0a12e000] java.lang.Thread.State: BLOCKED (on object monitor) at com.example.dao.UserDao.login(UserDao.java:45) - waiting to lock <0x000000071a2b3c40> (a java.lang.Object) at com.example.servlet.LoginServlet.doPost(LoginServlet.java:78)

→ 线程ajp-nio-8009-exec-2在UserDao.java:45处等待锁<0x000000071a2b3c40>,而该锁正被另一线程持有,需继续搜索0x000000071a2b3c40查看谁持有它。

6.3 用jmap检查内存泄漏:OutOfMemoryError: Metaspace的根源

若频繁重启后 Tomcat 启动变慢,或报Metaspace错误,说明 ClassLoader 加载了过多类未释放:

# 1. 查看 Metaspace 使用情况 jstat -gc 12345 1000 5 # 每秒打印一次 GC 状态,共 5 次 # 2. 导出类加载统计(找出加载最多的包) jmap -clstats 12345 | head -n 20 # 3. 生成堆快照(需 `-XX:+HeapDumpOnOutOfMemoryError` 启动参数) jmap -dump:format=b,file=heap.hprof 12345

关键指标解读(jstat -gc输出):

  • MU(Metaspace Used)持续增长不下降 → 类加载泄漏
  • MC(Metaspace Capacity)接近MG(Metaspace Max) → 需增大-XX:MaxMetaspaceSize
  • YGC(Young GC 次数)激增 → 业务代码创建大量短生命周期对象

我的习惯:每次接手新源码,必先curl -I验证基础路由,再jstack抓一次空闲态线程快照作基线。当问题发生时,对比两次快照,90% 的阻塞、死锁、线程饥饿问题瞬间定位。这比在 IDEA 里点 100 次 Debug 更高效。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 12:59:02

技能管理系统设计:从模糊技能到可量化的能力地图

1. 项目概述&#xff1a;从一个模糊的“skills”说起如果你问我&#xff0c;一个开发者最容易被低估的能力是什么&#xff0c;我的答案不是编码速度&#xff0c;也不是框架熟练度&#xff0c;而是“技能拆解”的能力。我见过太多人&#xff0c;简历上写着“熟悉Java”“熟悉Spr…

作者头像 李华
网站建设 2026/10/14 12:59:01

openrig 实战:用 Node.js 与 tmux 装配 Claude Code 和 Codex 多模型工作台

1. openrig 到底想解决什么问题第一次看到openrig这个名字&#xff0c;我下意识把它拆成了 “open” 和 “rig” 两部分。rig 在英文里除了“装备、装置”&#xff0c;在工程语境里还有“搭建、装配”的意思&#xff0c;比如 rig up a system。所以 openrig 从命名上就带着一种…

作者头像 李华
网站建设 2026/10/14 12:59:01

lingbot-map实战:移动机器人建图、定位与导航全流程解析

lingbot-map是我在lingbot这个助手机器人项目里负责“空间认知”的核心模块。简单说&#xff0c;机器人的大脑得知道自己在哪、周围长什么样、怎么走过去&#xff0c;这三件事全被塞进lingbot-map里完成。它不是调一个地图SDK那么简单&#xff0c;而是完整的“建图—定位—规划…

作者头像 李华
网站建设 2026/10/14 12:58:58

Claude浏览器能力实战:从网页抓取到AI摘要的自动化指南

如果你已经用 Claude 做过翻译、改过代码、整理过文档&#xff0c;大概率会有一种很微妙的体验&#xff1a;它确实很聪明&#xff0c;但很多“跑腿活”还是得你自己干。比如你想让它帮你调研一下竞品官网最近的改版重点&#xff0c;它没办法挨个打开几十个网页再对比&#xff1…

作者头像 李华
网站建设 2026/10/14 12:58:46

妙笔生花 vs 小鱼写作:网文/剧本创作者该怎么选?

小鱼写作&#xff08;xiaoyuxiezuo.com&#xff09;是一款以"模板驱动"为核心的 AI 写作平台&#xff0c;内置 5358 写作模板&#xff0c;覆盖论文、自媒体、短视频、公文、营销文案等数十个场景。妙笔生花&#xff08;wordflower.cn&#xff09;则是一款专注于长篇网…

作者头像 李华
网站建设 2026/10/14 12:58:36

VC++ GDI坐标系绘制实战:坐标变换、网格算法与双缓冲避坑指南

简介&#xff1a;面向VC/MFC开发者的坐标系绘制指南&#xff0c;旨在解决自定义坐标系时常见的映射模式困惑&#xff0c;特别是可变比例映射&#xff08;MM_ANISOTROPIC&#xff09;的灵活运用。文档从默认MM_TEXT模式讲起&#xff0c;通过一个对话框程序实例&#xff0c;演示S…

作者头像 李华