碍于面子,偷偷学了 JAX-RS 规范

最近入职新公司,发现使用的 JAX-RS(Java API for RESTful Web Service) 作为 web 开发,以前都习惯了 SpringMVC 那一套,不好意思问同事,只能偷偷学。

JAX-RS 介绍

是什么

JAX-RS(Java API for RESTful Web Services)是Java EE中用于开发 RESTful Web 服务的一个API 规范。注意它不是具体的实现,常见的具体实现有:

  • Jersey,由 Sun 提供的 JAX-RS 的参考实现
  • RESTEasy, JBoss 的实现
  • ApacheCXF,apache 开源的 Web 服务框架
  • Restlet,是最早的 REST 框架,先于 JAX-RS 出现
  • Apache Wink,Apache 软件基金会孵化器中的项目

核心概念

JAX-RS 的核心概念是资源,即面向资源的服务。每个资源都有唯一的 URI 标识,并支持标准的 HTTP 方法(如GET、POST、PUT、DELETE等)

在 RESTful Web Services 中,资源可以是任何东西,如文本文件、图像、视频等。

实现案例

先快速搭建一个案例一下体验 restful gitee.com/uzongn/rest...

注意:右边的窗口是通过 Apifox 插件自动生成的。

使用的是 RESTEasy 实现类,相关的几个核心依赖。(也可以使用 Jersey 实现)

xml 复制代码
<dependencies>
  <!-- Spring Boot Web Starter -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!-- JAX-RS API -->
  <dependency>
    <groupId>javax.ws.rs</groupId>
    <artifactId>javax.ws.rs-api</artifactId>
    <version>2.1</version>
  </dependency>
  <!-- RESTEasy Spring Boot Starter -->
  <dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-spring-boot-starter</artifactId>
    <version>3.3.2.Final</version>
    <scope>runtime</scope>
  </dependency>
  <!-- Lombok for reducing boilerplate code -->
  <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
  </dependency>
</dependencies>

通过一个小案例,快速体验 JAX-RS 用法。

入门教程

上面的代码只是完成了尝鲜,真正要能够熟悉使用还要继续深入。试想一下,springMVC 有的能力,jar-rs api 也应该要能够实现。那么顺着这个思路,我们先把基本的能力梳理一遍。

基础注解

注解对比

JAX-RS 注解 Spring MVC 注解 功能说明 差异点
@Path @RequestMapping 指定资源的URI路径。 @Path用于JAX-RS资源类或方法上,定义资源的基础路径;@RequestMapping用于Spring MVC的控制器类或方法上,可以指定请求路径、方法等。
@GET @GetMapping 将方法映射到HTTP GET请求。 @GET是JAX-RS中用于指定资源方法响应的HTTP GET请求的注解;@GetMapping是Spring MVC中用于同样目的的注解,是@RequestMapping的特化。
@POST @PostMapping 将方法映射到HTTP POST请求。 同上,@POST@PostMapping都用于映射POST请求,但属于不同框架。
@PUT @PutMapping 将方法映射到HTTP PUT请求。 同上,@PUT@PutMapping都用于映射PUT请求。
@DELETE @DeleteMapping 将方法映射到HTTP DELETE请求。 同上,@DELETE@DeleteMapping都用于映射DELETE请求。
@Produces @ResponseBody 指定方法响应体的媒体类型。 @Produces用于JAX-RS中指定可以产生的媒体类型;@ResponseBody用于Spring MVC中表示方法返回值作为响应体,不进行视图解析。
@Consumes @RequestBody 指定方法接受的请求体媒体类型。 @Consumes用于JAX-RS中指定可以消费的媒体类型;@RequestBody用于Spring MVC中将请求体绑定到Controller方法的参数上。
@QueryParam @RequestParam 用于获取查询参数。 @QueryParam用于JAX-RS中获取URL查询参数;@RequestParam用于Spring MVC中同样的目的。
@PathParam @PathVariable 用于获取路径参数。 @PathParam用于JAX-RS中获取URL模板中的参数;@PathVariable用于Spring MVC中同样的目的。
@HeaderParam @RequestHeader 用于获取请求头参数。 @HeaderParam用于JAX-RS中获取HTTP请求头参数;@RequestHeader用于Spring MVC中同样的目的。
@MatrixParam 无对应 用于获取矩阵参数。 @MatrixParam是JAX-RS特有的,用于获取类似;name=value的矩阵参数,Spring MVC中没有直接对应的注解。例如:/resource;pageSize=10;currentPage=2
@Context 无对应 提供对请求上下文的访问。 @Context注解用于JAX-RS中注入各种上下文对象,如UriInfo,Spring MVC中没有直接对应的注解。
@DefaultValue 无对应 为请求参数定义默认值。 @DefaultValue注解用于JAX-RS中为请求参数定义默认值,当请求中缺少相应的元数据时使用默认值,Spring MVC中没有直接对应的注解。

