JAX-RS规范介绍

JAX-RS (Java API for RESTful Web Services)是 Jakarta EE(原 Java EE)提供的一套用于构建 RESTful Web 服务的 API 规范。它定义了一套用于将 Java 类映射为 Web 资源的方法,广泛用于微服务、前后端分离架构和 API 开发。

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 请求数据:

注解来源
@PathParamURL 路径
@QueryParamURL 查询参数
@HeaderParamHTTP Header
@CookieParamCookie 值
@MatrixParamURL 矩阵参数
@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_JSON
  • MediaType.APPLICATION_XML
  • MediaType.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();

常用状态码

状态码含义
200OK
201Created
204No Content
400Bad Request
401Unauthorized
404Not Found
500Internal 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 InjectSpring IoC
响应式CompletionStage, SSEWebFlux (Reactor)
微服务框架Quarkus, MicronautSpring Boot
生态成熟度标准稳定社区庞大
启动速度Quarkus 原生编译极快Spring Boot 较慢
学习成本中等低(文档丰富)

选型建议

  • 云原生/微服务:Quarkus + JAX-RS(低内存、快启动、原生可编译)
  • 企业级应用:Spring Boot(生态完善、社区支持强)
  • 遗留系统迁移:从 javax 升级到 jakarta 命名空间

十三、小结

特性支持情况
注解驱动 REST API✅
内容协商✅
参数绑定与注入✅
异常处理✅
请求/响应拦截器✅
自定义序列化/反序列化✅
客户端访问 API✅
与 CDI / JPA / Bean Validation 集成✅
异步/响应式处理✅
SSE 服务端推送✅

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Spring Boot 入门》18.3 打包与运行
  2. 《Spring Boot 入门》18.2 实现
  3. 《Spring Boot 入门》18.1 需求与设计