背景

本文是《JavaEE 后端从小白到大神》修仙系列第七篇,正式进入JavaEE后端世界。若想详细学习请点击首篇博文,我们开始吧。

第七篇:JAX-RS(RESTful Web Service)实战

  • JAX-RS 核心注解(@Path、@GET、@POST、@Produces、@Consumes);
  • JSON 绑定(JSON-B、Jackson);
  • 异常处理与过滤器;
  • 参数绑定与路径解析;
  • 客户端调用;
  • 与 Spring MVC REST 的差异。

一、JAX-RS 概述

1. 什么是 JAX-RS

JAX-RS(Java API for RESTful Web Services)是 Jakarta EE(原 Java EE)中定义的RESTful Web 服务标准规范,提供了一套 Java 注解和 API,用于快速开发和部署遵循 REST 架构风格的 Web 服务。

从 Jakarta EE 9 开始,JAX-RS 已更名为 Jakarta RESTful Web Services,包名从 javax.ws.rs 迁移到 jakarta.ws.rs

一句话总结:JAX-RS = Java 平台开发 RESTful API 的标准规范。

2. JAX-RS 的核心演进

版本 发布时间 核心变化
JAX-RS 1.0(JSR 311) 2008 初始规范,定义核心注解
JAX-RS 1.1(JSR 311) 2011 小幅修订
JAX-RS 2.0(JSR 339) 2013 新增 Client API、过滤器和拦截器、异步处理
JAX-RS 2.1(JSR 370) 2017 新增反应式客户端、SSE(Server-Sent Events)支持
Jakarta REST 3.0 2020 包名从 javax 迁移到 jakarta
Jakarta REST 3.1 2022 适配 Jakarta EE 10
Jakarta REST 4.0 2024 适配 Jakarta EE 11
Jakarta REST 5.0 2026(开发中) 适配 Jakarta EE 12,进一步与 CDI 集成

3. 核心概念

概念 说明
资源(Resource) 通过 REST API 暴露的实体或服务,用 Java 类表示
资源方法(Resource Method) 资源类中处理 HTTP 请求的方法
表示(Representation) 资源的某种表现形式(JSON、XML、文本等)
提供者(Provider) JAX-RS 扩展组件(如 ExceptionMapper、MessageBodyReader/Writer)

4. JAX-RS 与实现的关系

JAX-RS 只是一个规范(Specification) ,不是具体实现。要使用 JAX-RS 开发应用,需要选择一个兼容的实现(Implementation)

JAX-RS 实现 说明
Jersey 参考实现,由 Eclipse 基金会维护,最常用
RESTEasy Red Hat 出品,与 WildFly 深度集成
Apache CXF Apache 开源,功能全面
Restlet 轻量级 REST 框架

二、核心注解

JAX-RS 通过一组注解来简化 RESTful 服务的开发。

1. @Path——定义资源路径

@Path 用于标注资源类或资源方法的相对路径。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import jakarta.ws.rs.Path;

// 类级别:基础路径
@Path("/api/users")
public class UserResource {
    
    // 方法级别:子路径
    @Path("/{id}")
    @GET
    public User getUser(@PathParam("id") Long id) {
        // ...
    }
}

访问路径:/api/users/123

2. HTTP 方法注解

JAX-RS 为每种 HTTP 方法提供了对应的注解。

注解 HTTP 方法 用途
@GET GET 查询资源
@POST POST 创建资源
@PUT PUT 全量更新资源
@DELETE DELETE 删除资源
@HEAD HEAD 获取响应头(不返回 body)
@OPTIONS OPTIONS 查询资源支持的方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Path("/api/users")
public class UserResource {
    
    @GET
    public List<User> listUsers() {
        // 查询所有用户
    }
    
    @GET
    @Path("/{id}")
    public User getUser(@PathParam("id") Long id) {
        // 查询单个用户
    }
    
    @POST
    public User createUser(User user) {
        // 创建用户
    }
    
    @PUT
    @Path("/{id}")
    public User updateUser(@PathParam("id") Long id, User user) {
        // 全量更新用户
    }
    
    @DELETE
    @Path("/{id}")
    public void deleteUser(@PathParam("id") Long id) {
        // 删除用户
    }
}

3. @Produces——指定响应格式

@Produces 用于指定资源方法可以产生(返回) 的 MIME 媒体类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

