Java Jersey 实战指南:用 JAX-RS 注解写清晰的 REST API

作者:唐青枫日期:2026/7/24

简介

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 MVCSpring 生态里最常见的 Web 框架
JerseyJakarta 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-RSJava RESTful Web Services 老称呼
Jakarta RESTful Web ServicesJAX-RS 迁到 Jakarta 后的新名字
JerseyJakarta REST / JAX-RS 的实现
RESTEasyJakarta 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 MVCJersey
路由类注解@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 项目里也可以使用 RestClientWebClient、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-jerseyResourceConfig 就能把 Jersey 接入应用。业务接口用 Resource 编写,异常交给 ExceptionMapper,横切逻辑交给 Filter,外部 HTTP 调用可以用 Jersey Client。

如果项目需要标准 JAX-RS 风格的 REST API,Jersey 是一套成熟、清晰、可落地的选择。


Java Jersey 实战指南:用 JAX-RS 注解写清晰的 REST API》 是转载文章,点击查看原文


相关推荐


数据结构之双链表
无忧.芙桃2026/7/16

本篇目标: 1. 学会关于双链表的相关操作 2. 了解链表和顺序表的区别和优点 一、双链表接口实现 1. 双向链表的结构 • 单链表的结点中保存了指向后继结点的地址,所以单链表中找当前结点的后继结点很容易,但要获取当前结点的前驱结点就很麻烦,就只能从头开始往后遍历获取,时间复杂度为 O(n);所以单链表中只有当前结点的指针 pos 时(没有头指针),想要在 pos 之前插入结点和删除 pos 位置结点都是无法实现的。 • 双向链表相比单链表最大的特征是每个结点中多了一个前驱指针,


Android 面试系列:Kotlin 协程的 delay 到底发生在哪个线程?
潜龙勿用之化骨龙2026/7/8

在开发中,我们经常写出这样的代码: mainScope.launch { log("start") delay(1000) log("end") } 这段代码看起来非常简单,但它隐藏了一个非常经典的问题: delay 这 1 秒到底发生在哪个线程? 主线程前后都在执行,那中间谁在“等时间”? 结论 delay 从来不占用线程等待,它是一次“挂起 + 时间注册 + 调度恢复 + 状态机推进”的过程。 中间没有任何业务线程在 sleep,也没有线程在阻塞计时。


别再只会用 cron:Linux systemd Timer 定时任务实战详解
唐青枫2026/6/30

简介 Linux 上提到定时任务,最先想到的通常是 cron。 cron 足够简单,也足够稳定,但任务一旦涉及日志、启动依赖、超时控制、错过后补跑、运行用户和资源限制,单独一行 crontab 很快就会变得难以维护。 systemd Timer 提供了另一套方案: .timer 负责决定什么时候执行 .service 负责决定执行什么、以什么方式执行 例如,每天凌晨备份一次应用数据,可以拆成两个单元: myapp-backup.timer | | 到达触发时间


火山 DTS 正式支持 MySQL 同步到 Milvus , 解决业务库到向量库最后一公里
火山引擎Agent社区2026/6/21

这两年,大模型、智能问答越来越多地落到实际业务里。很多企业在推进过程中慢慢发现,影响 AI 应用落地效率的,除了模型本身能力之外,数据链路是否能顺畅跑通,也同样非常关键。 目前,企业大部分的业务数据库依然在关系型数据库中,而AI应用对支撑语义检索、相似召回的向量数据库有着更强的依赖。怎么把结构化业务数据稳定、持续地同步到向量数据库,正在成为不少企业建设 AI 数据底座时绕不开的问题。 现在,火山引擎 DTS 正式支持 MySQL 同步到 Milvus,帮助企业快速打通从业务数据库到向量数据库的数


计算机网络基础:在 P2P 对等方中搜索对象
梁辰兴2026/6/13

