简介:PDF电子教程以IntelliJ IDEA 2018.1.4为操作环境,全程演示从零创建一个基于Maven与Jersey的Java Web后端RESTful API模板。教程先从Maven archetype选择maven-archetype-webapp开始,讲解GroupId、ArtifactId的含义,随后在pom.xml中加入Jersey容器与FastJson依赖,并配置web.xml中的JAX-RS Servlet,使接口统一映射到/api/*路径。在此基础上,教程说明如何新建java与resources目录、在IntelliJ IDEA中标记为Sources和Resources,并创建com.detectivehlh.test包及Hello类,通过@Path/@GET注解配合FastJson直接返回JSON数据,还附带Student类的简单示例,便于理解接口数据模型。通过阅读可以避开servlet映射或包扫描路径常见的坑,项目初始化后可直接运行并返回JSON内容。压缩包内含1个PDF文件,大小仅59KB,已有1319人学习使用,内容短小精悍,适合刚接触Java Web或打算快速搭建RESTful服务骨架的开发者参考。
1. 用IntelliJ IDEA新建RESTful API模板:从骨架到接口的一条完整链路
很多人在 IDEA 里第一次建 Java Web 后端,照着网上的教程敲完最后发现两个结果:要么 Tomcat 启动了但访问接口是 404,要么页面还停在经典的 Hello World,根本不是你写的接口。这套“IntelliJ IDEA 新建 Java Web 后端 RESTful API 模板”解决的就是这个问题——用 Maven 的 webapp 骨架建出工程,引入 Jersey 容器接管/api/*的请求,再用传统 Servlet 的方式把 RESTful 接口暴露出去,全程不依赖 Spring Boot。适合两类人:一是课程设计或毕业项目需要交付一个能演示 RESTful 接口的后端,二是维护老项目、需要给既有 Servlet 加 JSON 接口的从业者。它的核心价值不在框架多新,而在于把 JAX-RS 的注册链路完整走通,让你知道一个接口从 URL 到 Java 方法的路径到底是怎么串起来的。
2. Maven项目初始化:先锁定webapp骨架,再谈RESTful
2.1 选型理由:为什么用maven-archetype-webapp而不是Spring Boot
提到新建 Java Web 后端接口,第一反应往往是 Spring Boot。但很多课程设计和老项目场景里,环境是 JDK 7 或 8,或者老师只要求“用 Servlet 技术实现 RESTful 接口”,这时候 Spring Boot 的自动配置反而成了黑匣子,出了问题不好解释。
maven-archetype-webapp 生成的是最标准的 Java Web 工程结构:pom.xml、src/main/webapp、WEB-INF/web.xml 全都就位。它的优点有两个。第一,工程结构极简,没有任何多余的依赖,接口通了就是你加的那几段配置起了作用,逻辑链路一目了然。第二,它最终打包成 war,可以直接扔进 Tomcat,符合 Java Web 课程设计对“部署形态”的要求。Jersey 是 JAX-RS 的参考实现,把它接在 webapp 工程里,只需要一个 Servlet 容器类加一段 web.xml 配置,代价比引入整个 Spring 体系小得多。
需要说明的是,这套模板不是给大型生产项目准备的,生产上你自然会更倾向于 Spring Boot 或微服务。它的定位是“教学演示、课程设计、老工程改造前的最小验证模板”。如果你只是想知道 RESTful API 的后端是怎么跑起来的,用这套骨架去理解,效率比直接抄 Spring Boot 高。
2.2 从Create New Project到Enable Auto-Import的完整操作
打开 IntelliJ IDEA,版本无论 2018 还是 2024,入口基本一致,只是新版本界面稍微紧凑一些。第一步点击 Create New Project,在左侧列表选择 Maven,然后勾选 Create from archetype,在右侧列表中找到org.apache.maven.archetypes:maven-archetype-webapp,选中它,点 next。
接下来填写 GroupId 和 ArtifactId。这一步很多人随手写,但后面 web.xml 的包扫描路径完全依赖它,得认真想。我用一个对照来说明:GroupId 相当于组织标识,比如阿里的 fastjson 框架,groupId 是com.alibaba,artifactId 是fastjson。拿 GitHub 类比,GroupId 就是你的用户名,ArtifactId 就是仓库名。范例里写的是:
| 参数 | 示例值 | 说明 |
|---|---|---|
| GroupId | com.detectivehlh.test | 组织反向域名,也是资源类扫描的基础包 |
| ArtifactId | testDemo | 项目名,会作为本地目录和 war 包名 |
后续两页(Maven 配置、项目位置)都不用动,直接 next 直到完成。建完的瞬间 IDEA 右下角会弹出 “Maven projects need to be imported”,这里务必选择 Enable Auto-Import。这个动作意味着之后每次修改 pom.xml,IDEA 都会自动重新拉取依赖。没勾选的话,后面添加 fastjson、Jersey 依赖后,还需要手动刷 Maven,容易忘了导致代码大面积标红。
2.3 初始化完成后先检查三个位置
项目建好后,先别急着写代码,按下面顺序确认一遍状态,能省掉后面不少排查时间。
第一是看左侧 Project 面板,确认出现了 src/main/webapp 和 pom.xml。第二是打开 pom.xml,观察<packaging>标签,webapp 骨架生成的是war,这决定了最终部署方式。第三是看 IDEA 底部 Maven 工具窗口,依赖列表里默认只有 junit 和几个插件,以及 webapp 骨架自带的 servlet-api(版本很老,后面别跟 Jersey 依赖混淆)。
骨架生成的 web.xml 内容也是关键。看它的根节点:
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">这段配置声明了 Servlet 规范版本是 2.5。后面注册 Jersey 的 ServletContainer 时,一定要保留这个头,不要换成别的版本,否则 Tomcat 启动时可能因为 schema 不匹配解析失败。初学阶段最稳妥的做法是:骨架生成什么版本,就保持什么版本。
3. Jersey依赖与web.xml:把RESTful请求交给Servlet的配置细节
3.1 pom.xml引入jersey-container-servlet依赖
要让 JAX-RS 的注解生效,必须在 pom.xml 中引入 Jersey 的 Servlet 容器实现。打开根目录 pom.xml,在<dependencies>标签内添加:
<dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-servlet</artifactId> <version>2.22.2</version> </dependency>这个依赖内部会传递引入 jersey-server、jersey-common、hk2 等核心组件,不需要自己一个个加。版本 2.22.2 是教程里的选择,对应 JDK 7/8 比较稳。如果你本地是 JDK 8 以上,也可以放到 2.28 或 2.29,接口用法没差别。加了依赖后,观察 IDEA 是否自动在 Maven 面板刷新,如果 pom 上还有波浪线,点右键 Maven -> Reload Project。
从依赖层级上看,jersey-container-servlet 提供了org.glassfish.jersey.servlet.ServletContainer这个类,它本身继承了 HttpServlet。这意味着 Jersey 是借 Servlet 标准来接收 HTTP 请求,再通过内部的 JAX-RS 路由分发到你的@Path类。理解这一点,后面 web.xml 的配置就不会觉得玄学。
3.2 web.xml里注册JAX-RS Servlet
打开/src/main/webapp/WEB-INF/web.xml,在<web-app>标签内部添加以下配置:
<servlet> <servlet-name>JAX-RS Servlet</servlet-name> <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> <init-param> <param-name>jersey.config.server.provider.packages</param-name> <param-value>com.detectivehlh.test</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>JAX-RS Servlet</servlet-name> <url-pattern>/api/*</url-pattern> </servlet-mapping>这里有几个参数非常关键,后续 404 基本都出在它们身上。
jersey.config.server.provider.packages的值是资源类扫描根包。Jersey Servlet 启动时会去扫描这个包下所有带有@Path注解的类,注册成可路由的资源。这个值必须和代码包的包名保持一致,比如你的资源类在com.detectivehlh.test下,那这里就填com.detectivehlh.test。你要连子包也一起扫,习惯写成com.detectivehlh.test就行,Jersey 默认递归扫描子包。
url-pattern是/api/*,代表所有以/api/开头的请求都会进入这个 Servlet。一个 Tomcat 容器里可以注册多个 Servlet,分别响应不同前缀,/api/*只是把接口统一收口到这一条路径上。
load-on-startup设为 1,表示 Tomcat 启动时就实例化并初始化这个 Servlet,目的是让包扫描尽早执行。如果你在浏览器里直接访问项目根路径还是能看到 Hello World,那是 webapp 骨架自带的 index.jsp 在响应,和/api/*路径不冲突,不用奇怪。
3.3 URL路由拼接逻辑:三层路径是怎么变成最终接口的
很多人在这一步开始迷糊:到底访问什么地址才能看到接口?这其实是一个三层拼接的规则,看下表就清晰了:
| 配置层级 | 内容 | 说明 |
|---|---|---|
| Servlet 映射 | /api/* | 第一层前缀,由 web.xml 控制 |
| 类级别 @Path | /hello | 第二层,作用于整个资源类 |
| 方法级别 @Path | get | 第三层,作用于具体方法 |
三者拼接后得到完整接口路径:/api/hello/get。访问时还要加上上下文路径,也就是部署时项目的访问名。如果用 IDEA 的 Tomcat 集成配置,默认上下文根是 war 包名(比如 testDemo),那完整地址就是http://localhost:8080/testDemo/api/hello/get。
这个拼接规则是 JAX-RS 规范的核心,也是排查 404 的第一手依据。无论怎么改路径,最终决定权都在这三层上,缺一层都不行。
4. 目录标注、fastjson与第一个接口类:把序列化链路打通
4.1 Project Structure里标记源码目录,这一步不能省
webapp 骨架默认只有 src/main/webapp,没有 src/main/java 和 src/main/resources。这两个目录要手动新建,否则 Java 类没地方放。操作是:在 src/main 目录上右键,New -> Directory,分别创建 java 和 resources,然后进入 File -> Project Structure(macOS 快捷键 command + ;,Windows 下是 Ctrl + Alt + Shift + S),在 Modules 面板里把 java 目录标记为 Sources,把 resources 目录标记为 Resources。
这一步在初学时容易跳过去。不标记 Sources 的结果是:IDEA 不认为这个目录是可编译源码目录,你写的类的图标右下角会显示一个灰色的小标记,编译时直接忽略。到时候 Tomcat 能启动,但访问接口必然 404,因为 Hello.class 压根不存在。标记完成后,Apply 生效。
resources 目录里目前可以不放东西,但它以后会承担日志配置、数据库配置文件等资源,提前把工程规范建好,后续扩展不用再折腾目录结构。项目正式跑起来后,习惯就是 Java 代码严格放 Sources 目录,配置文件放 Resources 目录,不会乱。
4.2 编写Student类与Hello接口类
在src/main/java下,按 web.xml 里 param-value 的包路径逐层新建包。比如扫描根包是com.detectivehlh.test,就建出一模一样的包结构。实务上我一般直接这样建:右键 java 目录,New -> Package,输入com.detectivehlh.test回车,IDEA 会帮你一次性创建三层目录。
然后先写 Student 类,充当接口返回的数据对象:
package com.detectivehlh.test; public class Student { private String id; private String name; private int age; public Student(String id, String name, int age) { this.id = id; this.name = name; this.age = age; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }Student 类本身是个标准 POJO,字段是 id、name、age。关键点在于 getter/setter 必须齐全,后面 fastjson 序列化时,是依赖 getter 来读取字段值的。没有 getter 的字段,序列化时会静默缺失,不加报错也不知道。所以写这类数据对象时,我的习惯是字段定义了就把 getter/setter 一次性补全,不做半截工程。
然后写核心的 Hello 资源类:
package com.detectivehlh.test; import com.alibaba.fastjson.JSONObject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.ArrayList; import java.util.List; @Path("/hello") public class Hello { @Path("get") @GET @Produces(MediaType.APPLICATION_JSON) public Response getStudent() { List<Student> lists = new ArrayList<>(); lists.add(new Student("1", "mayun", 23)); lists.add(new Student("2", "mahuateng", 24)); lists.add(new Student("3", "zhouhongyi", 25)); JSONObject json = new JSONObject(); return Response.status(Response.Status.OK) .entity(json.toJSONString(lists)) .build(); } }这段代码的逻辑拆开来讲。@Path("/hello")是类级别的路由,URL 第二层;@Path("get")是方法级别的路由,URL 第三层。@GET限定只有 HTTP GET 请求才能命中这个方法,如果你用 POST 去访问,Jersey 会直接返回 405 Method Not Allowed,这一步是 JAX-RS 的硬规则。@Produces(MediaType.APPLICATION_JSON)指定响应的 Content-Type 是 application/json,这样浏览器和客户端拿到响应后,会按 JSON 去解析。
方法内部构造了一个包含三个 Student 对象的 List,然后JSONObject.toJSONString(lists)把 List 序列化成 JSON 数组字符串,比如[{id:"1",name:"mayun",age:23}, ...]。最后Response.status(Response.Status.OK).entity(...).build()是 JAX-RS 的标准响应封装,它同时设置 HTTP 状态码 200 和响应体。直接用 Response 而不是返回裸 List 的好处是,能精确控制状态码和响应类型,后续要加错误状态、自定义响应头,只需在同一条链路上扩展。
4.3 引入fastjson并理解为什么要显式序列化
写到这里,IDEA 会在 Hello 类的 JSONObject 上报红,因为还没引入 fastjson 依赖。回到 pom.xml 添加:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.21</version> </dependency>添加后 Maven 刷新,标红消失。fastjson 在这里的作用就是把 Java 对象转成 JSON 字符串。教程里特意用json.toJSONString(lists)这种写法,而不是依赖 JAX-RS 自动序列化,这在实际项目里是常见的手动控制风格——框架不负责序列化时,你显式调用工具方法,反而对输出的内容有完全的掌控力。
不过要提一句:fastjson 1.2.21 是很老的版本,历史上部分版本出过安全公告。如果这只是课程设计,无所谓;如果以后进了生产环境,要么升级到 1.2.83 以上,要么整体换成 Jackson 或 Gson。模板的作用是把链路跑通,具体序列化工具可以按团队规范替换,不影响 Jersey 的整体路由设计。
5. 避坑手册:404、不生效与社区版Tomcat的排查顺序
5.1 接口404:先看包扫描路径,再看URL拼写
现象:Tomcat 正常启动,浏览器访问http://localhost:8080/testDemo/api/hello/get返回 404,页面甚至还能看到 Hello World。
原因:404 在这个模板里有三个常见来源。一是 web.xml 里jersey.config.server.provider.packages的值与 Hello 所在的包不一致,Jersey 扫不到资源。二是访问 URL 里的上下文路径不对,比如部署名不是 testDemo。三是 Servlet 映射的/api/*与 @Path 拼接后不匹配。
解决:按顺序排查。先打开 web.xml,把 param-value 的值和 Hello.java 的 package 声明逐字符比对,一个字母都不能差。再 Run -> Edit Configurations 看 Deployment 里的 Application context,它决定了部署名。最后在浏览器直接访问根路径确认 Tomcat 已启动,再逐步加api/hello/get这段路径,走到哪一层 404 就停在哪一层,问题立刻暴露。
5.2 加了依赖类还是标红,Maven刷新不到位
现象:pom.xml 里已经写了 fastjson 和 Jersey 依赖,代码里 import 依然全部标红,或者只有部分类能识别。
原因:创建项目时右下角弹出的 Enable Auto-Import 没有点,或者点了之后因为网络问题依赖没有下载完整。
解决:打开 IDEA 右侧 Maven 工具窗口,点击刷新按钮(Reload All Maven Projects),强制重读 pom.xml 并重新拉取依赖。如果本地仓库缺包,File -> Settings -> Build Tools -> Maven里看本地仓库路径,必要时删除~/.m2/repository下对应目录再刷新。这一步是治本的手段,依赖问题八成出在本地仓库不完整,而不是 IDEA 坏了。
5.3 IDEA社区版没有Tomcat Server,用Maven插件代替
现象:打开 Run -> Edit Configurations,左侧加号里找不到 Tomcat Server 选项。
原因:Tomcat Server 集成是 IntelliJ IDEA Ultimate 版的功能,社区版里没有这个 Run 类型。
解决:社区版用户不用换 IDE,在 pom.xml 里加 tomcat7-maven-plugin 就能启动:
<plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/testDemo</path> </configuration> </plugin>然后在 IDEA 右侧 Maven 面板展开 Plugins -> tomcat7,双击 tomcat7:run,项目就起来了。访问路径由<path>控制,比如这里配的是/testDemo,访问http://localhost:8080/testDemo/api/hello/get。注意 tomcat7 插件内部的容器是 Tomcat 7,对应 Servlet 3.0 规范,跑这套 Jersey 2.x 模板没有压力。
5.4 返回的JSON中文乱码
现象:接口通了,但返回的 name 字段是乱码,类似ä½ å¥½这种。
原因:fastjson 序列化时输出的是 Unicode 字符串,响应时 Content-Type 里没有 charset 声明,Tomcat 默认按 ISO-8859-1 编码发送中文,前端拿到的字节流就乱了。
解决:给响应显式指定编码。把 Hello 类里的返回语句改成:
return Response.ok(json.toJSONString(lists), MediaType.APPLICATION_JSON_TYPE).build();.entity()换成Response.ok(entity, mediaType)的写法,MediaType.APPLICATION_JSON_TYPE 会带上 UTF-8 charset。如果还不行,在 Spring 风格的容器里再配置 CharacterEncodingFilter,但在 Jersey 这个模板里,上面这一行基本就治好了。
5.5 改了代码后运行结果没变,部署的war没更新
现象:改了 Hello 类的返回内容,重启 Tomcat,浏览器里还是旧数据。
原因:Deployment 里选的是testDemo:war,IDEA 打包成 war 再部署,改了代码需要重新 build 才会生成新的 war,没 build 自然跑的还是旧包。
解决:我把这个坑的经验写死成一条固定动作:在 Run -> Edit Configurations 的 Deployment 标签里,把部署项改成testDemo:war exploded。这个类型是直接加载项目目录下的编译产物,改完代码点一下构建,Tomcat 下次重启就是最新内容。开发阶段用 war exploded,发布时才用 war,这条规则不用怀疑。
6. Tomcat部署与接口验证:从war exploded到curl的一条龙技巧
6.1 配置Tomcat Server与Deployment
IDEA Ultimate 版用户走到最后一步,点击顶部 Run -> Edit Configurations,左侧加号选择 Tomcat Server -> local。这里首先要把 Application server 指向本机 Tomcat 的安装目录,Tomcat 8.x 或 9.x 都能跑这套模板,Jersey 2.22.2 对版本没有挑剔要求。
配置好 Server 后,切到旁边的 Deployment 标签,点加号选择 Artifact,弹窗里会列出两种格式:testDemo:war和testDemo:war exploded。选war exploded,然后 Apply。这时候在 Server 标签页会看到 Applications context 自动填了/testDemo/,这是上下文根,可以按需改,但要和访问 URL 对上。
这里说明一下两种类型的具体差别:war exploded 部署时把编译好的 classes 目录、web.xml、jsp 等资源直接暴露给 Tomcat,启动速度快,也不是打压缩包的过程;war 则是先打出整包,Tomcat 再解压运行,多一道打包过程。开发调试阶段用 exploded 能少等几十秒钟。
6.2 浏览器、curl与接口验证的完整路径
点右上角绿色运行按钮,Tomcat 启动,IDEA 会自动打开默认浏览器显示欢迎页。现在手动把地址改到/testDemo/api/hello/get,就能看到返回的 JSON 数组。但是这个验证方式有个不严谨的地方——浏览器地址栏回车走的是 GET 请求,而接口实际可能限制方法类型,所以更精准的验证方式是用 curl:
curl http://localhost:8080/testDemo/api/hello/get输出应该是一段数组字符串,格式类似[{"age":23,"id":"1","name":"mayun"},{"age":24,"id":"2","name":"mahuateng"},{"age":25,"id":"3","name":"zhouhongyi"}]。看到这个,说明从路由到序列化的整条链路都通了。要注意 key 的排序不按字段声明顺序,fastjson 默认输出顺序是按 getter 扫描来的,这是正常现象,不影响客户端解析。
如果需要更直观的调试体验,装一个 Postman 或直接用 IDEA 自带的 HTTP Client 新建.http文件,写上 GET 请求地址,点运行看响应。这个习惯比每次在浏览器里手动敲 URL 高效,还能保存请求记录供后续回归验证。
6.3 模板就绪后的三个扩展方向
模板跑通之后,这套工程本身可以继续生长。第一个方向是加 POST 接口,在 Hello 类里再写一个方法,把@GET换成@POST,方法参数加@RequestBody或用@Consumes(MediaType.APPLICATION_JSON),就能接收前端传来的 JSON 数据。第二个方向是加子包拆分,比如com.detectivehlh.test.controller放资源类、com.detectivehlh.test.model放 POJO,web.xml 的 param-value 改成com.detectivehlh.test即可扫描所有子包。第三个方向是把 fastjson 换成 Jackson,移除 fastjson 依赖后,在 Jersey 里注册 JacksonFeatures,返回类型直接写 POJO,让 Jersey 自动序列化。
我搭这套模板的经验是,多花五分钟把验证动作固定下来,后面半个月都不会踩翻车的坑。第一次搭完,我在浏览器里反复访问/api/hello/get,每次 404 都怀疑是 Jersey 没注册,折腾了一个下午才发现是 web.xml 的包名少打了个字母。从那以后,我每次建类似的 RESTful 模板都强制走一遍固定检查:先看 Maven 刷新完没,再比对 param-value 和 package 声明,最后启动前看一眼 Deployment 里是不是 war exploded。这套流程跑熟了,从建项目到看到 JSON 输出,十五分钟之内能完成,中间不会再被玄学问题拦住。希望帮到你。
本文还有配套的精品资源,点击获取