// 类级别:所有方法默认返回 JSON
@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {
    
    // 返回 JSON
    @GET
    public List<User> listUsers() { ... }
    
    // 指定返回 XML(覆盖类级别)
    @GET
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_XML)
    public User getUserXml(@PathParam("id") Long id) { ... }
    
    // 支持多种格式,根据请求头 Accept 选择
    @GET
    @Path("/{id}")
    @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
    public User getUser(@PathParam("id") Long id) { ... }
}

4. @Consumes——指定请求格式

@Consumes 用于指定资源方法可以消费(接收) 的 MIME 媒体类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.core.MediaType;

@Path("/api/users")
@Consumes(MediaType.APPLICATION_JSON)  // 类级别:默认接收 JSON
public class UserResource {
    
    @POST
    public User createUser(User user) {
        // 接收 JSON 请求体
    }
    
    @PUT
    @Path("/{id}")
    @Consumes(MediaType.APPLICATION_XML)  // 方法级别覆盖
    public User updateUser(@PathParam("id") Long id, User user) {
        // 接收 XML 请求体
    }
}

5. Hello World 示例

一个完整的 JAX-RS Hello World 示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

@Path("/hello")
public class HelloResource {
    
    @GET
    @Produces(MediaType.TEXT_PLAIN)
    public String sayHello() {
        return "Hello, World!";
    }
}

访问 http://localhost:8080/app/hello,返回 Hello, World!

三、参数绑定与路径解析

JAX-RS 提供了多种注解来从 HTTP 请求中提取参数。

1. @PathParam——路径参数

从 URL 路径中提取参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
@Path("/api/users")
public class UserResource {
    
    // 路径:/api/users/123
    @GET
    @Path("/{id}")
    public User getUser(@PathParam("id") Long id) {
        // id = 123
    }
    
    // 多段路径参数
    // 路径:/api/users/123/orders/456
    @GET
    @Path("/{userId}/orders/{orderId}")
    public Order getOrder(
            @PathParam("userId") Long userId,
            @PathParam("orderId") Long orderId) {
        // userId = 123, orderId = 456
    }
}

2. @QueryParam——查询参数

从 URL 查询字符串中提取参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Path("/api/users")
public class UserResource {
    
    // 请求:GET /api/users?page=1&size=20
    @GET
    public List<User> listUsers(
            @QueryParam("page") @DefaultValue("1") int page,
            @QueryParam("size") @DefaultValue("10") int size) {
        // page = 1, size = 20
    }
}

@DefaultValue 用于指定参数默认值,避免 null 或 0。

3. @FormParam——表单参数

application/x-www-form-urlencoded 格式的请求体中提取参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Path("/api/auth")
public class AuthResource {
    
    @POST
    @Path("/login")
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Response login(
            @FormParam("username") String username,
            @FormParam("password") String password) {
        // username = 表单提交的用户名
    }
}

4. @HeaderParam——请求头参数

从 HTTP 请求头中提取参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@Path("/api")
public class ApiResource {
    
    @GET
    public String getData(
            @HeaderParam("Authorization") String authToken,
            @HeaderParam("User-Agent") String userAgent) {
        // authToken = 请求头中的 Authorization 值
    }
}

5. @CookieParam——Cookie 参数

从 Cookie 中提取参数。

1
2
3
4
5
6
7
8
9
@Path("/api")
public class SessionResource {
    
    @GET
    public String getSession(
            @CookieParam("JSESSIONID") String sessionId) {
        // sessionId = Cookie 中的 JSESSIONID 值
    }
}

6. @BeanParam——参数聚合

将多个参数封装到一个对象中,简化代码。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义参数聚合类
public class UserQueryParams {
    @QueryParam("page")
    private int page;
    
    @QueryParam("size")
    private int size;
    
    @QueryParam("keyword")
    private String keyword;
    
    // getter/setter
}

// 使用 @BeanParam
@Path("/api/users")
public class UserResource {
    
    @GET
    public List<User> listUsers(@BeanParam UserQueryParams params) {
        // 所有查询参数自动注入到 params 对象
    }
}

7. 参数注解汇总

注解 参数来源 示例
@PathParam URL 路径 /users/{id}
@QueryParam URL 查询字符串 ?page=1
@FormParam 表单请求体 username=admin
@HeaderParam HTTP 请求头 Authorization: Bearer xxx
@CookieParam Cookie JSESSIONID=xxx
@BeanParam 多个参数聚合 封装为对象

四、JSON 绑定