简单对比,能力基本一致,这下心里有底气多了。(知识迁移)

使用案例说明

类级别注解

@Path

@Path注解用于指定资源的基础URI路径。可以在类级别和方法级别使用。

kotlin 复制代码
@Path("/api/users")
public class UserResource {
    // 类的其余部分
}

在这个例子中,所有的用户相关操作都以 /api/users 作为基础路径。

@Produces

指定资源方法返回的MIME类型。

kotlin 复制代码
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {
    // 表示这个资源类的所有方法都返回JSON格式的数据
}
@Consumes

指定资源方法可以接受的MIME类型。

kotlin 复制代码
@Consumes(MediaType.APPLICATION_JSON)
public class UserResource {
    // 表示这个资源类的所有方法都接受JSON格式的数据
}

方法级别注解

@GET - 查询操作
less 复制代码
@GET
@Path("/{id}")
public Response getUser(@PathParam("id") Long id) {
    User user = userService.getUser(id);
    if (user != null) {
        return Response.ok(user).build();
    }
    return Response.status(Response.Status.NOT_FOUND).build();
}
@POST - 创建操作
scss 复制代码
@POST
public Response createUser(User user) {
    User createdUser = userService.createUser(user);
    return Response.status(Response.Status.CREATED)
            .entity(createdUser)
            .build();
}
@PUT - 更新操作
less 复制代码
@PUT
@Path("/{id}")
public Response updateUser(@PathParam("id") Long id, User user) {
    User updatedUser = userService.updateUser(id, user);
    if (updatedUser != null) {
        return Response.ok(updatedUser).build();
    }
    return Response.status(Response.Status.NOT_FOUND).build();
}
@DELETE - 删除操作
less 复制代码
@DELETE
@Path("/{id}")
public Response deleteUser(@PathParam("id") Long id) {
    if (userService.deleteUser(id)) {
        return Response.noContent().build();
    }
    return Response.status(Response.Status.NOT_FOUND).build();
}

参数注解

@PathParam

用于从URI路径中提取参数。

less 复制代码
@GET
@Path("/{id}")
public Response getUser(@PathParam("id") Long id) {
    // id 参数会从URI路径中提取
}

当然也可以对多个路径参数进行绑定

less 复制代码
/**
 * 对多个路径参数进行绑定
 * @param id
 * @param month
 */
@GET
@Path("{id}/summary/{month}")
public String pathParam2(@PathParam("id") Long id,@PathParam("month")int month) {
    System.out.println(id);
    System.out.println(month);
    return "success";
}
@QueryParam

用于获取查询字符串参数。

less 复制代码
@GET
public Response searchUsers(@QueryParam("name") String name) {
    // 可以通过 /api/users?name=John 这样的URL访问
}
@FormParam

用于获取表单参数。(强调一下是表单内容的参数绑定)

less 复制代码
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response createUserFromForm(
    @FormParam("name") String name,
    @FormParam("email") String email) {
    // 处理表单提交的数据
}

注意表单编码格式:x-www-form-urlencoded

@DefaultValue

默认参数

less 复制代码
@PUT
@Path("/form")
@Produces(MediaType.APPLICATION_JSON)
public Employee formParam(@FormParam("name") String name, 
                          @DefaultValue("18") @FormParam("age") int age) {
    return new Employee(1L, name, age);
}
@HeaderParam
less 复制代码
@GET
public Response getUsers(
    @HeaderParam("User-Agent") String userAgent,
    @HeaderParam("Authorization") String auth) {
    // 从请求头中获取参数
}
@CookieParam
less 复制代码
@GET
public Response getUserPreferences(
    @CookieParam("sessionId") String sessionId,
    @CookieParam("theme") String theme) {
    // 从Cookie中获取参数
}

