9.2. XML
正如您可能已经知道,Jersey 使用 MessageBodyWriter
9.2.1. Low level XML support 低级 XML 支持
Jersey 目前支持一些低水平的数据类型:StreamSource, SAXSource, DOMSource 和 Document。您可以使用这些类型的返回类型或方法(资源)参数。让说我们想要测试这个功能,我们有 helloworld示例 作为起点。所有我们需要做的就是添加方法(资源)的消耗和产生的 XML 和类型将使用上面提到的。
Example 8.31. Low level XML test - methods added to HelloWorldResource.java
@POST
@Path("StreamSource")
public StreamSource getStreamSource(StreamSource streamSource) {
return streamSource;
}
@POST
@Path("SAXSource")
public SAXSource getSAXSource(SAXSource saxSource) {
return saxSource;
}
@POST
@Path("DOMSource")
public DOMSource getDOMSource(DOMSource domSource) {
return domSource;
}
@POST
@Path("Document")
public Document getDocument(Document document) {
return document;
}
MessageBodyWriter
curl -v http://localhost:8080/base/helloworld/StreamSource -d “
“
你应该从我们的服务得到完全相同的 XML ;在本例中,XML 头添加到响应但内容停留。自由的遍历所有资源。
9.2.2. Getting started with JAXB 开始
好的开始,人们已经有了一些 JAXB 注解的经验,例子是是 JAXB示例。你可以看到不同的用例。本文主要是针对那些没有 JAXB 经验。别指望所有可能的注释和他们的组合将在这一章,JAXB(JSR 222实现)是相当复杂和全面。但如果你只是想知道如何与 REST 服务交换 XML 消息,你看着合适的章节。
可以从简单的例子开始。让我们说我们有类 Planet 和服务生产的“Planets”。
Example 9.32. Planet class
@XmlRootElement
public class Planet {
public int id;
public String name;
public double radius;
}
Example 9.33. Resource class
@Path("planet")
public class Resource {
@GET
@Produces(MediaType.APPLICATION_XML)
public Planet getPlanet() {
final Planet planet = new Planet();
planet.id = 1;
planet.name = "Earth";
planet.radius = 1.0;
return planet;
}
}
你可以看到有一些额外的注释声明类 Planet,尤其是@XmlRootElement。这是一个 JAXB 注释的 java 类映射到 XML 元素。我们不需要指定任何其他,因为 Planet 非常简单的类,所有的字段都是公开的。在这种情况下,XML 元素名称将派生类名或你可以设置名称属性:@XmlRootElement(name=”yourName”)。
我们的资源类将响应 GET/planet 请求
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<planet>
<id>1</id>
<name>Earth</name>
<radius>1.0</radius>
</planet>
这可能正是我们想要的……与否。或者我们可能不关心,因为我们可以使用 JAX-RS 客户端发出请求该资源,这很容易:
Planet planet = webTarget.path("planet").request(MediaType.APPLICATION_XML_TYPE).get(Planet.class);
有预先创建 WebTarget 对象指向我们的应用程序的上下文根,只需添加路径(在我们的例子中是planet),接收 header(不是强制性的,但服务可以提供不同的内容基于这头,例如可以为 text/html 在 web 浏览器),最后我们指定,我们预计 Planet 类通过GET请求。
不仅可能需要生成 XML ,我们可能希望使用它。
Example 9.34. Method for consuming Planet
@POST
@Consumes(MediaType.APPLICATION_XML)
public void setPlanet(Planet planet) {
System.out.println("setPlanet " + planet);
}
有效的请求后,服务将打印字符串表示的 Planet,可以像 Planet{id=2, name=’Mars’, radius=1.51}。通过 JAX-RS 客户端你能做到:
webTarget.path("planet").post(planet);
如果有需要其他(非默认的) XML 表示,其他 JAXB 注解需要被使用。简化这一过程通常是由从 XML 模式生成 java 源代码是通过 XML 到 java 编译器和它的 xjc 是 JAXB 的一部分。
9.2.3. POJOs
有时,你不能或者不想在代码里面使用注解,但又想用消费和生成 XML 的类的表现形式。这种情况下就可以用 JAXBElement 。下面例子就是没有用 @XmlRootElement 注解:
Example 9.35. Resource class - JAXBElement
@Path("planet")
public class Resource {
@GET
@Produces(MediaType.APPLICATION_XML)
public JAXBElement<Planet> getPlanet() {
Planet planet = new Planet();
planet.id = 1;
planet.name = "Earth";
planet.radius = 1.0;
return new JAXBElement<Planet>(new QName("planet"), Planet.class, planet);
}
@POST
@Consumes(MediaType.APPLICATION_XML)
public void setPlanet(JAXBElement<Planet> planet) {
System.out.println("setPlanet " + planet.getValue());
}
}
正如您可以看到的,一切都是用了 JAXBElement 就会复杂一些。这是因为现在需要显式地设置元素名称的给 Planet 类的 XML 表示。客户端比服务器端更加复杂,因为你不能做 JAXBElement<Planet>
所以 JAX-RS 客户端 API 提供了如何通过 声明 GenericType<T>
的子类 解决它
Example 9.36. Client side - JAXBElement
// GET
GenericType<JAXBElement<Planet>> planetType = new GenericType<JAXBElement<Planet>>() {};
Planet planet = (Planet) webTarget.path("planet").request(MediaType.APPLICATION_XML_TYPE).get(planetType).getValue();
System.out.println("### " + planet);
// POST
planet = new Planet();
// ...
webTarget.path("planet").post(new JAXBElement<Planet>(new QName("planet"), Planet.class, planet));
9.2.4. Using custom JAXBContext 使用自定义 JAXBContext
有些场景适合使用自定义 JAXBContext。JAXBContext 的创建是一个昂贵的操作,如果你已经创建了一个,相同的实例被 Jersey 使用。其他可能使用的情况是当你需要给 JAXBContext 建立一些特定的东西,例如设置不同的类装载器。
Example 9.37. PlanetJAXBContextProvider
@Provider
public class PlanetJAXBContextProvider implements ContextResolver<JAXBContext> {
private JAXBContext context = null;
public JAXBContext getContext(Class<?> type) {
if (type != Planet.class) {
return null; // we don't support nothing else than Planet
}
if (context == null) {
try {
context = JAXBContext.newInstance(Planet.class);
} catch (JAXBException e) {
// log warning/error; null will be returned which indicates that this
// provider won't/can't be used.
}
}
return context;
}
}
上面示例简单创建 JAXBContext 的过程,所有你需要做的就是把这个@Provider
注释放上,这样 Jersey 就能找到它。用户有时在客户端使用 provider (提供者)类 出问题,所以只是为了提醒——你必须 在客户端配置(客户端做任何事情不像通过服务器包扫描)声明他们。
Example 9.38. Using Provider with JAX-RS client
ClientConfig config = new ClientConfig();
config.register(PlanetJAXBContextProvider.class);
Client client = ClientBuilder.newClient(config);
9.2.5. MOXy
如果你想使用 MOXy 作为 JAXB 实现而不是 JAXB RI 您有两种选择。您可以使用标准的 JAXB 机制来定义 从 JAXBContext 实例将获得(有关此主题的更多信息,读JavaDoc JAXBContext)的 JAXBContextFactory 或者你可以将 jersey-media-moxy 模块添加到您的项目和注册/配置 MoxyXmlFeature 类/实例的Configurable。
Example 9.39. Add jersey-media-moxy dependency.
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<version>2.16</version>
</dependency>
Example 9.40. Register the MoxyXmlFeature class.
final ResourceConfig config = new ResourceConfig()
.packages("org.glassfish.jersey.examples.xmlmoxy")
.register(MoxyXmlFeature.class);
Example 9.41. Configure and register an MoxyXmlFeature instance.
// Configure Properties.
final Map<String, Object> properties = new HashMap<String, Object>();
// ...
// Obtain a ClassLoader you want to use.
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
final ResourceConfig config = new ResourceConfig()
.packages("org.glassfish.jersey.examples.xmlmoxy")
.register(new MoxyXmlFeature(
properties,
classLoader,
true, // Flag to determine whether eclipselink-oxm.xml file should be used for lookup.
CustomClassA.class, CustomClassB.class // Classes to be bound.
));