1. JSON-B(Jakarta JSON Binding)

JSON-B 是 Jakarta EE 的标准 JSON 绑定规范,用于在 Java 对象和 JSON 之间自动转换。

Maven 依赖

1
2
3
4
5
<dependency>
    <groupId>jakarta.json.bind</groupId>
    <artifactId>jakarta.json.bind-api</artifactId>
    <version>3.0.0</version>
</dependency>

使用示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;

public class JsonbExample {
    public static void main(String[] args) {
        Jsonb jsonb = JsonbBuilder.create();
        
        // Java 对象 → JSON
        User user = new User(1L, "张三", 25);
        String json = jsonb.toJson(user);
        System.out.println(json); // {"id":1,"name":"张三","age":25}
        
        // JSON → Java 对象
        String jsonStr = "{\"id\":1,\"name\":\"张三\",\"age\":25}";
        User parsed = jsonb.fromJson(jsonStr, User.class);
    }
}

2. JAX-RS 中的自动 JSON 绑定

当资源方法的 @Consumes@Produces 指定为 application/json 时,JAX-RS 实现(如 Jersey)会自动使用 JSON-B 或 Jackson 完成 Java 对象与 JSON 的相互转换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
    
    // 返回 User 对象 → 自动转为 JSON
    @GET
    @Path("/{id}")
    public User getUser(@PathParam("id") Long id) {
        return userService.findById(id);
    }
    
    // 接收 JSON → 自动转为 User 对象
    @POST
    public User createUser(User user) {
        return userService.create(user);
    }
}

3. 自定义 JSON 序列化

通过 @JsonbProperty@JsonbTransient 等注解控制 JSON 序列化行为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import jakarta.json.bind.annotation.JsonbProperty;
import jakarta.json.bind.annotation.JsonbTransient;

public class User {
    private Long id;
    
    @JsonbProperty("userName")  // JSON 字段名改为 userName
    private String name;
    
    @JsonbTransient  // 不序列化到 JSON
    private String password;
    
    private Integer age;
    // getter/setter
}

五、异常处理

1. WebApplicationException

JAX-RS 提供了 WebApplicationException,可以在资源方法中直接抛出,并指定 HTTP 状态码和响应内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Path("/api/users")
public class UserResource {
    
    @GET
    @Path("/{id}")
    public User getUser(@PathParam("id") Long id) {
        User user = userService.findById(id);
        if (user == null) {
            // 返回 404 Not Found
            throw new WebApplicationException(Response.Status.NOT_FOUND);
        }
        return user;
    }
    
    @POST
    public User createUser(User user) {
        if (user.getName() == null || user.getName().isEmpty()) {
            // 返回 400 Bad Request,带错误信息
            throw new WebApplicationException(
                Response.status(Response.Status.BAD_REQUEST)
                    .entity("用户名不能为空")
                    .type(MediaType.TEXT_PLAIN)
                    .build()
            );
        }
        return userService.create(user);
    }
}

2. ExceptionMapper——统一异常处理

ExceptionMapper<E> 是一个接口,用于将特定类型的异常映射为 HTTP 响应。

核心步骤

  1. 创建一个类实现 ExceptionMapper<E> 接口;
  2. 使用 @Provider 注解标记;
  3. 实现 toResponse() 方法,返回 Response 对象。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;

// 1. 自定义业务异常
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(Long id) {
        super("用户不存在:" + id);
    }
}

// 2. 创建 ExceptionMapper
@Provider
public class UserNotFoundMapper implements ExceptionMapper<UserNotFoundException> {
    
    @Override
    public Response toResponse(UserNotFoundException exception) {
        return Response.status(Response.Status.NOT_FOUND)
                .entity("{\"error\":\"" + exception.getMessage() + "\"}")
                .type(MediaType.APPLICATION_JSON)
                .build();
    }
}

// 3. 在资源方法中使用
@Path("/api/users")
public class UserResource {
    
    @GET
    @Path("/{id}")
    public User getUser(@PathParam("id") Long id) {
        User user = userService.findById(id);
        if (user == null) {
            throw new UserNotFoundException(id);  // 由 Mapper 处理
        }
        return user;
    }
}

3. 全局异常捕获

可以创建一个 GenericExceptionMapper 捕获所有未处理的异常,作为兜底方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
    
    @Override
    public Response toResponse(Throwable exception) {
        // 记录日志
        exception.printStackTrace();
        
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                .entity("{\"error\":\"服务器内部错误\"}")
                .type(MediaType.APPLICATION_JSON)
                .build();
    }
}

