第一次在 MyEclipse 里写 Servlet 的新手十个有九个会被一堆web.xml配置、类路径和 Tomcat 日志搞得晕头转向。其实 Servlet 说白了就是运行在服务器上的一个 Java 类浏览器发请求过来它负责接住请求、处理一下、然后把结果返回去。今天分享一个最经典的入门案例在 MyEclipse 里创建一个 Servlet继承javax.servlet.GenericServlet让浏览器输出一行hello j2ee!。这个例子虽然短却能把 Web 项目从编码、部署到访问的完整链路走通。所以我一直建议初学者先别一头扎进各种集成开发框架就从这个最小的“能跑起来的程序”开始搞清楚请求到底是怎么被处理的。1. Servlet 到底是干什么的为什么要拿 GenericServlet 开刀1.1 一个 Servlet 请求的完整旅程很多人在学习 Servlet 时根本不清楚它处于整个流程的哪个位置于是写代码全靠模仿。这里用一种非常粗鲁但好懂的方式来理解你打开浏览器输入地址回车这个动作就是向服务器发了一个 HTTP 请求。服务器拿到请求后不会直接处理所有业务而是把它转交给对应的 Java 类这个 Java 类就是 Servlet。Servlet 实例由 Web 容器负责创建和调用。容器收到请求后会解析请求头、请求参数等信息包装成一个ServletRequest对象同时准备一个ServletResponse对象用来收集你返回的内容。然后容器根据请求地址找到匹配的 Servlet调用这个 Servlet 的service方法。你在service方法里写什么它就往响应里放什么最终由容器拼装成完整的 HTTP 响应送回浏览器。如果把整个流程比作一家餐馆Tomcat 这类容器就是前台Servlet 就是后厨负责做菜的厨师。你有自己的特色菜但怎么接单、怎么把菜端出去基本不用你操心。GenericServlet这个父类正好帮你把大部分“接单、备菜”的工作准备好了你只需要专心做一道菜。1.2 GenericServlet 与 HttpServlet 的取舍在 Servlet API 里GenericServlet和HttpServlet是两个常见父类。不少新手一开始就被教材带着用HttpServlet这没有错但也因此错过了理解 Servlet 本质的机会。GenericServlet与协议无关它不关心你是不是 HTTP 请求只提供一个需要你重写的service(ServletRequest request, ServletResponse response)方法。而HttpServlet是在GenericServlet基础上针对 HTTP 做了封装把service方法内部拆分成了doGet、doPost、doPut、doDelete等方法。比较项GenericServletHttpServlet关注协议与协议无关只处理 HTTP 协议核心要重写的方法service(ServletRequest, ServletResponse)doGet / doPost 等需要理解的前置内容请求对象、响应对象、Servlet 生命周期HTTP 方法、请求头、状态码典型使用场景教学、非 HTTP 自定义协议绝大多数 Web 开发适合初学者程度比较友好逻辑集中方便后续真实开发这个例子特意选GenericServlet就是想让你直面最核心的service方法。等你把hello j2ee!跑通再切到HttpServlet会发现只是把service里的逻辑搬到了doGet或doPost。底子是一样的。1.3 学习这个例子需要的最小知识清单不用怕这个例子对基础要求非常低。你需要确认自己已经具备下面几点会基本 Java 语法知道什么是类、对象、继承、方法重写。本机装好了 JDK并且配置了JAVA_HOME。MyEclipse 能正常启动至少见过它的 Java 透视图。知道 Tomcat 是用来跑 Web 项目的服务器不需要会调优。如果你连“继承”是什么意思都忘了赶紧翻一下 Java 面向对象那几章。因为GenericServlet是抽象类我们的 Servlet 必须继承它并且实现它唯一的抽象方法service。这个套路后面学什么都绕不开。说实话很多人没有跑通 Servlet 是因为卡在环境和部署上而不是卡在 Java 代码上。代码就十几行环境问题却能把人折磨到怀疑人生。所以接下来我们先耐心处理环境。2. 动手前先把环境捏合好JDK、MyEclipse 与 Tomcat2.1 版本搭配建议我的日常使用建议是JDK 8 或 JDK 11配合某个支持 Java EE 的 MyEclipse 版本再用 Tomcat 8.5 或 Tomcat 9 作为本地服务器。为什么不用 Tomcat 10因为 Tomcat 10 之后把javax.servlet包换成了jakarta.servlet标题里明确要求写javax.servlet.GenericServlet如果盲目装最新版 Tomcat代码虽然看着没变但编译期就会提示找不到javax.servlet包徒增困惑。还要强调一点javax.servlet下的类并不是 JDK 自带的它来自 Servlet 规范是 Tomcat 提供的servlet-api.jar。所以不要把这类包自己下载导入项目更不要去 JDK 安装目录里找。只要正确关联了 TomcatMyEclipse 在编译时就会帮你把对应的类加进来。如果你不用 IDE纯手写纯命令行编译才需要手动把servlet-api.jar放进classpath。2.2 新建 Web Project 时的四个关键选项打开 MyEclipse选择File - New - Web Project。不同小版本的菜单文案可能有点差异但核心选项基本一致。这里有几个容易埋坑的地方第一项目名。我建议用一个有意义的名字比如HelloJ2eeDemo。项目名大概率会变成后面访问地址的一部分也就是 context root提前想好能减少地址写错的概率。第二Java EE 版本或 Dynamic Web Module 版本。如果版本太低有些新特性用不了如果太高又可能与你选择的 Tomcat 不匹配。一般选 Servlet 3.x 或 4.x 这一档比较稳健。第三是否生成web.xml。有些版本默认用注解不帮你生成web.xml。本案例需要手动改web.xml所以我建议勾选“生成 web.xml”或者等创建后再手动补一个。第四Target Runtime。如果创建向导里让你选择服务器运行时直接选你已经配置好的 Tomcat。这一步能让 IDE 自动把 Servlet API 加入构建路径后面编译不会报红。2.3 项目目录中最需要盯住的三样东西新项目建好以后目录里会出现很多你不认识的东西。第一次写 Servlet只要盯住下面几个关键位置就行HelloJ2eeDemo/ ├─ src/ │ └─ com/demo/servlet/HelloJ2eeServlet.java ├─ WebRoot/ │ ├─ WEB-INF/ │ │ ├─ web.xml │ │ └─ lib/ │ ├─ index.jsp │ └─ META-INF/ └─ .classpath如果 MyEclipse 版本不同WebRoot也可能叫WebContent作用是一样的。src是你写 Java 代码的地方WEB-INF/web.xml是 Web 项目的核心配置文件WEB-INF/classes目录平时可能看不到它是服务器真正装载 class 文件的地方。部署时Tomcat 会把src下编译好的 class 放到WEB-INF/classes如果你发现改代码后服务器还在跑旧逻辑十有八九是这个过程出了问题。很多新手喜欢在WEB-INF里手动放一个 jar 包或者删掉里面“多余”的文件结果项目启动直接失败。记住WEB-INF不是用来乱玩的它是 Web 容器的“内部仓库”不允许浏览器直接访问里面的资源。2.4 为什么我不推荐用 MyEclipse 的 Servlet 向导MyEclipse 有New Servlet向导点几下就能生成一个带注释、带doGet、doPost的 Servlet。但我真的不建议第一次学 Servlet 就用它。向导生成的代码太“重”新手看着满屏注释和一堆方法反而不清楚自己到底该写哪里。更关键的是向导默认生成的父类往往是HttpServlet。如果你用向导后又临时把父类改成GenericServlet生成的方法签名可能全是doGet和doPost编译直接报错。所以本案例我建议你用最朴素的方式手动创建包手动创建类手动写service方法。这一类逻辑只有十几行完全可以自己敲一遍。自己敲过眼睛里才有印象。3. 手写一个 GenericServlet输出 hello j2ee3.1 创建包和类文件别用向导在src目录上右键选择New - Package包名写com.demo.servlet。包名不是必须带 demo关键是层次清楚让人一眼看到这是 Web 项目的 servlet 层。包建好后右键这个包选择New - Class。类名写HelloJ2eeServlet。在 Superclass 一栏里直接填javax.servlet.GenericServlet。这一步要手写完整限定名别只写GenericServlet否则 IDE 找不到。如果你不确定自己敲对了没有可以先写一个简单的类不要勾选任何回调方法。建好类后IDE 会生成一个空类头。你需要在类上补public关键字然后在大括号里重写service方法。我第一次带新人时经常发现他建类的时候没勾public static void main就觉得类有问题。其实 Web 项目里完全不需要 main 方法入口由容器控制你只需要把service方法交给容器调用就行。3.2 完整源码和关键行解释下面是我实际跑通过的完整代码直接把HelloJ2eeServlet.java里的内容替换掉即可。package com.demo.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.GenericServlet; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; public class HelloJ2eeServlet extends GenericServlet { private static final long serialVersionUID 1L; Override public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { response.setContentType(text/html;charsetUTF-8); PrintWriter out response.getWriter(); out.println(html); out.println(head); out.println(titlej2ee demo/title); out.println(/head); out.println(body); out.println(h1hello j2ee!/h1); out.println(/body); out.println(/html); } }这段代码里最核心的就是service方法的签名。如果你写错一个字母或者漏了throws ServletException, IOExceptionIDE 会抱怨这不是一个合法的实现。签名必须一字不差public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException这里解释几个新手容易产生疑惑的点response.setContentType(text/html;charsetUTF-8);的作用是告诉浏览器我返回的内容是 HTML 文本字符编码是 UTF-8。因为这次输出的是英文hello j2ee!不写也不影响显示。但如果你以后改成中文这里不写就会出现乱码。response.getWriter()返回一个PrintWriter你可以用它往响应里写内容。注意这个方法必须在setContentType之后调用顺序反过来可能导致编码设置不生效。还有同学会问写完内容后要不要out.close()。在这个例子里不写 close 也没问题因为容器在service方法执行完毕后会帮你自动处理响应。如果你主动关闭了这个 writer后面再想往响应里补内容就会出问题。一般演示用的 Servlet 代码我都不习惯在方法里手动 close除非需要立刻刷新流。3.3 在 web.xml 里配置 Servlet 与 URL 映射光有类还不够你得告诉容器“哪个请求地址应该找这个类”。这一步由web.xml完成。打开WebRoot/WEB-INF/web.xml在web-app节点中加入配置?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 servlet servlet-namehelloJ2ee/servlet-name servlet-classcom.demo.servlet.HelloJ2eeServlet/servlet-class /servlet servlet-mapping servlet-namehelloJ2ee/servlet-name url-pattern/hello/url-pattern /servlet-mapping /web-appservlet-name可以随便起但必须保持两个地方一致servlet里的名字和servlet-mapping里的名字要一模一样。servlet-class必须写类的完整限定名也就是包含包名在内的路径如果写成了HelloJ2eeServlet容器去com/demo/servlet包外找类一定找不到。url-pattern必须以/开头。这里写/hello意味着访问地址里的最后一个路径段是 hello。千万别写成hello那样启动时就会报映射格式错误。3.4 为什么还要写 web.xml用它到底解决了什么问题有的同学会问现在的 Servlet 不是可以用WebServlet注解吗为什么还要手动写web.xml这是个好问题。注解是 Servlet 3.0 之后引入的确实方便。如果你的项目不想用web.xml完全可以在HelloJ2eeServlet类上直接写WebServlet(/hello) public class HelloJ2eeServlet extends GenericServlet { ... }但我仍然建议新手先熟悉web.xml。原因是web.xml里的映射关系非常直白你能看到请求地址与类的对应关系。以后再看到项目里“少写一个配置文件导致所有请求 404”的诡异问题你至少有过排查方向。如果你用了注解同时项目里又保留了完整的web.xml并且web.xml里把metadata-complete设为true那么注解会被忽略。这个坑在真实项目里很常见。课设或练手阶段建议只保留一种配置方式别两套同时上否则容易自乱阵脚。4. 部署到 Tomcat 并让浏览器看到 hello j2ee4.1 用 MyEclipse 关联一个本地 Tomcat 实例类写好了配置也写好了接下来要让程序跑起来。首先保证 MyEclipse 的 Servers 视图里有 Tomcat。如果没有右键视图空白处选择New - Server选你本机安装的 Tomcat 版本再指定 Tomcat 安装目录。等服务器实例出现后右键这个 Server选择Add and Remove把HelloJ2eeDemo添加到右侧的 Configured 列表里。这一步就是把项目“发布”给 Tomcat。发布完成后点 Server 上的启动按钮控制台会出现 Tomcat 启动日志。看到Server startup in xx ms之类的信息基本就是启动成功了。有些版本卡在这一步是因为弹出的窗口让你配置 server location。如果你用的是独立安装的 Tomcat建议选择使用 Tomcat 安装目录作为部署位置如果你只是临时学一下用 IDE 工作空间的临时部署目录也可以。核心是别让 MyEclipse 和本机 Tomcat 抢同一个目录一个项目被两个管理器同时操作很容易出现资源锁定问题。4.2 启动后访问地址的三段式拆解一切正常后打开浏览器输入http://localhost:8080/HelloJ2eeDemo/hello如果页面显示了hello j2ee!恭喜你的第一个 Servlet 已经跑通了。这个地址很好拆localhost:8080本机 Tomcat 的地址和默认端口。HelloJ2eeDemo项目的 context root通常就是项目名。如果项目名改过或者部署时指定了别的 context root这里的路径也要跟着变。/helloweb.xml里配置的url-pattern。很多新手把http://localhost:8080/HelloJ2eeDemo/hello错写成http://localhost:8080/hello结果一直 404。问题就出在漏掉了项目名。这不是代码问题而是 URL 组成的常识问题。4.3 怎么确认请求真的经过了你的类页面能显示hello j2ee!大概率说明链路是通的。但如果你想更确定地看到 Servlet 执行过程可以临时在service方法第一行加一句日志输出System.out.println([Servlet] service method invoked);然后重启 Tomcat刷新浏览器页面回到 MyEclipse 的控制台应该能看到这行日志。这个方法在以后排查复杂问题时非常有用。以后部署到远程服务器网站没反应日志就是你判断“请求到底到没到应用层”的最直接证据。看日志也许比看页面更枯燥但请记住页面只是最终结果日志才是过程真相。5. 踩坑日志新手最容易翻车的五个细节5.1 404大概率不是代码错了是路径没对上看到 404 时先别急着怀疑 Java 代码。我见过很多次的情况是代码写得完全没问题但 Tomcat 压根没加载到这个项目或者 URL 少了一段。排查顺序是先看 Tomcat 是不是真的启动了再确认项目有没有出现在Add and Remove的右侧列表里然后检查地址栏路径。路径要完全区分大小写HelloJ2eeDemo和helloJ2eedemo是两回事。最后再去核对web.xml里的url-pattern是不是/hello。还有一个隐藏问题如果项目已经部署到 Tomcat 的webapps目录里但你后来又手动删除过该目录里的项目文件夹MyEclipse 可能没有感知。这时候右键 Server选择Clean或Remove All然后再重新添加项目。别怕这几个操作看起来“暴力”清掉旧的部署状态往往是解决 404 的捷径。5.2 500先从父类、方法签名和控制台报错下手如果页面显示 500说明 Tomcat 收到了请求但执行过程中出了问题。打开 MyEclipse 控制台看异常堆栈通常能直接定位原因。最常见的错误有两类。第一类类没有正确继承GenericServlet或者service方法没有实现报AbstractMethodError或 “Servlet.service() threw exception”。第二类web.xml里的servlet-class写错报ClassNotFoundException。还有一种特别容易犯的低级错误service方法自己写成了任何名字都行只要不是service容器就无法调用。IDE 里的那个Override能帮你提前发现这种问题——如果父类根本没有对应的方法编译器会直接报错。看到方法名旁边有红色波浪线就说明方法签名不对。5.3 中文乱码这次躲过了下次换中文就掉坑hello j2ee!是英文所以浏览器怎么显示都正常。一旦你改成“你好J2EE”可能会瞬间出现一片乱码。原因不在println而在于响应头里没有告诉浏览器用什么编码。正确做法是在拿到PrintWriter之前设置response.setContentType(text/html;charsetUTF-8);而且web.xml里最好也声明web-app ... metadata-completefalse这件事情请一定形成肌肉记忆。以后学 Spring MVC、前后端分离项目编码设置依然是高频问题。所有看起来“莫名其妙”的中文乱码绝大多数都是字符编码在两个环节不一致造成的服务器写出的编码和浏览器解析时用的编码。5.4 端口占用Tomcat 刚点 Start 就失败启动 Tomcat 时如果弹出类似Port 8080 required by Tomcat ... is already in use的错误说明 8080 端口已经被其他程序占用。可能是你之前启动了一个没关干净的 Tomcat也可能是其他软件占了端口。最简单的处理方案是关闭占用端口的进程或者修改conf/server.xml里的端口号。比如把 8080 改成 8081然后访问地址也同步改成http://localhost:8081/HelloJ2eeDemo/hello。但别只改一个端口完事要检查三个HTTP 端口、AJP 端口、SHUTDOWN 端口它们默认分别是 8080、8009、8005。只改其中一起可能还会出现另一个端口冲突。不过初学者不用背遇到端口占用先看看完整报错到底说的是哪个端口。5.5 代码改了却不生效发布目录里的“过去式”问题这是非常常见、非常烦人的问题你改了 Java 代码也很确认保存了但刷新页面结果还是旧内容。原因可能是 MyEclipse 的自动编译没有生效也可能 Tomcat 没有重新加载项目。先手动执行Project - Clean让 IDE 强制重新编译全部代码再去 Server 上执行一遍重启。如果还不行把项目从 Server 里 Remove 掉再重新 Add 进去相当于把部署目录整个清理后重来。浏览器缓存也可能干扰你。你可以按CtrlF5强制刷新或者用一个无痕窗口打开页面这样能排除本地缓存干扰。这个问题的核心是你改的是源码文件但服务器运行的是WEB-INF/classes里的一份拷贝。这份拷贝什么时候同步更新由 IDE 和容器配合决定。理解了这个机制以后排查“为什么代码没更新”问题思路就会清晰很多。最后多说一句个人经验。hello j2ee!这个例子看着简单但它是一根很趁手的“探针”能一次性验证开发环境、服务器、部署路径、项目结构和 Servlet 基本写法。我见过不少同学跑通之后感觉特别开心第二天又遇到 404 就蒙了其实是因为对这个流程还缺少“肌肉记忆”。我的建议是跑通之后把这个项目整个删掉重新手动创建一遍不复制代码凭记忆写类和配置。第二次往往还是会卡在细小的地方但卡住的那一刻就是你真正掌握某个知识点的时候。先别急着去学框架把这一条链路焊死在脑子里后面接触HttpServlet、注解式开发、Spring MVC 时你会发现所有复杂的东西都是在给这个最朴素的“请求-处理-响应”流程加包装而已。