碍于面子,偷偷学了 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...

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

相关推荐
南宫生15 分钟前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
计算机毕设定制辅导-无忧学长38 分钟前
Maven 基础环境搭建与配置(一)
java·maven
ITPUB-微风41 分钟前
Service Mesh在爱奇艺的落地实践:架构、运维与扩展
运维·架构·service_mesh
bing_1581 小时前
简单工厂模式 (Simple Factory Pattern) 在Spring Boot 中的应用
spring boot·后端·简单工厂模式
天上掉下来个程小白1 小时前
案例-14.文件上传-简介
数据库·spring boot·后端·mybatis·状态模式
风与沙的较量丶2 小时前
Java中的局部变量和成员变量在内存中的位置
java·开发语言
m0_748251722 小时前
SpringBoot3 升级介绍
java
Asthenia04122 小时前
基于Jackson注解的JSON工具封装与Redis集成实战
后端
编程星空2 小时前
css主题色修改后会多出一个css吗?css怎么定义变量?
开发语言·后端·rust
极客先躯3 小时前
说说高级java每日一道面试题-2025年2月13日-数据库篇-请说说 MySQL 数据库的锁 ?
java·数据库·mysql·数据库的锁·模式分·粒度分·属性分