4. 异常处理最佳实践

  • 业务异常:自定义异常类,配合 ExceptionMapper 返回有意义的 HTTP 状态码;
  • 参数校验:使用 WebApplicationException 返回 400 错误;
  • 资源不存在:返回 404;
  • 权限不足:返回 403;
  • 兜底方案:提供 GenericExceptionMapper 捕获所有未处理异常,避免泄露敏感信息。

六、过滤器与拦截器

JAX-RS 2.0 引入了过滤器(Filter)拦截器(Interceptor) 的标准 API,用于在请求/响应的处理管道中插入横切逻辑。

1. Filter vs Interceptor

对比维度 Filter(过滤器) Interceptor(拦截器)
操作对象 消息元数据(HTTP 头、查询参数、媒体类型等) 消息体(Entity)的输入/输出流
主要用途 日志、认证、授权、CORS 压缩/解压、加密/解密
能否中止请求 可以(如认证失败时中止) 不可以
接口 ContainerRequestFilterContainerResponseFilter ReaderInterceptorWriterInterceptor

2. ContainerRequestFilter——请求过滤器

在服务器端拦截请求,可在资源方法执行前进行预处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerRequestFilter;
import jakarta.ws.rs.ext.Provider;
import jakarta.ws.rs.core.Response;
import java.io.IOException;

@Provider
@Priority(1000)  // 优先级数值越小越早执行
public class AuthenticationFilter implements ContainerRequestFilter {
    
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        // 获取 Authorization 头
        String authHeader = requestContext.getHeaderString("Authorization");
        
        // 简单认证示例(实际应使用 JWT 或 Session)
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            // 中止请求,返回 401 Unauthorized
            requestContext.abortWith(
                Response.status(Response.Status.UNAUTHORIZED)
                    .entity("{\"error\":\"未授权访问\"}")
                    .type(MediaType.APPLICATION_JSON)
                    .build()
            );
        }
    }
}

3. ContainerResponseFilter——响应过滤器

在服务器端拦截响应,可在资源方法执行后进行后处理。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ContainerResponseContext;
import jakarta.ws.rs.container.ContainerResponseFilter;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;

@Provider
public class CorsFilter implements ContainerResponseFilter {
    
    @Override
    public void filter(ContainerRequestContext requestContext,
                       ContainerResponseContext responseContext) throws IOException {
        // 添加 CORS 响应头
        responseContext.getHeaders().add("Access-Control-Allow-Origin", "*");
        responseContext.getHeaders().add("Access-Control-Allow-Methods", 
            "GET, POST, PUT, DELETE, OPTIONS");
        responseContext.getHeaders().add("Access-Control-Allow-Headers", 
            "Content-Type, Authorization");
    }
}

4. 服务器端过滤器执行顺序

JAX-RS 服务器端有 4 个过滤器/拦截器的扩展点,按以下顺序执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
请求到达
1. PreMatchContainerRequest 过滤器(资源匹配前执行)
2. ContainerRequest 过滤器(资源匹配后执行)
3. ReadInterceptor(读取请求体时执行)
4. 资源方法执行
5. WriteInterceptor(写入响应体时执行)
6. ContainerResponse 过滤器
响应返回

5. @NameBinding——选择性绑定

默认情况下,Filter 会应用到所有资源方法。如果只想拦截特定方法,可以使用 @NameBinding

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 1. 定义名称绑定注解
import jakarta.ws.rs.NameBinding;
import java.lang.annotation.*;

@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Secured {}

// 2. 在 Filter 上使用 @Secured
@Secured
@Provider
public class AuthenticationFilter implements ContainerRequestFilter {
    // ...
}

// 3. 在资源方法或类上使用 @Secured
@Path("/api/admin")
@Secured  // 该类的所有方法都需要认证
public class AdminResource {
    
    @GET
    @Secured  // 仅该方法需要认证
    public String getAdminData() { ... }
}

6. @PreMatching——资源匹配前拦截

@PreMatching 标记的 Filter 在资源匹配之前执行,此时无法获取具体的资源方法信息。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import jakarta.ws.rs.container.PreMatching;
import jakarta.ws.rs.container.ContainerRequestFilter;

@Provider
@PreMatching
public class PreMatchFilter implements ContainerRequestFilter {
    
