JAX-RS 规范详解
一、什么是 JAX-RS?
JAX-RS(Java API for RESTful Web Services)是 Jakarta EE(原 Java EE)提供的一套用于构建 RESTful Web 服务的 API 规范。它定义了一套用于将 Java 类映射为 Web 资源的方法,广泛用于微服务、前后端分离架构和 API 开发。
JAX-RS 在 Jakarta EE 9 中迁移到了 jakarta.ws.rs 命名空间,之前为 javax.ws.rs。
二、设计目标
JAX-RS 的设计目标包括:
- 简化 RESTful 服务开发
- 注解驱动的声明式编程模型
- 与 HTTP 方法和资源 URI 紧密集成
- 易于扩展(拦截器、过滤器、实体提供者)
- 支持内容协商(Content Negotiation)
- 规范与实现分离,支持多种实现(如 Jersey、RESTEasy、Apache CXF)
三、核心注解和说明
1. @Path
定义类或方法处理的 URI 路径。
@Path("/users")
public class UserResource {
@GET
public String listUsers() {
return "List of users";
}
}
- 支持动态路径参数
/users/{id}。 - 类上的
@Path是基础路径,方法上的路径会拼接。
2. HTTP 方法注解
这些注解表明方法响应特定的 HTTP 动作:
@GET:获取资源@POST:创建资源@PUT:更新资源@DELETE:删除资源@PATCH:部分更新@HEAD/@OPTIONS:特殊用途
@GET
@Path("/{id}")
public String getUser(@PathParam("id") int id) {
return "User with ID: " + id;
}
3. 参数注解
用于绑定 HTTP 请求数据:
| 注解 | 来源 |
|---|---|
@PathParam | URL 路径 |
@QueryParam | URL 查询参数 |
@HeaderParam | HTTP Header |
@CookieParam | Cookie 值 |
@MatrixParam | URL 矩阵参数 |
@FormParam | 表单字段 |
@Context | 注入上下文信息对象(如 UriInfo, HttpHeaders) |
@GET
@Path("/search")
public String search(@QueryParam("q") String query) {
return "Searching for: " + query;
}
4. @Produces 与 @Consumes
用于声明处理的媒体类型(MIME types)。
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public User getUser(@PathParam("id") int id) {
return userService.find(id);
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response addUser(User user) {
userService.save(user);
return Response.ok().build();
}
支持类型如:
MediaType.APPLICATION_JSONMediaType.APPLICATION_XMLMediaType.TEXT_PLAIN- 自定义类型,如
application/vnd.myapp+json
四、响应构造器 jakarta.ws.rs.core.Response
Response 是一个强类型响应类,用于定制状态码、实体、头部等。
return Response.status(Response.Status.CREATED)
.entity(user)
.header("Location", "/users/123")
.build();
常用状态码
| 状态码 | 含义 |
|---|---|
| 200 | OK |
| 201 | Created |
| 204 | No Content |
| 400 | Bad Request |
| 401 | Unauthorized |
| 404 | Not Found |
| 500 | Internal Server Error |
五、内容协商(Content Negotiation)
通过 HTTP 的 Accept 和 Content-Type 头部,服务端可以自动处理客户端请求的内容格式。
@GET
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public User getUser() {
return new User("Tom", 25);
}
客户端:
curl -H "Accept: application/xml" http://localhost:8080/api/user
六、异常处理:ExceptionMapper
可通过实现 ExceptionMapper<T> 捕获并转换异常为 HTTP 响应。
@Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
public Response toResponse(Throwable ex) {
return Response.status(500)
.entity("Error: " + ex.getMessage())
.type(MediaType.TEXT_PLAIN)
.build();
}
}
注册后自动生效。
七、请求过滤器与拦截器
1. 请求/响应过滤器(ContainerRequestFilter / ContainerResponseFilter)
用于处理请求前后逻辑,例如日志、认证、CORS。
@Provider
public class LoggingFilter implements ContainerRequestFilter {
public void filter(ContainerRequestContext ctx) {
System.out.println("Request URI: " + ctx.getUriInfo().getRequestUri());
}
}
2. 实体拦截器(ReaderInterceptor / WriterInterceptor)
拦截 HTTP 实体读写,用于加密、压缩等。
@Provider
public class GzipWriterInterceptor implements WriterInterceptor {
public void aroundWriteTo(WriterInterceptorContext ctx) throws IOException {
// wrap output stream with gzip
ctx.proceed();
}
}
八、实体提供者(MessageBodyReader / Writer)
可自定义如何序列化与反序列化 Java 对象与 HTTP 实体。
@Provider
@Consumes("application/myformat")
public class MyFormatReader implements MessageBodyReader<MyObject> {
public MyObject readFrom(...) {
// 读取并解析输入流
}
}
九、客户端 API(jakarta.ws.rs.client)
JAX-RS 提供标准 REST 客户端:
Client client = ClientBuilder.newClient();
WebTarget target = client.target("http://localhost:8080/api/users/1");
User user = target.request(MediaType.APPLICATION_JSON).get(User.class);
POST 示例:
User newUser = new User("Alice", 30);
Response response = client.target("http://localhost:8080/api/users")
.request()
.post(Entity.entity(newUser, MediaType.APPLICATION_JSON));
十、完整示例:用户资源类
@Path("/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
private static Map<Integer, User> users = new HashMap<>();
private static AtomicInteger idCounter = new AtomicInteger();
@GET
public Collection<User> getAll() {
return users.values();
}
@GET
@Path("/{id}")
public Response getUser(@PathParam("id") int id) {
User user = users.get(id);
if (user == null) return Response.status(404).build();
return Response.ok(user).build();
}
@POST
public Response createUser(User user) {
int id = idCounter.incrementAndGet();
user.setId(id);
users.put(id, user);
return Response.status(201).entity(user).build();
}
@PUT
@Path("/{id}")
public Response updateUser(@PathParam("id") int id, User updated) {
if (!users.containsKey(id)) return Response.status(404).build();
updated.setId(id);
users.put(id, updated);
return Response.ok(updated).build();
}
@DELETE
@Path("/{id}")
public Response deleteUser(@PathParam("id") int id) {
users.remove(id);
return Response.noContent().build();
}
}
对应实体类:
public class User {
private int id;
private String name;
private int age;
// Constructors, getters, setters
}
十一、与 Jakarta EE 其他技术集成
- JPA:用于持久化实体对象
- CDI:用于依赖注入与生命周期管理
- Bean Validation:用于输入校验(如
@NotNull,@Size)
示例:
@POST
public Response addUser(@Valid User user) {
em.persist(user); // EntityManager 注入的
return Response.status(201).entity(user).build();
}
十二、部署方式
1. Jakarta EE 容器
将资源类打包为 .war 部署到如 GlassFish、Payara、WildFly。
2. 使用 Jersey 独立部署
public class Main {
public static void main(String[] args) {
ResourceConfig config = new ResourceConfig().packages("com.example.api");
HttpServer server = GrizzlyHttpServerFactory.createHttpServer(
URI.create("http://localhost:8080/api/"), config);
}
}
十三、异步与响应式支持
JAX-RS 2.1 引入了完整的异步支持,通过 CompletionStage 和 Reactive Streams 实现非阻塞处理:
import jakarta.ws.rs.*;
import jakarta.ws.rs.container.*;
import java.util.concurrent.*;
@Path("/async")
public class AsyncResource {
private ExecutorService executor = Executors.newFixedThreadPool(10);
// 异步方法:释放容器线程,在自定义线程中处理
@GET
@Path("/users/{id}")
public void getUserAsync(@PathParam("id") String id,
@Suspended AsyncResponse response) {
response.setTimeout(5, TimeUnit.SECONDS);
executor.submit(() -> {
try {
User user = userService.findById(id); // 耗时操作
response.resume(Response.ok(user).build());
} catch (Exception e) {
response.resume(Response.status(500).entity(e.getMessage()).build());
}
});
}
// CompletionStage 方式(更简洁)
@GET
@Path("/orders/{id}")
public CompletionStage<Response> getOrderAsync(@PathParam("id") String id) {
return CompletableFuture.supplyAsync(() -> orderService.findById(id))
.thenApply(order -> Response.ok(order).build())
.exceptionally(ex -> Response.status(500).entity(ex.getMessage()).build());
}
// Server-Sent Events (SSE)
@GET
@Path("/events")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void streamEvents(@Context SseEventSink eventSink,
@Context Sse sse) {
executor.submit(() -> {
for (int i = 0; i < 10; i++) {
eventSink.send(sse.newEvent("message", "Event " + i));
try { Thread.sleep(1000); } catch (InterruptedException e) {}
}
eventSink.close();
});
}
}
异步处理适用场景
| 场景 | 同步处理 | 异步处理 | 推荐方案 |
|---|---|---|---|
| 简单 CRUD | 低延迟 | 增加复杂度 | 同步 |
| 外部 HTTP 调用 | 线程阻塞 | 立即释放 | 异步 |
| 数据库复杂查询 | 连接占用 | 连接池复用 | 异步 |
| 长连接推送 | 每连接一线程 | 事件驱动 | SSE/WebSocket |
十四、Bean Validation 集成
JAX-RS 与 Bean Validation 无缝集成,实现声明式参数校验:
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
public class UserDTO {
@NotNull(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度 3-50 字符")
private String username;
@NotEmpty(message = "邮箱必填")
@Email(message = "邮箱格式不正确")
private String email;
@Min(value = 18, message = "年龄必须 ≥ 18")
@Max(value = 120, message = "年龄必须 ≤ 120")
private Integer age;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}
@Path("/users")
public class UserResource {
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response createUser(@Valid UserDTO user) { // @Valid 触发校验
// 如果校验失败,自动返回 400 Bad Request
// 错误信息包含具体字段和消息
return Response.status(201).entity(userService.create(user)).build();
}
// 路径参数校验
@GET
@Path("/{id}")
public Response getUser(@PathParam("id") @Positive(message = "ID 必须为正数") Long id) {
return Response.ok(userService.findById(id)).build();
}
}
// 自定义校验注解
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = StrongPasswordValidator.class)
public @interface StrongPassword {
String message() default "密码必须包含大小写字母、数字和特殊字符";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
全局异常处理(统一返回校验错误)
@Provider
public class ValidationExceptionMapper implements ExceptionMapper<ConstraintViolationException> {
@Override
public Response toResponse(ConstraintViolationException ex) {
List<Map<String, String>> errors = new ArrayList<>();
for (ConstraintViolation<?> violation : ex.getConstraintViolations()) {
Map<String, String> error = new HashMap<>();
error.put("field", violation.getPropertyPath().toString());
error.put("message", violation.getMessage());
errors.add(error);
}
return Response.status(400)
.entity(Map.of("errors", errors, "message", "请求参数校验失败"))
.build();
}
}
十五、与 Spring 生态对比
| 特性 | JAX-RS (Jersey/RESTEasy) | Spring Web MVC |
|---|---|---|
| 规范性质 | Jakarta EE 标准 | Spring 专有 |
| 注解风格 | @Path, @GET, @POST | @RequestMapping, @GetMapping |
| 依赖注入 | CDI / Jakarta Inject | Spring IoC |
| 响应式 | CompletionStage, SSE | WebFlux (Reactor) |
| 微服务框架 | Quarkus, Micronaut | Spring Boot |
| 生态成熟度 | 标准稳定 | 社区庞大 |
| 启动速度 | Quarkus 原生编译极快 | Spring Boot 较慢 |
| 学习成本 | 中等 | 低(文档丰富) |
选型建议
- 云原生/微服务:Quarkus + JAX-RS(低内存、快启动、原生可编译)
- 企业级应用:Spring Boot(生态完善、社区支持强)
- 遗留系统迁移:从
javax升级到jakarta命名空间
十三、小结
| 特性 | 支持情况 |
|---|---|
| 注解驱动 REST API | ✅ |
| 内容协商 | ✅ |
| 参数绑定与注入 | ✅ |
| 异常处理 | ✅ |
| 请求/响应拦截器 | ✅ |
| 自定义序列化/反序列化 | ✅ |
| 客户端访问 API | ✅ |
| 与 CDI / JPA / Bean Validation 集成 | ✅ |
| 异步/响应式处理 | ✅ |
| SSE 服务端推送 | ✅ |
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。