530 likes | 795 Views
JAX-RS 2.0 : REST 式 Web 服务 API 中新增的和值得关注的功能. John Clingan Java EE 和 GlassFish 产品经理 john.clingan@oracle.com. JAX-RS 回顾 客户端 API 通用配置 异步处理 过滤器 / 拦截器 超媒体支持 服务器端内容协商. JAX-RS 2.0 中的新增功能. JAX-RS — 用于 REST 式服务的 Java API. 注解驱动的标准 API , 用于帮助开发人员 使用 Java 构建 REST 式 Web 服务
E N D
JAX-RS 2.0:REST 式 Web 服务 API 中新增的和值得关注的功能 John Clingan Java EE 和 GlassFish 产品经理 john.clingan@oracle.com
JAX-RS 回顾 • 客户端 API • 通用配置 • 异步处理 • 过滤器/拦截器 • 超媒体支持 • 服务器端内容协商 JAX-RS 2.0 中的新增功能
JAX-RS — 用于 REST 式服务的 Java API 注解驱动的标准API, 用于帮助开发人员 使用 Java 构建 REST 式Web 服务 和客户端 • 基于 POJO 的资源类 • 以 HTTP 为中心的编程模型 • 实体格式独立性 • 容器独立性 • 包括在 Java EE 中
JAX-RS 示例 ... @POST @Path("/withdrawal") @Consumes("text/plain") @Produces("application/json") public Money withdraw( @PathParam("card") String card, @QueryParam("pin") String pin, String amount) { return getMoney(card, pin, amount); } }
客户端 API 动机 • HTTP 客户端库太低级 • 利用 JAX-RS 1.x API 中的提供商/概念 • 主要实现引入的专用 API
客户端 API // Get instance of Client Client client = ClientBuilder.newClient(); // Get account balance String bal = client.target("http://.../atm/{cardId}/balance") .resolveTemplate("cardId", "111122223333") .queryParam("pin", "9876") .request("text/plain").get(String.class);
客户端 API // Withdraw some money Moneymoney =client.target("http://.../atm/{cardId}/withdrawal") .resolveTemplate("cardId", "111122223333") .queryParam("pin", "9876") .request("application/json") .post(text("50.0"), Money.class);
客户端 API Invocationinvocation1 = client.target("http://.../atm/{cardId}/balance")… .request(“text/plain”).buildGet(); Invocationinvocation2 = client.target("http://.../atm/{cardId}/withdraw")… .request("application/json") .buildPost(text("50.0"));
客户端 API Collection<Invocation> invocations =Arrays.asList(inv1, inv2); Collection<Response> responses = Collections.transform( invocations, new F<Invocation, Response>() { public Responseapply(Invocationinvocation) { return invocation.invoke(); } });
客户端 API // Create client and register MyProvider1 Client client = ClientBuilder.newClient(); client.register(MyProvider1.class); // Create atm target; inherits MyProvider1 WebTargetatm = client.target("http://.../atm"); // Register MyProvider2 atm.register(MyProvider2.class); // Create balance target; inherits MyProvider1, MyProvider2 WebTarget balance = atm.path(”{cardId}/balance"); // Register MyProvider3 balance.register(MyProvider3.class);
通用配置 — 动机 客户端 客户端 .register(JsonMessageBodyReader.class) .register(JsonMessageBodyWriter.class) .register(JsonpInterceptor.class) .property(“jsonp.callback.name”, “callback”) .property(“jsonp.callback.queryParam”, “true”) ...
通用配置 — 动机 服务器端 public class MyApp extends javax.ws.rs.core.Application { public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<…>(); ... classes.add(JsonMessageBodyReader.class); classes.add(JsonMessageBodyWriter.class); classes.add(JsonpInterceptor.class); ... return classes; } }
通用配置 — 解决方案 客户端 客户端 .register(JsonMessageBodyReader.class) .register(JsonMessageBodyWriter.class) .register(JsonpInterceptor.class) .property(“jsonp.callback.name”, “callback”) .property(“jsonp.callback.queryParam”, “true”) ... JsonFeaturejf= new JsonFeature().enableCallbackQueryParam(); client.register(jf);
通用配置 — 解决方案 服务器端 public Set<Class<?>>getClasses() { ... classes.add(JsonMessageBodyReader.class); classes.add(JsonMessageBodyWriter.class); classes.add(JsonpInterceptor.class); ... } public Set<Class<?>>getClasses() { ... classes.add(JsonFeature.class); ... }
通用配置 public interface Configurable { Configuration getConfiguration(); Configurable property(String name, Object value); Configurable register(...); } public interface Configuration { Set<Class> getClasses(); Map<Class,Integer> getContracts(Class componentClass); Set<Object> getInstances(); Map<String,Object> getProperties(); Object getProperty(String name); Collection<String> getPropertyNames(); boolean isEnabled(Feature feature); boolean isRegistered(Object component); ... }
通用配置 public interface Feature { boolean configure(FeatureContext context); }
Feature 示例 public void JsonFeature implements Feature { public boolean configure(FeatureContext context) { context.register(JsonMessageBodyReader.class) .register(JsonMessageBodyWriter.class) .register(JsonpInterceptor.class) .property(CALLBACK_NAME, calbackName) .property(USE_QUERY_PARAM, useQueryParam); return true; } }
动态 Feature 仅服务器端 public interface DynamicFeature { void configure(ResourceInfo ri, FeatureContext context); } public interface ResourceInfo { Method getResourceMethod(); Class<?> getResourceClass(); }
异步处理 • 服务器 API • 分流 I/O 容器线程 • 高效的异步事件处理 • 利用 Servlet 3.x 异步支持(如果可用) • 客户端 API • 异步的请求调用 API
异步处理 @Stateless @Path("/async/longRunning") public class MyResource { @GET @Asynchronous public void longRunningOp(@SuspendedAsyncResponse ar) { ar.setTimeoutHandler(new MyTimoutHandler()); ar.setTimeout(15, SECONDS); final String result = executeLongRunningOperation(); ar.resume(result); } }
异步处理:服务器端 public interface AsyncResponse { public void resume(Object/Throwable response); public void cancel(); public void cancel(int/Date retryAfter); public boolean isSuspended(); public boolean isCancelled(); public boolean isDone(); public void setTimeout(long time, TimeUnit unit); public void setTimeoutHandler(TimeoutHandler handler); public Collection<Class<?>> register(Class<?> callback); public Map<Class<?>,Collection<Class<?>>> register(Class<?> callback, Class<?>... callbacks); public Collection<Class<?>> register(Object callback); public Map<Class<?>,Collection<Class<?>>> register(Object callback, Object... callbacks); }
异步处理:服务器端 @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Suspended { } public interface TimeoutHandler { void handleTimeout(AsyncResponse asyncResponse); }
异步处理:服务器端 public interface CompletionCallback { public void onComplete(Throwable throwable); } public interface ConnectionCallback { public void onDisconnect(AsyncResponse disconnected); }
异步处理:客户端 WebTarget target = client.target("http://.../balance”)… // Start async call and register callback Future<?> handle = target.request().async().get( new InvocationCallback<String>() { void complete(String balance) { … } void failed(InvocationException e) { … } }); // After waiting for too long… if (!handle.isDone()) handle.cancel(true);
过滤器和拦截器 动机 • 定制 JAX-RS 请求/响应处理 • 用例:日志记录、压缩、安全性,等等 • 针对客户端和服务器 API 而引入 • 替换现有的专用支持
过滤器和拦截器 过滤每个传入/传出消息 • 非包装过滤器链 • 过滤器不直接调用链中的下一个过滤器 • 由 JAX-RS 运行时管理 • 每个过滤器决定是继续还是中断链 • 请求 请求 • ContainerRequestFilter, ClientRequestFilter • 响应 响应 • ContainerResponseFilter, ClientResponseFilter • 服务器端特性 • @PreMatching、DynamicFeature
过滤器和拦截器 日志记录过滤器示例 public class RequestLoggingFilter implements ContainerRequestFilter { @Override public void filter(ContainerRequestContext requestContext) { log(requestContext); // non-wrapping => returns without invoking the next filter } ... }
过滤器和拦截器 拦截实体提供者 • 只有当发生实体处理时才会调用 • 性能提升 • 包装拦截器链 • 每个拦截器都通过 context.proceed() 调用链中的下一个拦截器 • MessageBodyReader拦截器 • ReaderInterceptor接口 • MessageBodyWriter 拦截器 • WriterInterceptor接口
过滤器和拦截器 Gzip 读取器拦截器示例 public class GzipInterceptor implements ReaderInterceptor { @Override Object aroundReadFrom(ReaderInterceptorContext ctx) { InputStream old = ctx.getInputStream(); ctx.setInputStream(new GZIPInputStream(old)); // wrapping => invokes the next interceptor Object entity = ctx.proceed(); ctx.setInputStream(old); return entity; } }
传输 响应 过滤器和拦截器 … 写入器 拦截器 写入器 拦截器 MBW 网络 应用程序 … 请求 过滤器 过滤器 … 过滤器 过滤器 … MBR 读取器 拦截器 读取器 拦截器
过滤器和拦截器 … 读取器 拦截器 读取器 拦截器 MBR @PreMatching 网络 资源匹配 … 应用程序 请求 过滤器 过滤器 过滤器 请求 过滤器 … 过滤器 过滤器 响应 响应 过滤器 过滤器 … MBW 写入器 拦截器 写入器 拦截器
绑定和优先级 • 绑定 • 将过滤器和拦截器与资源方法相关联 • 服务器端概念 • 优先级 • 声明在执行链中的相对位置 • @Priority(int priority) • 过滤器和拦截器共有的概念
绑定 @NameBinding @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNTIME) public @interface Logged {} @Provider @Logged @Priority(USER) public class LoggingFilter implements ContainerRequestFilter, ContainerResponseFilter { … }
绑定 @Path("/greet/{name}") @Produces("text/plain") public class MyResourceClass { @Logged @GET public String hello(@PathParam("name") String name) { return "Hello " + name; } }
DynamicFeature 示例 仅服务器端 public void SecurityFeature implements DynamicFeature { public boolean configure(ResourceInfo ri, FeatureContext context) { String[] roles = getRolesAllowed(ri); if (roles != null) { context.register(new RolesAllowedFilter(roles)); } } ... }
超媒体支持 • REST 原则 • 标识符和链接 • HATEOAS(超媒体作为应用程序状态的引擎) • 链接类型: • 结构性链接 • 过渡性链接
超媒体支持 Link:<http://.../orders/1/ship>; rel=ship, <http://.../orders/1/cancel>; rel=cancel ... <order id="1"> <customer>http://.../customers/11</customer> <address>http://.../customers/11/address/1</address> <items> <item> <product>http://.../products/111</product> <quantity>2</quantity> </item> <items> ... </order>
超媒体 • Link和 LinkBuilder类 • RFC 5988:Web 链接 • 支持 ResponseBuilder和过滤器中的链接 • 过渡性链接(标头) • 支持手动结构性链接 • 通过 Link.JaxbAdapter和 Link.JaxbLink • 从客户端 API 中的链接创建资源目标
超媒体 // Producer API (server-side) Link self= Link.fromMethod(MyResource.class, ”handleGet”) .build(); Link update= Link.fromMethod(MyResource.class, “handlePost”) .rel(”update”) .build(); ... Response res = Response.ok(order) .link("http://.../orders/1/ship", "ship") .links(self, update) .build();
超媒体 Response order = client.target(…).request("application/xml").get(); // Consumer API (client-side) Link shipmentLink = order.getLink(“ship”); if (shipmentLink != null) { Response shipment = client.target(shipmentLink).post(null); … }
服务器端内容协商 GET http://.../widgets2 Accept: text/*; q=1 … Path("widgets2") public class WidgetsResource2 { @GET @Produces("text/plain", "text/html") public Widgets getWidget() {...} }
服务器端内容协商 GET http://.../widgets2 Accept: text/*; q=1 … Path("widgets2") public class WidgetsResource2 { @GET @Produces("text/plain; qs=0.5", "text/html; qs=0.75") public Widgets getWidget() {...} }