最近入职新公司,发现使用的 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...
本文到此结束,感谢阅读,下期再见!