简介
Jersey 是 Jakarta RESTful Web Services 规范的一种实现。
老名字常叫 JAX-RS,新包名是:
1jakarta.ws.rs 2
Jersey 自己的核心包名通常是:
1org.glassfish.jersey 2
简单理解:
1Jakarta REST / JAX-RS 是规范 2Jersey 是实现 3Spring Boot 提供 Jersey 自动配置和 starter 4
Jersey 的开发方式是用注解把 Java 类声明成 HTTP 资源:
1@Path("/users") 2public class UserResource { 3 4 @GET 5 @Path("/{id}") 6 public UserView getById(@PathParam("id") Long id) { 7 return userService.getById(id); 8 } 9} 10
这种写法和 Spring MVC 的 Controller 很像,只是注解来自 Jakarta REST 规范。
Jersey 适合什么场景
Java 写 REST API 常见有几种方式:
| 方式 | 说明 |
|---|---|
| Servlet | 最底层,直接处理 request / response |
| Spring MVC | Spring 生态里最常见的 Web 框架 |
| Jersey | Jakarta REST / JAX-RS 实现 |
| RESTEasy | 另一种 Jakarta REST / JAX-RS 实现 |
| Apache CXF | 支持 JAX-RS、JAX-WS 等能力 |
Jersey 适合这些场景:
- 已经在使用 JAX-RS / Jakarta REST 注解的项目
- 传统 Java EE / Jakarta EE 项目
- 需要兼容标准 REST 资源模型的服务
- Spring Boot 项目里更偏好 JAX-RS 编程模型
- 需要 Jersey Client、Filter、ExceptionMapper 等能力的项目
如果项目已经深度使用 Spring MVC,继续用 Spring MVC 通常更顺。
如果团队习惯 @Path、@GET、@Produces 这套标准注解,Jersey 会更自然。
Jersey、JAX-RS、Jakarta REST 的关系
这些名字容易混。
| 名称 | 含义 |
|---|---|
| JAX-RS | Java RESTful Web Services 老称呼 |
| Jakarta RESTful Web Services | JAX-RS 迁到 Jakarta 后的新名字 |
| Jersey | Jakarta REST / JAX-RS 的实现 |
| RESTEasy | Jakarta REST / JAX-RS 的另一种实现 |
| javax.ws.rs | 老包名,常见于 Java EE 8、Spring Boot 2 项目 |
| jakarta.ws.rs | 新包名,常见于 Jakarta EE、Spring Boot 3 项目 |
Spring Boot 3 相关项目里通常使用:
1import jakarta.ws.rs.GET; 2import jakarta.ws.rs.Path; 3import jakarta.ws.rs.Produces; 4
老项目里可能会看到:
1import javax.ws.rs.GET; 2import javax.ws.rs.Path; 3
迁移到 Spring Boot 3 或 Jakarta EE 新版本时,包名变化是主要改动之一。
Jersey 和 Spring MVC 对比
同一个用户查询接口,用 Spring MVC 可能这样写:
1@RestController 2@RequestMapping("/users") 3public class UserController { 4 5 @GetMapping("/{id}") 6 public UserView getById(@PathVariable Long id) { 7 return userService.getById(id); 8 } 9} 10
Jersey 写法:
1@Path("/users") 2public class UserResource { 3 4 @GET 5 @Path("/{id}") 6 public UserView getById(@PathParam("id") Long id) { 7 return userService.getById(id); 8 } 9} 10
对比:
| 维度 | Spring MVC | Jersey |
|---|---|---|
| 路由类注解 | @RestController、@RequestMapping | @Path |
| GET | @GetMapping | @GET |
| POST | @PostMapping | @POST |
| 路径参数 | @PathVariable | @PathParam |
| 查询参数 | @RequestParam | @QueryParam |
| 请求体 | @RequestBody | 方法参数直接接收实体 |
| 响应对象 | 直接返回对象或 ResponseEntity | 直接返回对象或 Response |
两者都能写 REST API。
区别不在“能不能写”,而在项目选择的编程模型和生态集成方式。
Spring Boot 集成 Jersey
Spring Boot 项目里使用 Jersey,直接引入 starter。
1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-jersey</artifactId> 4</dependency> 5
如果需要参数校验:
1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-validation</artifactId> 4</dependency> 5
Spring Boot 会为 Jersey 做自动配置。
还需要提供一个 ResourceConfig Bean,用来注册资源类、过滤器、异常映射器等组件。
ResourceConfig 配置
1package com.example.jersey.config; 2 3import com.example.jersey.exception.ApiExceptionMapper; 4import com.example.jersey.exception.ConstraintViolationMapper; 5import com.example.jersey.filter.RequestLogFilter; 6import com.example.jersey.resource.UserResource; 7import org.glassfish.jersey.server.ResourceConfig; 8import org.springframework.stereotype.Component; 9 10@Component 11public class JerseyConfig extends ResourceConfig { 12 13 public JerseyConfig() { 14 register(UserResource.class); 15 register(ApiExceptionMapper.class); 16 register(ConstraintViolationMapper.class); 17 register(RequestLogFilter.class); 18 } 19} 20
Spring Boot 官方文档建议在可执行 jar 场景里显式 register(...) 端点。
原因是 Jersey 对可执行 jar 的包扫描支持有限,显式注册更稳定。
如果要给 Jersey 统一加路径前缀,可以在 ResourceConfig 上加 @ApplicationPath:
1package com.example.jersey.config; 2 3import com.example.jersey.resource.UserResource; 4import jakarta.ws.rs.ApplicationPath; 5import org.glassfish.jersey.server.ResourceConfig; 6import org.springframework.stereotype.Component; 7 8@Component 9@ApplicationPath("/api") 10public class JerseyConfig extends ResourceConfig { 11 12 public JerseyConfig() { 13 register(UserResource.class); 14 } 15} 16
这样 @Path("/users") 最终访问路径就是:
1/api/users 2
第一个 Resource
1package com.example.jersey.resource; 2 3import jakarta.ws.rs.GET; 4import jakarta.ws.rs.Path; 5import jakarta.ws.rs.Produces; 6import jakarta.ws.rs.core.MediaType; 7import org.springframework.stereotype.Component; 8 9@Component 10@Path("/hello") 11public class HelloResource { 12 13 @GET 14 @Produces(MediaType.TEXT_PLAIN) 15 public String hello() { 16 return "Hello Jersey"; 17 } 18} 19
注册:
1register(HelloResource.class); 2
访问:
1GET http://localhost:8080/api/hello 2
如果 Resource 类加了 @Component,它可以交给 Spring 管理,也可以注入 Spring Bean。
用户 CRUD Demo
下面用一个用户接口串起常用注解。
请求对象:
1package com.example.jersey.user; 2 3import jakarta.validation.constraints.Email; 4import jakarta.validation.constraints.NotBlank; 5import jakarta.validation.constraints.NotNull; 6import jakarta.validation.constraints.Size; 7 8public record CreateUserRequest( 9 10 @NotBlank(message = "用户名不能为空") 11 @Size(min = 2, max = 20, message = "用户名长度需要在2到20之间") 12 String username, 13 14 @NotBlank(message = "邮箱不能为空") 15 @Email(message = "邮箱格式不正确") 16 String email, 17 18 @NotNull(message = "年龄不能为空") 19 Integer age 20) { 21} 22
响应对象:
1package com.example.jersey.user; 2 3public record UserView( 4 Long id, 5 String username, 6 String email, 7 Integer age 8) { 9} 10
业务服务:
1package com.example.jersey.user; 2 3import org.springframework.stereotype.Service; 4 5import java.util.ArrayList; 6import java.util.List; 7import java.util.Map; 8import java.util.concurrent.ConcurrentHashMap; 9import java.util.concurrent.atomic.AtomicLong; 10 11@Service 12public class UserService { 13 14 private final AtomicLong idGenerator = new AtomicLong(1000); 15 16 private final Map<Long, UserView> users = new ConcurrentHashMap<>(); 17 18 public List<UserView> findAll(String keyword) { 19 return new ArrayList<>(users.values()) 20 .stream() 21 .filter(user -> keyword == null || user.username().contains(keyword)) 22 .toList(); 23 } 24 25 public UserView findById(Long id) { 26 UserView user = users.get(id); 27 if (user == null) { 28 throw new ApiException(404, "用户不存在"); 29 } 30 return user; 31 } 32 33 public UserView create(CreateUserRequest request) { 34 Long id = idGenerator.incrementAndGet(); 35 UserView user = new UserView(id, request.username(), request.email(), request.age()); 36 users.put(id, user); 37 return user; 38 } 39 40 public void delete(Long id) { 41 users.remove(id); 42 } 43} 44
Resource:
1package com.example.jersey.resource; 2 3import com.example.jersey.user.CreateUserRequest; 4import com.example.jersey.user.UserService; 5import com.example.jersey.user.UserView; 6import jakarta.validation.Valid; 7import jakarta.ws.rs.Consumes; 8import jakarta.ws.rs.DELETE; 9import jakarta.ws.rs.GET; 10import jakarta.ws.rs.POST; 11import jakarta.ws.rs.Path; 12import jakarta.ws.rs.PathParam; 13import jakarta.ws.rs.Produces; 14import jakarta.ws.rs.QueryParam; 15import jakarta.ws.rs.core.MediaType; 16import jakarta.ws.rs.core.Response; 17import org.springframework.stereotype.Component; 18 19import java.net.URI; 20import java.util.List; 21 22@Component 23@Path("/users") 24@Produces(MediaType.APPLICATION_JSON) 25@Consumes(MediaType.APPLICATION_JSON) 26public class UserResource { 27 28 private final UserService userService; 29 30 public UserResource(UserService userService) { 31 this.userService = userService; 32 } 33 34 @GET 35 public List<UserView> list(@QueryParam("keyword") String keyword) { 36 return userService.findAll(keyword); 37 } 38 39 @GET 40 @Path("/{id}") 41 public UserView getById(@PathParam("id") Long id) { 42 return userService.findById(id); 43 } 44 45 @POST 46 public Response create(@Valid CreateUserRequest request) { 47 UserView user = userService.create(request); 48 return Response.created(URI.create("/api/users/" + user.id())) 49 .entity(user) 50 .build(); 51 } 52 53 @DELETE 54 @Path("/{id}") 55 public Response delete(@PathParam("id") Long id) { 56 userService.delete(id); 57 return Response.noContent().build(); 58 } 59} 60
接口效果:
| 请求 | 说明 |
|---|---|
| GET /api/users | 查询全部用户 |
| GET /api/users?keyword=tom | 按用户名关键字查询 |
| GET /api/users/1001 | 根据 ID 查询 |
| POST /api/users | 创建用户 |
| DELETE /api/users/1001 | 删除用户 |
POST 请求示例:
1{ 2 "username": "tom", 3 "email": "tom@example.com", 4 "age": 20 5} 6
常用注解
Jersey 主要使用 Jakarta REST 注解。
| 注解 | 作用 |
|---|---|
| @Path | 定义类或方法路径 |
| @GET | 处理 GET 请求 |
| @POST | 处理 POST 请求 |
| @PUT | 处理 PUT 请求 |
| @PATCH | 处理 PATCH 请求 |
| @DELETE | 处理 DELETE 请求 |
| @Produces | 声明响应媒体类型 |
| @Consumes | 声明请求体媒体类型 |
| @PathParam | 获取路径参数 |
| @QueryParam | 获取查询参数 |
| @HeaderParam | 获取请求头 |
| @CookieParam | 获取 Cookie |
| @FormParam | 获取表单字段 |
| @BeanParam | 把多个参数封装到一个对象 |
| @Context | 注入上下文对象 |
@Produces 和 @Consumes 可以放在类上,也可以放在方法上。
方法上的配置优先级更高。
参数绑定
路径参数:
1@GET 2@Path("/{id}") 3public UserView getById(@PathParam("id") Long id) { 4 return userService.findById(id); 5} 6
查询参数:
1@GET 2public List<UserView> list(@QueryParam("keyword") String keyword, 3 @QueryParam("page") @DefaultValue("1") Integer page) { 4 return userService.findAll(keyword); 5} 6
请求头:
1@GET 2@Path("/profile") 3public UserView profile(@HeaderParam("X-User-Id") Long userId) { 4 return userService.findById(userId); 5} 6
表单参数:
1@POST 2@Path("/login") 3@Consumes(MediaType.APPLICATION_FORM_URLENCODED) 4public Response login(@FormParam("username") String username, 5 @FormParam("password") String password) { 6 return Response.ok().build(); 7} 8
上下文对象:
1@GET 2@Path("/request-info") 3public String requestInfo(@Context jakarta.ws.rs.core.UriInfo uriInfo) { 4 return uriInfo.getRequestUri().toString(); 5} 6
BeanParam:封装查询条件
查询条件比较多时,可以用 @BeanParam。
1package com.example.jersey.user; 2 3import jakarta.ws.rs.DefaultValue; 4import jakarta.ws.rs.QueryParam; 5 6public class UserQueryParam { 7 8 @QueryParam("keyword") 9 private String keyword; 10 11 @QueryParam("page") 12 @DefaultValue("1") 13 private Integer page; 14 15 @QueryParam("size") 16 @DefaultValue("20") 17 private Integer size; 18 19 public String getKeyword() { 20 return keyword; 21 } 22 23 public Integer getPage() { 24 return page; 25 } 26 27 public Integer getSize() { 28 return size; 29 } 30} 31
Resource:
1@GET 2@Path("/search") 3public List<UserView> search(@BeanParam UserQueryParam queryParam) { 4 return userService.findAll(queryParam.getKeyword()); 5} 6
这种写法适合列表查询、分页查询、筛选条件较多的接口。
Response:控制状态码和响应头
直接返回对象时,Jersey 会自动序列化。
需要精细控制状态码、响应头时,可以返回 Response。
创建成功:
1return Response.created(URI.create("/api/users/" + user.id())) 2 .entity(user) 3 .build(); 4
无内容:
1return Response.noContent().build(); 2
自定义响应头:
1return Response.ok(user) 2 .header("X-Request-Source", "jersey") 3 .build(); 4
常见状态码:
| 场景 | 状态码 |
|---|---|
| 查询成功 | 200 OK |
| 创建成功 | 201 Created |
| 删除成功且无响应体 | 204 No Content |
| 参数错误 | 400 Bad Request |
| 未登录 | 401 Unauthorized |
| 无权限 | 403 Forbidden |
| 资源不存在 | 404 Not Found |
| 服务端异常 | 500 Internal Server Error |
异常处理:ExceptionMapper
Jersey 用 ExceptionMapper 统一处理异常。
先定义业务异常:
1package com.example.jersey.user; 2 3public class ApiException extends RuntimeException { 4 5 private final int status; 6 7 public ApiException(int status, String message) { 8 super(message); 9 this.status = status; 10 } 11 12 public int getStatus() { 13 return status; 14 } 15} 16
错误响应:
1package com.example.jersey.exception; 2 3public record ApiErrorResponse( 4 String code, 5 String message 6) { 7} 8
异常映射器:
1package com.example.jersey.exception; 2 3import com.example.jersey.user.ApiException; 4import jakarta.ws.rs.ext.ExceptionMapper; 5import jakarta.ws.rs.ext.Provider; 6import jakarta.ws.rs.core.MediaType; 7import jakarta.ws.rs.core.Response; 8 9@Provider 10public class ApiExceptionMapper implements ExceptionMapper<ApiException> { 11 12 @Override 13 public Response toResponse(ApiException exception) { 14 ApiErrorResponse body = new ApiErrorResponse( 15 "BUSINESS_ERROR", 16 exception.getMessage() 17 ); 18 19 return Response.status(exception.getStatus()) 20 .type(MediaType.APPLICATION_JSON) 21 .entity(body) 22 .build(); 23 } 24} 25
注册:
1register(ApiExceptionMapper.class); 2
这样 Resource 里可以直接抛业务异常,统一由 Mapper 转成 JSON 响应。
参数校验
Jersey 可以结合 Jakarta Validation。
请求对象上写约束:
1public record CreateUserRequest( 2 3 @NotBlank(message = "用户名不能为空") 4 String username, 5 6 @Email(message = "邮箱格式不正确") 7 String email 8) { 9} 10
Resource 参数上加 @Valid:
1@POST 2public Response create(@Valid CreateUserRequest request) { 3 UserView user = userService.create(request); 4 return Response.ok(user).build(); 5} 6
校验异常也可以通过 ExceptionMapper 统一处理。
1package com.example.jersey.exception; 2 3import jakarta.validation.ConstraintViolation; 4import jakarta.validation.ConstraintViolationException; 5import jakarta.ws.rs.core.Response; 6import jakarta.ws.rs.ext.ExceptionMapper; 7import jakarta.ws.rs.ext.Provider; 8 9import java.util.List; 10 11@Provider 12public class ConstraintViolationMapper implements ExceptionMapper<ConstraintViolationException> { 13 14 @Override 15 public Response toResponse(ConstraintViolationException exception) { 16 List<String> errors = exception.getConstraintViolations() 17 .stream() 18 .map(ConstraintViolation::getMessage) 19 .toList(); 20 21 return Response.status(Response.Status.BAD_REQUEST) 22 .entity(errors) 23 .build(); 24 } 25} 26
Filter:请求日志和鉴权入口
Jersey 里常用过滤器处理请求日志、鉴权、TraceId 等逻辑。
1package com.example.jersey.filter; 2 3import jakarta.ws.rs.container.ContainerRequestContext; 4import jakarta.ws.rs.container.ContainerRequestFilter; 5import jakarta.ws.rs.ext.Provider; 6 7import java.io.IOException; 8 9@Provider 10public class RequestLogFilter implements ContainerRequestFilter { 11 12 @Override 13 public void filter(ContainerRequestContext requestContext) throws IOException { 14 System.out.printf( 15 "Jersey 请求:%s %s%n", 16 requestContext.getMethod(), 17 requestContext.getUriInfo().getPath() 18 ); 19 } 20} 21
注册:
1register(RequestLogFilter.class); 2
简单鉴权示例:
1package com.example.jersey.filter; 2 3import jakarta.ws.rs.container.ContainerRequestContext; 4import jakarta.ws.rs.container.ContainerRequestFilter; 5import jakarta.ws.rs.core.Response; 6import jakarta.ws.rs.ext.Provider; 7 8import java.io.IOException; 9 10@Provider 11public class TokenAuthFilter implements ContainerRequestFilter { 12 13 @Override 14 public void filter(ContainerRequestContext requestContext) throws IOException { 15 String token = requestContext.getHeaderString("Authorization"); 16 if (token == null || token.isBlank()) { 17 requestContext.abortWith( 18 Response.status(Response.Status.UNAUTHORIZED) 19 .entity("未登录") 20 .build() 21 ); 22 } 23 } 24} 25
复杂安全逻辑更适合交给 Spring Security 或网关统一处理。
Jersey Client
Jersey 不只可以写服务端,也提供 Client API 调用 HTTP 服务。
Maven 依赖:
1<dependency> 2 <groupId>org.glassfish.jersey.core</groupId> 3 <artifactId>jersey-client</artifactId> 4 <version>${jersey.version}</version> 5</dependency> 6 7<dependency> 8 <groupId>org.glassfish.jersey.media</groupId> 9 <artifactId>jersey-media-json-jackson</artifactId> 10 <version>${jersey.version}</version> 11</dependency> 12
Client 示例:
1package com.example.jersey.client; 2 3import jakarta.ws.rs.client.Client; 4import jakarta.ws.rs.client.ClientBuilder; 5import jakarta.ws.rs.client.Entity; 6import jakarta.ws.rs.core.MediaType; 7import jakarta.ws.rs.core.Response; 8 9public class JerseyClientDemo { 10 11 public static void main(String[] args) { 12 Client client = ClientBuilder.newClient(); 13 14 CreateUserRequest request = new CreateUserRequest( 15 "tom", 16 "tom@example.com", 17 20 18 ); 19 20 Response response = client.target("http://localhost:8080/api") 21 .path("/users") 22 .request(MediaType.APPLICATION_JSON) 23 .post(Entity.entity(request, MediaType.APPLICATION_JSON)); 24 25 System.out.println(response.getStatus()); 26 System.out.println(response.readEntity(String.class)); 27 28 response.close(); 29 client.close(); 30 } 31} 32
Spring 项目里也可以使用 RestClient、WebClient、OpenFeign 等客户端。
Jersey Client 更适合已经在 JAX-RS/Jersey 体系里的项目。
独立运行:Grizzly Demo
Jersey 也可以不依赖 Spring Boot,用 Grizzly 启动一个轻量 HTTP 服务。
依赖:
1<dependency> 2 <groupId>org.glassfish.jersey.containers</groupId> 3 <artifactId>jersey-container-grizzly2-http</artifactId> 4 <version>${jersey.version}</version> 5</dependency> 6 7<dependency> 8 <groupId>org.glassfish.jersey.inject</groupId> 9 <artifactId>jersey-hk2</artifactId> 10 <version>${jersey.version}</version> 11</dependency> 12 13<dependency> 14 <groupId>org.glassfish.jersey.media</groupId> 15 <artifactId>jersey-media-json-jackson</artifactId> 16 <version>${jersey.version}</version> 17</dependency> 18
启动类:
1package com.example.jersey.standalone; 2 3import org.glassfish.grizzly.http.server.HttpServer; 4import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; 5import org.glassfish.jersey.server.ResourceConfig; 6 7import java.io.IOException; 8import java.net.URI; 9 10public class JerseyStandaloneApplication { 11 12 public static void main(String[] args) throws IOException { 13 ResourceConfig config = new ResourceConfig() 14 .register(HelloResource.class); 15 16 HttpServer server = GrizzlyHttpServerFactory.createHttpServer( 17 URI.create("http://localhost:8080/api/"), 18 config 19 ); 20 21 System.out.println("Jersey Grizzly 服务已启动"); 22 System.in.read(); 23 server.shutdownNow(); 24 } 25} 26
这种方式适合 Demo、测试工具、轻量 HTTP 服务。
正式业务系统更常见的方式还是 Spring Boot 或应用服务器部署。
和 Spring MVC 共存
Spring Boot 项目里,Jersey 和 Spring MVC 可以共存,但需要明确请求由谁处理。
一种常见方式是让 Jersey 走 Filter 模式,并把 404 转发给其他 Web 框架。
application.yml:
1spring: 2 jersey: 3 type: filter 4
ResourceConfig:
1package com.example.jersey.config; 2 3import org.glassfish.jersey.server.ResourceConfig; 4import org.glassfish.jersey.servlet.ServletProperties; 5import org.springframework.stereotype.Component; 6 7@Component 8public class JerseyConfig extends ResourceConfig { 9 10 public JerseyConfig() { 11 register(UserResource.class); 12 property(ServletProperties.FILTER_FORWARD_ON_404, true); 13 } 14} 15
这样 Jersey 没匹配到的请求,可以继续交给 Spring MVC。
如果项目没有共存需求,Jersey 独立处理接口会更简单。
Spring Security 集成注意点
Jersey 和 Spring Security 结合时,如果使用方法级安全,Spring Boot 官方文档建议设置:
1setProperties(Map.of( 2 "jersey.config.server.response.setStatusOverSendError", 3 true 4)); 5
完整示例:
1import com.example.jersey.resource.UserResource; 2import org.glassfish.jersey.server.ResourceConfig; 3import org.springframework.stereotype.Component; 4 5import java.util.Map; 6 7@Component 8public class JerseyConfig extends ResourceConfig { 9 10 public JerseyConfig() { 11 register(UserResource.class); 12 setProperties(Map.of( 13 "jersey.config.server.response.setStatusOverSendError", 14 true 15 )); 16 } 17} 18
这个配置可以避免 Jersey 过早提交错误响应,让 Spring Security 有机会正确处理认证和授权失败。
常见问题
Resource 没有生效
常见原因:
| 原因 | 处理方式 |
|---|---|
| 没有注册 Resource | 在 ResourceConfig 里 register(...) |
| 只使用 packages(...) 扫描 | 可执行 jar 场景优先显式注册 |
| 缺少 @Component | 需要 Spring 管理时加 @Component |
| 路径前缀理解错误 | 检查 @ApplicationPath 和 @Path |
JSON 不能正常序列化
常见原因是缺少 Jackson 适配。
Spring Boot starter 通常会处理常见 JSON 场景。
独立 Jersey 项目可以加入:
1<dependency> 2 <groupId>org.glassfish.jersey.media</groupId> 3 <artifactId>jersey-media-json-jackson</artifactId> 4 <version>${jersey.version}</version> 5</dependency> 6
@RequestBody 为什么不能用
@RequestBody 是 Spring MVC 注解。
Jersey 使用 JAX-RS 模型,请求体通常直接放在方法参数里:
1@POST 2public Response create(CreateUserRequest request) { 3 return Response.ok().build(); 4} 5
路径参数使用 @PathParam,查询参数使用 @QueryParam。
javax.ws.rs 和 jakarta.ws.rs 能不能混用
不适合混用。
Spring Boot 2、老 Java EE 项目常见 javax.ws.rs。
Spring Boot 3、Jakarta EE 新项目常见 jakarta.ws.rs。
同一个项目里混用两套包名,容易出现注解不识别、类加载冲突、依赖版本不匹配等问题。
实践建议
| 场景 | 建议 |
|---|---|
| Spring Boot 3 项目 | 使用 spring-boot-starter-jersey |
| Resource 注册 | 优先在 ResourceConfig 显式 register |
| 路径前缀 | 使用 @ApplicationPath("/api") |
| JSON 接口 | 类上统一 @Produces、@Consumes |
| 业务异常 | 使用 ExceptionMapper |
| 请求日志 | 使用 ContainerRequestFilter |
| 参数校验 | 结合 Jakarta Validation |
| 和 Spring MVC 共存 | 使用 filter 模式和 404 转发 |
| 新项目包名 | 使用 jakarta.ws.rs |
小结
Jersey 的核心是 JAX-RS / Jakarta REST 编程模型。
@Path 定义资源路径,@GET、@POST 等注解定义 HTTP 方法,@Produces 和 @Consumes 控制媒体类型,@PathParam、@QueryParam 等注解完成参数绑定。
Spring Boot 项目里,spring-boot-starter-jersey 加 ResourceConfig 就能把 Jersey 接入应用。业务接口用 Resource 编写,异常交给 ExceptionMapper,横切逻辑交给 Filter,外部 HTTP 调用可以用 Jersey Client。
如果项目需要标准 JAX-RS 风格的 REST API,Jersey 是一套成熟、清晰、可落地的选择。