    @Override
    public void filter(ContainerRequestContext requestContext) {
        // 在 JAX-RS 将请求匹配到具体资源方法之前执行
        // 可用于请求路由、日志等
        System.out.println("请求路径:" + requestContext.getUriInfo().getPath());
    }
}

七、JAX-RS 客户端 API

JAX-RS 2.0 引入了标准化的客户端 API,用于调用 RESTful 服务。

1. 基本用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

public class RestClientExample {
    
    public static void main(String[] args) {
        // 1. 创建 Client 实例
        Client client = ClientBuilder.newClient();
        
        try {
            // 2. 设置目标 URL
            WebTarget target = client.target("http://localhost:8080/api");
            
            // 3. 发起 GET 请求
            String response = target.path("/users/1")
                    .request(MediaType.APPLICATION_JSON)
                    .get(String.class);
            System.out.println("响应:" + response);
            
            // 4. 获取完整 Response 对象(可获取状态码、响应头)
            Response resp = target.path("/users")
                    .queryParam("page", 1)
                    .queryParam("size", 10)
                    .request(MediaType.APPLICATION_JSON)
                    .get();
            
            System.out.println("状态码:" + resp.getStatus());
            String body = resp.readEntity(String.class);
            System.out.println("响应体:" + body);
            resp.close();  // 关闭响应
        } finally {
            // 5. 关闭 Client(释放资源)
            client.close();
        }
    }
}

2. POST 请求(发送 JSON)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class PostExample {
    public static void main(String[] args) {
        Client client = ClientBuilder.newClient();
        
        try {
            // 创建 User 对象
            User user = new User("张三", 25);
            
            // 发送 POST 请求
            User created = client.target("http://localhost:8080/api/users")
                    .request(MediaType.APPLICATION_JSON)
                    .post(Entity.json(user), User.class);
            
            System.out.println("创建成功:" + created.getId());
        } finally {
            client.close();
        }
    }
}

3. 路径参数与查询参数

1
2
3
4
5
6
7
8
// 路径参数
WebTarget target = client.target("http://localhost:8080/api/users/{id}")
        .resolveTemplate("id", 123);  // 替换路径参数

// 查询参数
WebTarget target = client.target("http://localhost:8080/api/users")
        .queryParam("page", 1)
        .queryParam("size", 20);

4. Client 实例管理

Client重量级对象,创建和销毁开销较大。

最佳实践

  • 整个应用只创建一个 Client 实例;
  • 使用连接池(如 Apache HttpClient)提升性能;
  • 应用关闭时统一关闭 Client
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 应用启动时创建
@ApplicationScoped
public class RestClientProducer {
    private Client client;
    
    @PostConstruct
    public void init() {
        client = ClientBuilder.newClient();
    }
    
    @PreDestroy
    public void destroy() {
        if (client != null) {
            client.close();
        }
    }
    
    public Client getClient() {
        return client;
    }
}

八、JAX-RS 与 Spring MVC REST 对比

1. 本质区别

对比维度 JAX-RS Spring MVC REST
类型 规范(Specification) 框架(Framework)
实现 需要第三方实现(Jersey、RESTEasy) 自带完整实现
定位 专注于 RESTful Web Services 完整的 Web 应用框架(包含 REST 支持)

2. 注解对比

功能 JAX-RS Spring MVC
资源路径 @Path @RequestMapping / @GetMapping
HTTP 方法 @GET@POST@PUT@DELETE @GetMapping@PostMapping
路径参数 @PathParam @PathVariable
查询参数 @QueryParam @RequestParam
请求体 自动绑定(JSON → 对象) @RequestBody
响应体 自动绑定(对象 → JSON) @ResponseBody / @RestController
依赖注入 需配合 CDI @Autowired 原生支持

3. 代码对比

JAX-RS(使用 Jersey)

1
2
3
4
5
6
7
8
9
@Path("/hello")
public class HelloController {
    @GET
    @Path("/{name}")
    @Produces(MediaType.TEXT_PLAIN)
    public Response hello(@PathParam("name") String name) {
        return Response.ok("Hello, " + name).build();
    }
}

Spring MVC

1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/hello")
public class HelloController {
    @GetMapping(value = "/{name}", produces = MediaType.TEXT_PLAIN_VALUE)
    public ResponseEntity<String> hello(@PathVariable String name) {
        return new ResponseEntity<>("Hello, " + name, HttpStatus.OK);
    }
}

4. 选型建议

