input发送a.jax
带有JAX-RS 2.0的Java EE 7带来了几个有用的功能,这些功能进一步简化了开发并导致创建了更加复杂但精简的Java SE / EE RESTful应用程序。 亚当·比恩(Adam Bien)向我们介绍了Java EE 7的主要功能之一。 经Oracle技术网(Oracle公司)许可转载。
大多数需要远程API和自由选择的Java EE 6应用程序都使用或多或少具有RESTful风格的JAX-RS 1.0规范。 带有JAX-RS 2.0的Java EE 7带来了几个有用的功能,这些功能进一步简化了开发并导致创建了更加复杂但精简的Java SE / EE RESTful应用程序。
烤房
Roast House是一个Java友好但简单的JAX-RS 2.0示例,它管理和烘焙一些咖啡豆。 CoffeeBeansResource
本身表示为CoffeeBeansResource
。 URI "coffeebeans"
唯一地标识CoffeeBeansResource
(请参见清单1 )。
清单1
//...
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
@ApplicationScoped
@Path("coffeebeans")
public class CoffeeBeansResource {
@Context
ResourceContext rc;
Map<String, Bean> bc;
@PostConstruct
public void init() {
this.bc = new ConcurrentHashMap<>();
}
@GET
public Collection<Bean> allBeans() {
return bc.values();
}
@GET
@Path("{id}")
public Bean bean(@PathParam("id") String id) {
return bc.get(id);
}
@POST
public Response add(Bean bean) {
if (bean != null) {
bc.put(bean.getName(), bean);
}
final URI id = URI.create(bean.getName());
return Response.created(id).build();
}
@DELETE
@Path("{id}")
public void remove(@PathParam("id") String id) {
bc.remove(id);
}
@Path("/roaster/{id}")
public RoasterResource roaster(){
return this.rc.initResource(new RoasterResource());
}
}
与以前的JAX-RS规范一样,资源可以是@Singleton
或@Stateless
EJB。 此外,所有根资源,提供程序和Application
子类都可以部署为托管或CDI托管Bean。 在所有带有@Provider
注释的扩展中,注入功能也可用,从而简化了与现有代码的集成。 还可以使用ResourceContext
将特定于JAX-RS的组件注入子ResourceContext
:
清单2
@Context
ResourceContext rc;
@Path("/roaster/{id}")
public RoasterResource roaster(){
return this.rc.initResource(new RoasterResource());
}
有趣的是, javax.ws.rs.container.ResourceContext
不仅允许您将JAX-RS信息注入到现有实例中,而且还允许您使用ResourceContext#getResource(Class<T> resourceClass)
方法访问资源类。 传递给ResourceContext#initResource
方法的实例的注入点由JAX-RS运行时使用当前上下文中的值进行设置。 RoasterResource
类中的String id
字段(如清单3所示)接收父级资源的path参数的值:
清单3
public class RoasterResource {
@PathParam("id")
private String id;
@POST
public void roast(@Suspended AsyncResponse ar, Bean bean) {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
}
bean.setType(RoastType.DARK);
bean.setName(id);
bean.setBlend(bean.getBlend() + ": The dark side of the bean");
Response response = Response.ok(bean).header("x-roast-id", id).build();
ar.resume(response);
}
}
参数javax.ws.rs.container.AsyncResponse
与Servlet 3.0 javax.servlet.AsyncContext
类相似,并允许异步请求执行。 在上面的示例中,请求在处理期间被挂起,并且通过调用方法AsyncResponse#resume
将响应推送到客户端。 方法roast
仍然是同步执行的,因此异步执行根本不会带来任何异步行为。 但是,EJB的@javax.ejb.Asynchronous
批注和@Suspended AsyncResponse
可以异步执行业务逻辑,并最终通知感兴趣的客户端。 任何JAX-RS根资源都可以使用@Stateless
或@Singleton
注释进行注释,并且实际上可以用作EJB(参见清单4 ):
清单4
import javax.ejb.Asynchronous;
import javax.ejb.Singleton;
@Stateless
@Path("roaster")
public class RoasterResource {
@POST
@Asynchronous
public void roast(@Suspended AsyncResponse ar, Bean bean) {
//heavy lifting
Response response = Response.ok(bean).build();
ar.resume(response);
}
}
带有@Suspended AsyncResponse
参数的@Asynchronous
资源方法以“ @Suspended AsyncResponse
方式执行。 尽管可以立即释放请求处理线程,但AsyncResponse
仍然为客户端提供了方便的句柄。 完成耗时的工作后,可以方便地将结果推回给客户。 通常,您希望将特定于JAX-RS的行为与实际业务逻辑分开。 可以轻松地将所有业务逻辑提取到专用的边界EJB中,但是CDI事件甚至更适合于覆盖即发即弃的情况。 定制事件类RoastRequest
携带有效负载( Bean
类)作为处理输入,并携带AsyncResponse
作为结果提交(请参见清单5 ):
清单5
public class RoastRequest {
private Bean bean;
private AsyncResponse ar;
public RoastRequest(Bean bean, AsyncResponse ar) {
this.bean = bean;
this.ar = ar;
}
public Bean getBean() {
return bean;
}
public void sendMessage(String result) {
Response response = Response.ok(result).build();
ar.resume(response);
}
public void errorHappened(Exception ex) {
ar.resume(ex);
}
}
CDI事件不仅使业务逻辑与JAX-RS API脱钩,而且还大大简化了JAX-RS代码(请参见清单6) :
清单6
public class RoasterResource {
@Inject
Event<RoastRequest> roastListeners;
@POST
public void roast(@Suspended AsyncResponse ar, Bean bean) {
roastListeners.fire(new RoastRequest(bean, ar));
}
}
任何CDI管理的bean或EJB都可以以发布-订阅的方式接收RoastRequest
,并使用简单的观察者方法: void onRoastRequest(@Observes RoastRequest request){}
同步或异步处理有效负载。
通过AsyncResponse
类,JAX-RS规范引入了一种将信息实时推送到HTTP的简便方法。 从客户端的角度来看,服务器上的异步请求仍然处于阻塞状态,因此是同步的。 从REST设计的角度来看,所有长时间运行的任务都应立即返回HTTP状态代码202,以及有关在处理完成后如何获取结果的其他信息。
方面的回归
流行的REST API通常要求其客户端计算消息的指纹并将其与请求一起发送。 在服务器端,计算指纹并将其与附加信息进行比较。 如果两者都不匹配,则消息将被拒绝。 随着JAX-RS的到来和javax.ws.rs.ext.ReaderInterceptor javax.ws.rs.ext.WriterInterceptor
的引入,可以在服务器端甚至在客户端拦截流量。 服务器上ReaderInterceptor
接口的实现包装了MessageBodyReader#readFrom
并在实际序列化之前执行。
PayloadVerifier
从标头中获取签名,从流中计算指纹,并最终调用ReaderInterceptorContext#proceed
方法,该方法将调用链中的下一个拦截器或MessageBodyReader
实例(请参见清单7 )。
清单7
public class PayloadVerifier implements ReaderInterceptor{
public static final String SIGNATURE_HEADER = "x-signature";
@Override
public Object aroundReadFrom(ReaderInterceptorContext ric) throws IOException,
WebApplicationException {
MultivaluedMap<String, String> headers = ric.getHeaders();
String headerSignagure = headers.getFirst(SIGNATURE_HEADER);
InputStream inputStream = ric.getInputStream();
byte[] content = fetchBytes(inputStream);
String payload = computeFingerprint(content);
if (!payload.equals(headerSignagure)) {
Response response = Response.status(Response.Status.BAD_REQUEST).header(
SIGNATURE_HEADER, "Modified content").build();
throw new WebApplicationException(response);
}
ByteArrayInputStream buffer = new ByteArrayInputStream(content);
ric.setInputStream(buffer);
return ric.proceed();
}
//...
}
修改后的内容会导致不同的指纹,并导致使用BAD_REQUEST(400)响应代码引发WebApplicationException
。
指纹或传出请求的所有计算都可以通过WriterInterceptor
的实现轻松实现自动化。 WriterInterceptor
的实现包装MessageBodyWriter#writeTo
并在将实体序列化为流之前执行。 对于指纹计算,需要“在线”实体的最终表示形式,因此我们传递ByteArrayOutputStream
作为缓冲区,调用WriterInterceptorContext#proceed()
方法,获取原始内容并计算指纹。 参见清单8 。
清单8
public class PayloadVerifier implements WriterInterceptor {
public static final String SIGNATURE_HEADER = "x-signature";
@Override
public void aroundWriteTo(WriterInterceptorContext wic) throws IOException,
WebApplicationException {
OutputStream oos = wic.getOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
wic.setOutputStream(baos);
wic.proceed();
baos.flush();
byte[] content = baos.toByteArray();
MultivaluedMap<String, Object> headers = wic.getHeaders();
headers.add(SIGNATURE_HEADER, computeFingerprint(content));
oos.write(content);
}
//...
}
最后,将计算出的签名作为标头添加到请求,将缓冲区写入原始流,然后将整个请求发送到客户端。 当然,单个类也可以同时实现两个接口:
清单9
import javax.ws.rs.ext.Provider;
@Provider
public class PayloadVerifier implements ReaderInterceptor, WriterInterceptor {
}
与以前的JAX-RS版本一样,将自动发现自定义扩展并将其注册为@Provider
批注。 为了拦截MessageBodyWriter
和MessageBodyReader
实例,只需使用@Provider
批注对ReaderInterceptor
和WriterInterceptor
的实现进行批注-无需其他配置或API调用。
请求拦截
ContainerRequestFilter
和ContainerResponseFilter
拦截整个请求,而不仅是读取和写入实体的过程。 这两个拦截器的功能远比记录原始javax.servlet.http.HttpServletRequest
实例中包含的信息有用。 TrafficLogger
类不仅能够记录HttpServletRequest
包含的信息,而且还可以跟踪与匹配特定请求的资源有关的信息,如清单10所示。
清单10
@Provider
public class TrafficLogger implements ContainerRequestFilter, ContainerResponseFilter {
//ContainerRequestFilter
public void filter(ContainerRequestContext requestContext) throws IOException {
log(requestContext);
}
//ContainerResponseFilter
public void filter(ContainerRequestContext requestContext, ContainerResponseContext
responseContext) throws IOException {
log(responseContext);
}
void log(ContainerRequestContext requestContext) {
SecurityContext securityContext = requestContext.getSecurityContext();
String authentication = securityContext.getAuthenticationScheme();
Principal userPrincipal = securityContext.getUserPrincipal();
UriInfo uriInfo = requestContext.getUriInfo();
String method = requestContext.getMethod();
List<Object> matchedResources = uriInfo.getMatchedResources();
//...
}
void log(ContainerResponseContext responseContext) {
MultivaluedMap<String, String> stringHeaders = responseContext.getStringHeaders();
Object entity = responseContext.getEntity();
//...
}
}
因此,注册实施的ContainerResponseFilter
得到的实例ContainerResponseContext
并能够访问由服务器生成的数据。 状态代码和标头内容(例如Location
标头)可轻松访问。 ContainerRequestContext
和ContainerResponseContext
是可变的类,可以通过过滤器进行修改。
在没有任何其他配置的情况下, ContainerRequestFilter
在HTTP资源匹配阶段之后执行。 此时,不再可能修改传入的请求以自定义资源绑定。 如果您希望影响请求和资源之间的绑定,可以将ContainerRequestFilter
配置为在资源绑定阶段之前执行。 任何使用javax.ws.rs.container.PreMatching
注释注释的ContainerRequestFilter
都会在资源绑定之前执行,因此可以对HTTP请求内容进行调整以进行所需的映射。 @PreMatching
过滤器的常见用例是调整HTTP谓词以克服网络基础结构中的限制。 更多“神秘”的方法(例如PUT
, OPTIONS
, HEAD
或DELETE
可能会被防火墙过滤掉,或者某些HTTP客户端不支持。 @PreMatching ContainerRequestFilter
实现可以从标头(例如,“ X-HTTP-Method-Override
”)中获取指示所需的HTTP动词的信息,并且可以将POST
请求即时更改为PUT
(请参见清单11 )。
清单11
@Provider
@PreMatching
public class HttpMethodOverrideEnabler implements ContainerRequestFilter {
public void filter(ContainerRequestContext requestContext) throws IOException {
String override = requestContext.getHeaders()
.getFirst("X-HTTP-Method-Override");
if (override != null) {
requestContext.setMethod(override);
}
}
}
组态
使用@Provider
批注注册的所有拦截器和过滤器@Provider
为所有资源全局启用。 在部署时,服务器会在部署单元中扫描@Provider
批注,并在激活应用程序之前自动注册所有扩展。 所有扩展都可以打包到专用JAR中,并根据需要与WAR一起部署(在WEB-INF/lib
文件夹中)。 JAX-RS运行时将扫描JAR并自动注册扩展。 自包含的JAR的直接部署很好,但是需要细粒度的扩展打包。 JAR中包含的所有扩展都将被立即激活。
JAX-RS引入了绑定注释,用于选择性装饰资源。 其机制类似于CDI限定词。 任何以meta-annotation javax.ws.rs.NameBinding
表示的自定义注释都可以用于声明拦截点:
清单12
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Tracked {
}
通过在Application的类,方法甚至子类上应用相同的Tracked
注释,可以有选择地激活用Tracked
注释表示的所有拦截器或过滤器:
清单13
@Tracked
@Provider
public class TrafficLogger implements ContainerRequestFilter, ContainerResponseFilter {
}
可以将自定义NameBinding
批注与相应的过滤器或拦截器打包在一起,并由应用程序开发人员有选择地应用于资源。 尽管注释驱动的方法显着提高了灵活性并允许使用较粗的插件程序包,但绑定仍然是静态的。 需要重新编译应用程序并有效地对其进行重新部署,以更改拦截器或过滤器链。
除了跨领域功能的全局和注释驱动配置之外,JAX-RS 2.0还引入了用于动态扩展注册的新API。 容器使用带有@Provider
注释的javax.ws.rs.container.DynamicFeature
接口的实现作为容器的钩子,用于动态注册拦截器和过滤器,而无需重新编译。 LoggerRegistration
扩展通过查询是否存在预定义的系统属性来有条件地注册PayloadVerifier
拦截器和TrafficLogger
过滤器,如清单14所示:
清单14
@Provider
public class LoggerRegistration implements DynamicFeature {
@Override
public void configure(ResourceInfo resourceInfo, FeatureContext context) {
String debug = System.getProperty("jax-rs.traffic");
if (debug != null) {
context.register(new TrafficLogger());
}
String verification = System.getProperty("jax-rs.verification");
if (verification != null) {
context.register(new PayloadVerifier());
}
}
}
客户端
JAX-RS 1.1规范未涵盖该客户端。 尽管客户端REST API的专有实现(例如RESTEasy或Jersey)可以与任何HTTP资源进行通信(甚至无法使用Java EE进行通信),但是客户端代码直接取决于特定的实现。 JAX-RS 2.0引入了新的标准化客户端API。 使用标准的引导程序,可以替换服务提供商接口(SPI)。 该API很流畅,并且与大多数专有REST客户端实现类似(请参见清单15 )。
清单15
import java.util.Collection;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
public class CoffeeBeansResourceTest {
Client client;
WebTarget root;
@Before
public void initClient() {
this.client = ClientBuilder.newClient().register(PayloadVerifier.class);
this.root = this.client.target("http://localhost:8080/roast-house/api/coffeebeans");
}
@Test
public void crud() {
Bean origin = new Bean("arabica", RoastType.DARK, "mexico");
final String mediaType = MediaType.APPLICATION_XML;
final Entity<Bean> entity = Entity.entity(origin, mediaType);
Response response = this.root.request().post(entity, Response.class);
assertThat(response.getStatus(), is(201));
Bean result = this.root.path(origin.getName()).request(mediaType).get(Bean.class);
assertThat(result, is(origin));
Collection<Bean> allBeans = this.root.request().get(
new GenericType<Collection<Bean>>() {
});
assertThat(allBeans.size(), is(1));
assertThat(allBeans, hasItem(origin));
response = this.root.path(origin.getName()).request(mediaType).delete(Response.class);
assertThat(response.getStatus(), is(204));
response = this.root.path(origin.getName()).request(mediaType).get(Response.class);
assertThat(response.getStatus(), is(204));
}
//..
}
在上面的集成测试中,默认的Client
实例是使用无参数的ClientFactory.newClient()
方法获得的。 使用内部javax.ws.rs.ext.RuntimeDelegate
抽象工厂对引导过程本身进行标准化。 可以将现有的RuntimeDelegate
实例(例如通过依赖项注入框架)注入ClientFactory
,也可以通过在文件META-INF/services/javax.ws.rs.ext.RuntimeDelegate
和${java.home}/lib/jaxrs.properties
并最终通过搜索javax.ws.rs.ext.RuntimeDelegate
系统属性。 通过失败的发现,尝试初始化默认(Jersey)实现。
javax.ws.rs.client.Client
的主要目的是能够流畅地访问javax.ws.rs.client.WebTarget
或javax.ws.rs.client.Invocation
实例。 WebTarget
代表JAX-RS资源,而Invocation
是等待提交的即用型请求。 WebTarget
也是一个Invocation
工厂。
在方法CoffeBeansResourceTest#crud()
, Bean
对象在客户端和服务器之间来回传递。 通过选择MediaType.APPLICATION_XML
,只需要几个JAXB批注即可发送和接收在XML文档中序列化的DTO:
清单16
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class Bean {
private String name;
private RoastType type;
private String blend;
}
类名和属性的名称必须匹配才能成功与服务器的表示进行封送处理,但是DTO不必与二进制兼容。 在上面的示例中,两个Bean
类都位于不同的包中,甚至实现了不同的方法。 所需的MediaType
传递到WebTarget#request()
方法,该方法返回同步Invocation.Builder
的实例。 以HTTP动词( GET
, POST
, PUT
, DELETE
, HEAD
, OPTIONS
或TRACE
)命名的方法的最终调用将启动同步请求。
新的客户端API还支持异步资源调用。 如前所述, Invocation
实例将请求与提交分离。 可以通过链接的async()
方法调用来发起异步请求,该调用将返回AsyncInvoker
实例。 参见清单17 。
清单17
@Test
public void roasterFuture() throws Exception {
//...
Future<Response> future = this.root.path("roaster").path("roast-id").request().async().post(entity);
Response response = future.get(5000, TimeUnit.SECONDS);
Object result = response.getEntity();
assertNotNull(result);
assertThat(roasted.getBlend(),containsString("The dark side of the bean"));
}
在上面的示例中,“准异步”通信方式没有太多好处-客户端仍然必须阻止并等待直到响应到达。 但是,基于Future
的调用对于批处理非常有用:客户端可以一次发出多个请求,收集Future
实例,然后再处理它们。
可以通过回调注册来实现真正的异步实现,如清单18所示:
清单18
@Test
public void roasterAsync() throws InterruptedException {
//...
final Entity<Bean> entity = Entity.entity(origin, mediaType);
this.root.path("roaster").path("roast-id").request().async().post(
entity, new InvocationCallback<Bean>() {
public void completed(Bean rspns) {
}
public void failed(Throwable thrwbl) {
}
});
}
对于每个返回Future
方法,都有相应的回调方法可用。 InvocationCallback
接口的实现被接受为方法的最后一个参数post()
在上面的示例中为post()
,并在成功调用有效负载或发生异常情况(异常情况下)时被异步通知。
可以使用内置的模板机制简化URI的自动构造。 可以在执行请求之前不久替换预定义的占位符,并保存WebTarget
实例的重复创建:
清单19
@Test
public void templating() throws Exception {
String rootPath = this.root.getUri().getPath();
URI uri = this.root.path("{0}/{last}").
resolveTemplate("0", "hello").
resolveTemplate("last", "REST").
getUri();
assertThat(uri.getPath(), is(rootPath + "/hello/REST"));
}
一个很小但很重要的细节:在客户端,初始化时未发现扩展; 而是必须在Client实例中显式注册它们: ClientFactory.newClient().register(PayloadVerifier.class)
。 但是,可以在客户端和服务器之间共享相同的实体拦截器实现,从而简化了测试,减少了潜在的错误并提高了生产率。 已经引入的PayloadVerifier
拦截器也可以重复使用,而无需在客户端进行任何更改。
结论:是否使用Java EE?
有趣的是,JAX-RS甚至不需要成熟的应用程序服务器。 满足指定的上下文类型后,符合JAX-RS 2.0的API可以是任何东西。 但是,与EJB 3.2的结合带来了异步处理,池化(以及节流)和监视。 与Servlet 3+的紧密集成通过AsyncContext
支持对@Suspended
响应进行了高效的异步处理,并且CDI运行时带来了事件。 此外,Bean验证已很好地集成在一起,可用于验证资源参数。 结合使用JAX-RS 2.0和其他Java EE 7 API,可以将对象公开给远程系统,这是最方便(=无配置)和生产力最高(=无需重新发明)的方式。
也可以看看
作者简介:顾问兼作者Adam Bien是Java EE 6/7,EJB 3.X,JAX-RS和JPA 2.X JSR的专家组成员。 从JDK 1.0开始,他就与Java技术合作,并与servlets / EJB 1.0合作。现在,他是Java SE和Java EE项目的架构师和开发人员。 他编辑了几本有关JavaFX,J2EE和Java EE的书,并且是《 Real World Java EE Patterns — Rethinking Best Practices and Real World Java EE Night Hacks》的作者 。 Adam还是Java冠军,2012年Java大使,JavaOne 2009、2011年和2012 Rock Star。 Adam在慕尼黑的机场偶尔组织Java(EE)研讨会。
经Oracle公司Oracle技术网许可转载
翻译自: https://jaxenter.com/java-ee-7-and-jax-rs-2-0-106101.html
input发送a.jax