掌握上面这些注解,进行 CRUD 开发应该不难了,再看看响应的处理。

响应处理

Response类

JAX-RS提供了Response类来构建HTTP响应:

scss 复制代码
// 成功响应 (200 OK)
return Response.ok(user).build();

// 创建成功响应 (201 Created)
return Response.status(Response.Status.CREATED)
        .entity(createdUser)
        .build();

// 没有内容响应 (204 No Content)
return Response.noContent().build();

// 错误响应 (404 Not Found)
return Response.status(Response.Status.NOT_FOUND).build();

上面只是简单的参数,复杂参数的处理又有哪些方式呢

复杂对象绑定

首先创建一个用户对象:

arduino 复制代码
public class User {
    private String name;
    private String email;
    private int age;
    private Address address;  // 嵌套对象

    // getters and setters
}

public class Address {
    private String street;
    private String city;
    private String country;

    // getters and setters
}
JSON请求体绑定
less 复制代码
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response createUser(User user) {
    // JAX-RS会自动将JSON请求体转换为User对象
    // 示例JSON:
    // {
    //   "name": "John Doe",
    //   "email": "john@example.com",
    //   "age": 30,
    //   "address": {
    //     "street": "123 Main St",
    //     "city": "Boston",
    //     "country": "USA"
    //   }
    // }
}
BeanParam绑定

创建一个参数包装类:@QueryParam标签移动到了 UserSearchParams 的对应属性上

kotlin 复制代码
public class UserSearchParams {
    @QueryParam("name")
    private String name;

    @QueryParam("minAge")
    private Integer minAge;

    @QueryParam("maxAge")
    private Integer maxAge;

    @HeaderParam("X-API-Key")
    private String apiKey;

    // getters and setters
}

在资源类中使用:

less 复制代码
@GET
@Path("/search")
public Response searchUsers(@BeanParam UserSearchParams params) {
    // 所有参数都会被自动绑定到UserSearchParams对象中
    String name = params.getName();
    Integer minAge = params.getMinAge();
    // ...
}
自定义参数转换器

对于复杂的参数转换,可以实现ParamConverter接口:

typescript 复制代码
public class DateParamConverter implements ParamConverter<LocalDate> {
    private static final DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;

    @Override
    public LocalDate fromString(String value) {
        if (value == null) return null;
        return LocalDate.parse(value, formatter);
    }

    @Override
    public String toString(LocalDate value) {
        if (value == null) return null;
        return formatter.format(value);
    }
}

@Provider
public class DateParamConverterProvider implements ParamConverterProvider {
    @Override
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
        if (rawType.equals(LocalDate.class)) {
            return (ParamConverter<T>) new DateParamConverter();
        }
        return null;
    }
}

使用自定义转换器:

less 复制代码
@GET
@Path("/users")
public Response getUsersByBirthDate(@QueryParam("birthDate") LocalDate birthDate) {
    // 访问 /users?birthDate=2024-12-14
    // birthDate 将被自动转换为 LocalDate 对象
}

其他

@MatrixParam

@MatrixParam 是 JAX-RS 提供的一个参数注解,用于提取 URI 路径中的矩阵参数。矩阵参数是 URI 路径的一种特殊形式,使用分号(;)来分隔参数,格式为:path;param1=value1;param2=value2。

多矩阵参数

less 复制代码
@GET
@Path("/products")
public Response getProducts(
    @MatrixParam("category") List<String> categories,
    @MatrixParam("tag") List<String> tags) {
    // 访问 URL: /products;category=electronics;category=computers;tag=new;tag=sale
    // categories = ["electronics", "computers"]
    // tags = ["new", "sale"]
    
    return Response.ok()
        .entity("Categories: " + categories + ", Tags: " + tags)
        .build();
}

@MatrixParam 提供了一种组织复杂参数的优雅方式,适合层次化的参数结构。

这些基本的知识有了之后,再深入看看进阶知识。

进阶教程

本节主要围绕一些核心 API 讲解,知道一些高级的用法和场景解决方案。

UriInfo