场景 推荐
追求轻量、标准化、可移植 JAX-RS(规范统一,换实现不换代码)
Spring 生态下开发 Spring MVC(天然集成 Spring Security、Spring Data 等)
微服务架构 两者均可,Spring Cloud 生态更成熟
需要 HTML 视图渲染 Spring MVC(JSP/Thymeleaf 支持更好)
已有 Spring 经验 Spring MVC(上手更快)

核心观点:JAX-RS 是标准规范,适合需要跨实现移植的场景;Spring MVC 是完整框架,适合深度使用 Spring 生态的项目。两者没有绝对优劣,根据项目需求选择。

九、实战案例:用户管理 RESTful API

1. 业务需求

实现一个完整的用户管理 RESTful API:

接口 方法 路径 功能
查询所有用户 GET /api/users 分页查询用户列表
查询单个用户 GET /api/users/{id} 根据 ID 查询用户
创建用户 POST /api/users 新增用户
更新用户 PUT /api/users/{id} 全量更新用户
删除用户 DELETE /api/users/{id} 删除用户

2. 实体类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import jakarta.json.bind.annotation.JsonbProperty;
import jakarta.json.bind.annotation.JsonbTransient;
import java.time.LocalDateTime;

public class User {
    private Long id;
    private String username;
    private String email;
    
    @JsonbTransient  // 不序列化到 JSON(密码不返回)
    private String password;
    
    private Integer age;
    private LocalDateTime createTime;
    
    // 构造器、getter、setter
    public User() {}
    
    public User(String username, String email, String password, Integer age) {
        this.username = username;
        this.email = email;
        this.password = password;
        this.age = age;
        this.createTime = LocalDateTime.now();
    }
    
    // getter/setter 省略
}

3. Service 层

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import jakarta.ejb.Stateless;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List;

@Stateless
public class UserService {
    
    @PersistenceContext
    private EntityManager em;
    
    public List<User> findAll(int page, int size) {
        return em.createQuery("SELECT u FROM User u ORDER BY u.id", User.class)
                .setFirstResult((page - 1) * size)
                .setMaxResults(size)
                .getResultList();
    }
    
    public User findById(Long id) {
        User user = em.find(User.class, id);
        if (user == null) {
            throw new UserNotFoundException(id);
        }
        return user;
    }
    
    public User create(User user) {
        user.setCreateTime(LocalDateTime.now());
        em.persist(user);
        return user;
    }
    
    public User update(Long id, User userData) {
        User user = findById(id);
        user.setUsername(userData.getUsername());
        user.setEmail(userData.getEmail());
        user.setAge(userData.getAge());
        if (userData.getPassword() != null && !userData.getPassword().isEmpty()) {
            user.setPassword(userData.getPassword());
        }
        return em.merge(user);
    }
    
    public void delete(Long id) {
        User user = findById(id);
        em.remove(user);
    }
}

4. 自定义异常

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class UserNotFoundException extends RuntimeException {
    public UserNotFoundException(Long id) {
        super("用户不存在:" + id);
    }
}

public class ValidationException extends RuntimeException {
    public ValidationException(String message) {
        super(message);
    }
}

5. ExceptionMapper

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@Provider
public class UserNotFoundMapper implements ExceptionMapper<UserNotFoundException> {
    @Override
    public Response toResponse(UserNotFoundException e) {
        return Response.status(Response.Status.NOT_FOUND)
                .entity("{\"error\":\"" + e.getMessage() + "\"}")
                .type(MediaType.APPLICATION_JSON)
                .build();
    }
}

@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ValidationException> {
    @Override
    public Response toResponse(ValidationException e) {
        return Response.status(Response.Status.BAD_REQUEST)
                .entity("{\"error\":\"" + e.getMessage() + "\"}")
                .type(MediaType.APPLICATION_JSON)
                .build();
    }
}

6. 资源类(REST Controller)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import jakarta.ejb.EJB;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.util.List;