📌目录 ⚖️ 在P2P对等方中搜索对象:去中心化网络的信息发现机制🎯 一、P2P搜索问题概述:去中心化带来的挑战(一)搜索问题的本质(二)搜索算法设计目标(三)搜索算法的分类体系 📦 二、无结构P2P网络中的搜索机制(一)泛洪查询机制(二)随机漫步搜索(三)迭代加深搜索(四)Gossip协议搜索(五)向量时钟与语义搜索 🌐 三、分布式哈希表:结构化搜索的突破(一)DHT的基本原理(二)Chord算法详解(三)CAN算法详解(四)Kademlia算法详解(五)Pastry与T


真正值钱的 AI 小工具,可能只是帮人少打一遍字
深海恶霸Grace2026/6/6

我有个朋友是做财务兼采购的。 他最近有个特别烦的工作: 整理各种报价单信息。 有时候是供应商发来的截图。 有时候是一张图片。 有时候干脆就是一段文字描述。 最后这些东西都要被他重新整理进 Excel。 项目名称、规格、数量、单位、单价、总价。 听起来不难。 但真正做起来,很折磨。 因为这不是一道复杂题。 这是重复劳动。 你得盯着图片看一眼,再切到 Excel 里打一条。 再回来看一眼,再打一条。 遇到数字多一点、截图糊一点、格式乱一点的时候,眼睛真的会看花。 最烦的是,录完之后还不能放心。 因为


Sqoop 安装完整教程(基于 WSL2 + Ubuntu 24.04)
穆金秋2026/5/30

本教程详细介绍了在WSL2+Ubuntu24.04环境下安装配置Sqoop1.4.7的完整流程: 环境准备 Java8+、Hadoop3.3.6、MySQL8.0.45已安装验证命令:java -version/hadoop version/mysql --version 安装步骤 下载Sqoop1.4.7并解压到/usr/local配置环境变量(SQOOP_HOME和PATH)安装MySQL JDBC驱动到Sqoop/lib目录解决依赖问题(commons-lang等jar包)


Gogs: 打造属于你自己的轻量级 Git 服务
修己xj2026/5/8

在软件开发的世界里,Git 已经成为版本控制的事实标准。GitHub、GitLab 等平台提供了强大的托管服务,但有时候,我们需要一个完全属于自己的私有 Git 仓库——可能是为了代码安全,可能是为了定制化需求,可能是为了集成到现有服务中,也可能只是想在自己的服务器上搭建一个个人代码库。开源gitlab有点重,最近我在GitHub上发现了一个轻量级项目Gogs。 什么是 Gogs? Gogs 是一个用 Go 语言编写的自助 Git 托管服务。它的目标是以最简单、最轻松的方式搭建一个简单、稳定且


【系统架构师案例题-知识点】数据库与缓存设计
roman_日积跬步-终至千里2026/4/28

本文聚焦系统架构师案例题中的数据库与缓存设计,重点说明关系型数据库设计、NoSQL 选型、分库分表、读写分离、缓存策略、缓存故障模式以及缓存与数据库一致性问题,并结合电商、支付、内容平台、搜索系统、推荐系统等真实软件行业场景说明这些技术为什么会出现、各自解决什么问题、工程上该如何取舍。 阅读时可以按三个层次把握:先理解数据为什么会成为瓶颈,再理解数据库和缓存分别解决哪一类问题,最后把题干中的业务信号翻译成卷面表达。 一、先建立整体认识 数据库与缓存设计的核心,不是“会不会背名词”,而是看清系统到


Visual Studio 与 Visual Studio Code 区别
日更嵌入式的打工靓仔2026/4/20

特性Visual Studio (VS)Visual Studio Code (VS Code)本质类型集成开发环境 (IDE)轻量级源代码编辑器核心定位大型、复杂的项目开发(Windows、游戏、企业级应用)快速编辑、脚本编写、Web/云开发主要平台Windows、macOS (功能有差异)Windows、macOS、Linux占用空间大 (安装需要几GB到几十GB空间)小 (安装包约100MB以下)性能/速度启动和加载大型项目较慢启动迅速,打开文件极快价格社区版免费;专业版/企业版付费完全免

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读