UriInfo 是 JAX-RS 提供的一个强大接口,用于获取请求 URI 的各种信息。它提供了访问和操作 URI 组件的丰富功能集。

less 复制代码
/**
 * 演示获取基本 URI 信息
 * 展示如何获取请求的各种 URI 组件
 *
 * 示例请求:GET http://localhost:8080/api/demo/uri-info
 * 
 * @param uriInfo 注入的 UriInfo 对象
 * @return 包含各种 URI 信息的响应
 */
@GET
@Path("/uri-info")
public Response getUriInfo(@Context UriInfo uriInfo) {
    // 获取完整的请求 URI(包含查询参数)
    // 例如:http://localhost:8080/api/demo/uri-info?param=value
    URI requestUri = uriInfo.getRequestUri();

    // 获取基础 URI(应用的根路径)
    // 例如:http://localhost:8080/api/
    URI baseUri = uriInfo.getBaseUri();

    // 获取绝对路径(不包含查询参数)
    // 例如:http://localhost:8080/api/demo/uri-info
    URI absolutePath = uriInfo.getAbsolutePath();

    // 获取相对路径(相对于应用根路径的路径)
    // 例如:demo/uri-info
    String path = uriInfo.getPath();

    Map<String, Object> result = new HashMap<>();
    result.put("requestUri", requestUri.toString());
    result.put("baseUri", baseUri.toString());
    result.put("absolutePath", absolutePath.toString());
    result.put("path", path);

    return Response.ok(result).build();
}

获取 queryParam

less 复制代码
/**
 * 演示查询参数处理
 * 展示如何获取和处理 URL 查询参数
 *
 * 示例请求:GET http://localhost:8080/api/demo/query?name=John&age=30&tags=java&tags=rest
 * 
 * @param uriInfo 注入的 UriInfo 对象
 * @return 处理后的查询参数
 */
@GET
@Path("/query")
public Response handleQueryParams(@Context UriInfo uriInfo) {
    // 获取所有查询参数(自动解码)
    MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();

    // 获取原始查询参数(不解码)
    MultivaluedMap<String, String> rawParams = uriInfo.getQueryParameters(false);

    // 获取特定参数的第一个值
    String name = queryParams.getFirst("name");

    // 获取多值参数
    List<String> tags = queryParams.get("tags");

    Map<String, Object> result = new HashMap<>();
    result.put("decodedParams", queryParams);
    result.put("rawParams", rawParams);
    result.put("name", name);
    result.put("tags", tags);

    return Response.ok(result).build();
}

HttpHeaders

less 复制代码
/**
 * 演示 HttpHeaders 的使用
 * 展示如何获取和处理 HTTP 请求头信息
 *
 * 示例请求:
 * GET http://localhost:8080/api/context/headers
 * Headers:
 * Accept: application/json
 * User-Agent: Mozilla/5.0
 * Custom-Header: test
 */
@GET
@Path("/headers")
public Response handleHeaders(@Context HttpHeaders headers) {
    Map<String, Object> result = new HashMap<>();

    // 获取所有请求头
    MultivaluedMap<String, String> requestHeaders = headers.getRequestHeaders();
    result.put("allHeaders", requestHeaders);

    // 获取特定请求头
    String userAgent = headers.getHeaderString("User-Agent");
    result.put("userAgent", userAgent);

    // 获取Accept头信息
    List<MediaType> acceptableMediaTypes = headers.getAcceptableMediaTypes();
    result.put("acceptableMediaTypes", acceptableMediaTypes);

    // 获取Content-Type
    MediaType contentType = headers.getMediaType();
    result.put("contentType", contentType != null ? contentType.toString() : null);

    // 获取语言偏好
    List<Locale> acceptableLanguages = headers.getAcceptableLanguages();
    result.put("acceptableLanguages", acceptableLanguages);

    return Response.ok(result).build();
}

设置新的 cookies 信息

java 复制代码
// 设置新的 Cookie
NewCookie newCookie = new NewCookie("lastVisit", 
                                  new Date().toString(),
                                  "/",
                                  null,
                                  1, // version
                                  "Visit timestamp", // comment
                                  3600, // max age in seconds
                                  false); // secure

获取Servlet相关对象

获取Servlet相关对象,比如 HttpServletRequest,HttpServletResponse. @Context注解给我们提供了这个能力