@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
    
    @EJB
    private UserService userService;
    
    // 1. 查询所有用户(分页)
    @GET
    public Response listUsers(
            @QueryParam("page") @DefaultValue("1") int page,
            @QueryParam("size") @DefaultValue("10") int size) {
        
        if (page < 1) page = 1;
        if (size < 1 || size > 100) size = 10;
        
        List<User> users = userService.findAll(page, size);
        return Response.ok(users).build();
    }
    
    // 2. 查询单个用户
    @GET
    @Path("/{id}")
    public Response getUser(@PathParam("id") Long id) {
        User user = userService.findById(id);
        return Response.ok(user).build();
    }
    
    // 3. 创建用户
    @POST
    public Response createUser(User user) {
        // 简单校验
        if (user.getUsername() == null || user.getUsername().isEmpty()) {
            throw new ValidationException("用户名不能为空");
        }
        if (user.getEmail() == null || user.getEmail().isEmpty()) {
            throw new ValidationException("邮箱不能为空");
        }
        
        User created = userService.create(user);
        return Response.status(Response.Status.CREATED)
                .entity(created)
                .build();
    }
    
    // 4. 更新用户
    @PUT
    @Path("/{id}")
    public Response updateUser(@PathParam("id") Long id, User user) {
        User updated = userService.update(id, user);
        return Response.ok(updated).build();
    }
    
    // 5. 删除用户
    @DELETE
    @Path("/{id}")
    public Response deleteUser(@PathParam("id") Long id) {
        userService.delete(id);
        return Response.noContent().build();
    }
}

7. 应用配置(Application 子类)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
import java.util.HashSet;
import java.util.Set;

@ApplicationPath("/api")  // 所有 JAX-RS 资源的基础路径
public class RestApplication extends Application {
    
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<>();
        // 注册资源类和 Provider
        classes.add(UserResource.class);
        classes.add(UserNotFoundMapper.class);
        classes.add(ValidationExceptionMapper.class);
        classes.add(AuthenticationFilter.class);
        classes.add(CorsFilter.class);
        return classes;
    }
}

访问路径:http://localhost:8080/app/api/users

8. 测试用例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class UserResourceTest {
    
    public static void main(String[] args) {
        Client client = ClientBuilder.newClient();
        
        try {
            // 1. 创建用户
            User newUser = new User("张三", "[email protected]", "123456", 25);
            User created = client.target("http://localhost:8080/app/api/users")
                    .request(MediaType.APPLICATION_JSON)
                    .post(Entity.json(newUser), User.class);
            System.out.println("创建成功,ID:" + created.getId());
            
            // 2. 查询用户
            User user = client.target("http://localhost:8080/app/api/users/{id}")
                    .resolveTemplate("id", created.getId())
                    .request(MediaType.APPLICATION_JSON)
                    .get(User.class);
            System.out.println("查询结果:" + user.getUsername());
            
            // 3. 删除用户
            Response resp = client.target("http://localhost:8080/app/api/users/{id}")
                    .resolveTemplate("id", created.getId())
                    .request()
                    .delete();
            System.out.println("删除状态码:" + resp.getStatus());
            resp.close();
            
        } finally {
            client.close();
        }
    }
}

十、总结

1. 核心知识脉络

层级 技术 核心概念
核心注解 @Path@GET@POST@Produces@Consumes 定义资源和 HTTP 方法映射
参数绑定 @PathParam@QueryParam@FormParam@HeaderParam 从 HTTP 请求中提取参数
数据绑定 JSON-B / Jackson Java 对象 ↔ JSON 自动转换
异常处理 ExceptionMapperWebApplicationException 统一异常 → HTTP 响应映射
横切逻辑 Filter、Interceptor 请求/响应拦截、认证、日志
客户端调用 Client API 调用远程 RESTful 服务

2. 关键理解

  1. JAX-RS 是规范,不是实现:需要选择一个兼容的实现(Jersey 或 RESTEasy);
  2. 注解驱动:通过 @Path@GET 等注解声明式定义 RESTful 服务,代码简洁;
  3. 自动数据绑定:配合 JSON-B,Java 对象与 JSON 自动转换,无需手动序列化;
  4. 统一异常处理ExceptionMapper 可将业务异常统一转换为规范的 HTTP 响应;
  5. Filter 处理元数据,Interceptor 处理消息体:两者分工明确,各司其职;
  6. 标准化客户端 API:JAX-RS 2.0 提供了统一的客户端 API,可调用任意 REST 服务。

3. 与后续学习的衔接

理解 JAX-RS 后,你将能更好地理解:

  • MicroProfile REST Client:声明式 REST 客户端,简化微服务调用;
  • OpenAPI / Swagger:自动生成 REST API 文档;
  • Spring Web MVC 的 REST 支持:对比两种 REST 开发方式的异同;
  • 云原生微服务:RESTful API 是微服务间通信的基础。