less 复制代码
/**
 * 演示 Servlet 相关功能
 * 展示如何使用 HttpServletRequest 和 HttpSession
 *
 * 示例请求:
 * GET http://localhost:8080/api/context/servlet
 */
@GET
@Path("/servlet")
public Response handleServletFeatures(
        @Context HttpServletRequest request,
        @Context HttpServletResponse response) {
    Map<String, Object> result = new HashMap<>();

    // 获取 Session 信息
    HttpSession session = request.getSession();
    result.put("sessionId", session.getId());
    result.put("sessionCreationTime", new Date(session.getCreationTime()));
    result.put("sessionLastAccessedTime", new Date(session.getLastAccessedTime()));

    // 获取请求信息
    result.put("requestMethod", request.getMethod());
    result.put("requestURI", request.getRequestURI());
    result.put("requestURL", request.getRequestURL().toString());
    result.put("queryString", request.getQueryString());
    result.put("contextPath", request.getContextPath());
    result.put("servletPath", request.getServletPath());
    result.put("remoteAddr", request.getRemoteAddr());
    result.put("remoteHost", request.getRemoteHost());
    result.put("remotePort", request.getRemotePort());
    result.put("localAddr", request.getLocalAddr());
    result.put("localName", request.getLocalName());
    result.put("localPort", request.getLocalPort());

    // 获取所有请求参数
    Map<String, String[]> parameterMap = request.getParameterMap();
    result.put("parameters", parameterMap);

    // Session 属性操作
    session.setAttribute("lastAccessTime", new Date());
    result.put("sessionAttributes", getSessionAttributes(session));

    return Response.ok(result).build();
}

给一个丰富一点的案例

less 复制代码
/**
 * 演示综合使用场景
 * 展示如何结合使用 Headers、Cookies 和 Session
 */
@POST
@Path("/login")
@Consumes(MediaType.APPLICATION_JSON)
public Response handleLogin(
        @Context HttpHeaders headers,
        @Context HttpServletRequest request,
        Map<String, String> credentials) {
    
    Map<String, Object> result = new HashMap<>();

    // 验证请求头
    String authHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
    if (authHeader == null || !authHeader.startsWith("Bearer ")) {
        return Response.status(Response.Status.UNAUTHORIZED)
                .entity("Missing or invalid authorization header")
                .build();
    }

    // 处理登录逻辑
    String username = credentials.get("username");
    String password = credentials.get("password");

    // 假设验证成功
    // 设置 Session 属性
    HttpSession session = request.getSession(true);
    session.setAttribute("user", username);
    session.setAttribute("loginTime", new Date());

    // 创建认证 Cookie
    NewCookie authCookie = new NewCookie("authToken",
                                       generateAuthToken(),
                                       "/",
                                       null,
                                       1,
                                       "Authentication token",
                                       3600,
                                       true);

    // 设置响应头
    result.put("message", "Login successful");
    result.put("username", username);
    result.put("sessionId", session.getId());

    return Response.ok(result)
            .cookie(authCookie)
            .header("X-Auth-Token", generateAuthToken())
            .build();
}

filter

JAX-RS 提供了强大的过滤器机制,用于处理请求和响应。

JAX-RS 提供两种类型的过滤器:

  • ContainerRequestFilter:处理入站请求
  • ContainerResponseFilter:处理出站响应
less 复制代码
@Provider           // 标记为JAX-RS提供者
@Component          // Spring组件
@Priority(value)    // 设置过滤器优先级
@PreMatching       // 在URI匹配之前执行(可选)

示例代码

less 复制代码
@Provider
@Component
public class RequestLoggingFilter implements ContainerRequestFilter, ContainerResponseFilter {
    @Override
    public void filter(ContainerRequestContext requestContext) {
        // 记录请求信息
    }

    @Override
    public void filter(ContainerRequestContext requestContext,
                      ContainerResponseContext responseContext) {
        // 记录响应信息
    }
}

过滤器链配置

scala 复制代码
@ApplicationPath("/api")
public class RestApplication extends Application {
    @Override
    public Set<Class<?>> getClasses() {
        Set<Class<?>> classes = new HashSet<>();
        classes.add(RequestLoggingFilter.class);
        classes.add(AuthenticationFilter.class);
        classes.add(PerformanceMonitorFilter.class);
        classes.add(CorsFilter.class);
        classes.add(CompressionFilter.class);
        return classes;
    }
}

实现一个简单的拦截

typescript 复制代码
/**
 * 认证过滤器
 * 验证请求中的认证信息
 */
@Provider
@Component
@Priority(Priorities.AUTHENTICATION)
public class AuthenticationFilter implements ContainerRequestFilter {

    private static final String AUTHENTICATION_SCHEME = "Bearer";

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        // 跳过登录和公开接口
        String path = requestContext.getUriInfo().getPath();
        if (isPublicPath(path)) {
            return;
        }

        // 获取认证头
        String authHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);

        // 验证认证头
        if (!isValidAuthHeader(authHeader)) {
            abortWithUnauthorized(requestContext);
            return;
        }

        // 验证令牌
        String token = authHeader.substring(AUTHENTICATION_SCHEME.length()).trim();
        if (!isValidToken(token)) {
            abortWithUnauthorized(requestContext);
            return;
        }

        // 设置安全上下文
        setSecurityContext(requestContext, token);
    }

    private boolean isPublicPath(String path) {
        return path.equals("login") || 
               path.equals("register") || 
               path.startsWith("public/");
    }

    private boolean isValidAuthHeader(String authHeader) {
        return authHeader != null && 
               authHeader.toLowerCase().startsWith(AUTHENTICATION_SCHEME.toLowerCase() + " ");
    }

    private boolean isValidToken(String token) {
        // 实现令牌验证逻辑
        return token != null && !token.isEmpty();
    }

    private void abortWithUnauthorized(ContainerRequestContext requestContext) {
        requestContext.abortWith(
            Response.status(Response.Status.UNAUTHORIZED)
                    .header(HttpHeaders.WWW_AUTHENTICATE, AUTHENTICATION_SCHEME)
                    .entity("User cannot access the resource.")
                    .build());
    }

    private void setSecurityContext(ContainerRequestContext requestContext, String token) {
        // 实现安全上下文设置
    }
}

动态过滤器

@NameBinding

less 复制代码
@NameBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface Logged { }

@Logged
@Provider
public class LoggingFilter implements ContainerRequestFilter {
    // 实现
}

@Logged
@Path("/api")
public class MyResource {
    // 只有标记了@Logged的资源会被过滤
}

动态资源

typescript 复制代码
@Path("/dynamic")
public class DynamicResource {
    @Path("{path: .*}")
    public Object handleDynamic(@PathParam("path") String path) {
        // 根据路径动态返回不同的资源处理器
        switch (path) {
            case "users": return new UserResource();
            case "orders": return new OrderResource();
            default: return new DefaultResource();
        }
    }
}

支持动态路径的匹配,@Path("{path: .*}")

全局异常捕获

typescript 复制代码
@Provider
public class GlobalExceptionMapper implements ExceptionMapper<Throwable> {
    @Override
    public Response toResponse(Throwable exception) {
        if (exception instanceof WebApplicationException) {
            return ((WebApplicationException) exception).getResponse();
        }
        
        return Response
            .status(Response.Status.INTERNAL_SERVER_ERROR)
            .entity(new ErrorResponse(exception.getMessage()))
            .type(MediaType.APPLICATION_JSON)
            .build();
    }
}

统一处理系统中的各类异常,并转换为标准的API响应格式

知道这些核心 API,应该可以应对日常 90% 的工作了,下面再给几个应用场景。

应用场景

302 重定向

less 复制代码
/**
 * 重定向过滤器
 * 处理302临时重定向场景
 */
@Provider
@Component
@PreMatching  // 在URI匹配之前执行
public class RedirectFilter implements ContainerRequestFilter {
    
    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        String path = requestContext.getUriInfo().getPath();
        
        // 处理需要重定向的路径
        if (shouldRedirect(path)) {
            String newLocation = getRedirectLocation(path, requestContext);
            
            // 构建重定向响应
            Response response = Response
                .status(Response.Status.FOUND)  // 302状态码
                .location(URI.create(newLocation))
                .build();
                
            // 中断当前请求并发送重定向响应
            requestContext.abortWith(response);
        }
    }
 ......
}

请求限流

java 复制代码
@Provider
public class ThrottlingFilter implements ContainerRequestFilter {
    private RateLimiter rateLimiter = RateLimiter.create(100.0); // 每秒100个请求

    @Override
    public void filter(ContainerRequestContext requestContext) {
        if (!rateLimiter.tryAcquire()) {
            throw new WebApplicationException(Response.Status.TOO_MANY_REQUESTS);
        }
    }
}

MDC

typescript 复制代码
@Provider
public class MDCFilter implements ContainerRequestFilter, ContainerResponseFilter {
    @Override
    public void filter(ContainerRequestContext requestContext) {
        String requestId = UUID.randomUUID().toString();
        MDC.put("requestId", requestId);
        requestContext.getHeaders().add("X-Request-ID", requestId);
    }

    @Override
    public void filter(ContainerRequestContext requestContext,
                      ContainerResponseContext responseContext) {
        MDC.clear();
    }
}

责任链

java 复制代码
@Provider
@Priority(1)
public class FilterChain implements ContainerRequestFilter {
    private List<RequestProcessor> processors;
    
    @Override
    public void filter(ContainerRequestContext requestContext) {
        for (RequestProcessor processor : processors) {
            if (!processor.process(requestContext)) {
                break;
            }
        }
    }
}

interface RequestProcessor {
    boolean process(ContainerRequestContext context);
}

动态压缩

typescript 复制代码
@Provider
public class DynamicCompressionFilter implements ContainerResponseFilter {
    @Override
    public void filter(ContainerRequestContext requestContext,
                      ContainerResponseContext responseContext) {
        String acceptEncoding = requestContext.getHeaderString(HttpHeaders.ACCEPT_ENCODING);
        if (shouldCompress(responseContext) && acceptEncoding != null) {
            if (acceptEncoding.contains("br")) {
                applyBrotliCompression(responseContext);
            } else if (acceptEncoding.contains("gzip")) {
                applyGzipCompression(responseContext);
            } else if (acceptEncoding.contains("deflate")) {
                applyDeflateCompression(responseContext);
            }
        }
    }
    
    private boolean shouldCompress(ContainerResponseContext response) {
        MediaType mediaType = response.getMediaType();
        return mediaType != null && 
               (mediaType.isCompatible(MediaType.APPLICATION_JSON_TYPE) ||
                mediaType.isCompatible(MediaType.TEXT_HTML_TYPE) ||
                mediaType.isCompatible(MediaType.TEXT_PLAIN_TYPE));
    }
}

遗留进步空间

还有像异步处理、流处理、响应式编程等 API 没有讲解到,还有进步空间!感兴趣可以再研究一下


JAX-RS Restful 风格特性

URI 设计原则:

  • 使用名词而不是动词
  • 使用复数形式表示资源集合
  • 使用层级结构表示资源关系

总结与回顾

通过对 JAX-RS 规范的熟悉,到案例,通过详细的入门教程,再到进阶教程,对 JAX-RS 算是有一个比较全面和深入的了解。再也不用担心尴尬了。

参考资料

RestEasy 教程:howtodoinjava.com/restful-web...

CXF开发:docs.redhat.com/zh-cn/docum...

本文到此结束,感谢阅读,下期再见!

相关推荐
葫芦和十三3 小时前
图解 MongoDB 21|选举与 failover:Primary 是怎么选出来的
后端·mongodb·agent
GetcharZp3 小时前
26k Star 开源内网穿透神器 NetBird,一分钟实现全球设备互联!
后端
考虑考虑4 小时前
Mybatis实现批量插入
java·后端·mybatis
咖啡八杯5 小时前
GoF设计模式——中介者模式
java·后端·spring·设计模式
lizhongxuan7 小时前
多Agent之间的区别
后端
青石路9 小时前
记一次多JDK版本问题的排查,一坑套一坑,差点没爬上来
java
杨充9 小时前
1.面向对象设计思想
后端
IT_陈寒9 小时前
Java的Date类又坑了我一次,改用时间戳真香
前端·人工智能·后端
systemPro10 小时前
2.6亿条设备数据,历史查询从超时到50ms,我做了什么
后端
要阿尔卑斯吗10 小时前
提示词优化启示:为什么“按顺序输出“比“关键度评分“更有效
后端