第 51 章 实体支持
摘要
Apache CXF 运行时支持开箱即用 MIME 类型和 Java 对象之间的有限映射。开发人员可以通过实施自定义读取器和写入器来扩展映射。自定义读取器和写入器在启动时使用运行时注册。
概述
该运行时依赖于 JAX-RS MessageBodyReader 和 MessageBodyWriter 实现,以在 HTTP 消息及其 Java 表示之间序列化和反序列化数据。读取器和写入器可以限制其能够处理的 MIME 类型。
运行时为多个常用映射提供读取器和写入器。如果应用程序需要更高级的映射,开发人员可以提供消息BodyReader 接口和/或 MessageBodyWriter 接口的自定义实现。在应用程序启动时,自定义读取器和写入器会在运行时注册。
原生支持的类型
表 51.1 “原生支持的实体映射” 列出 Apache CXF 提供的实体映射。
Java 类型 | MIME 类型 |
---|---|
原语类型 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JAXB 注解的对象 |
|
javax.ws.rs.core.MultivaluedMap<String, String> |
|
javax.ws.rs.core.StreamingOutput |
|
[a]
此映射用于处理 HTML 表单数据。
[b]
此映射只支持将数据返回到消费者。
|
自定义读取器
自定义实体读取器负责将传入的 HTTP 请求映射到服务实施可以操作的 Java 类型。它们实施 javax.ws.rs.ext.MessageBodyReader 接口。
接口在 例 51.1 “消息读取器界面” 中显示,有两个需要实现的方法:
例 51.1. 消息读取器界面
package javax.ws.rs.ext; public interface MessageBodyReader<T> { public boolean isReadable(java.lang.Class<?> type, java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] annotations, javax.ws.rs.core.MediaType mediaType); public T readFrom(java.lang.Class<T> type, java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] annotations, javax.ws.rs.core.MediaType mediaType, javax.ws.rs.core.MultivaluedMap<String, String> httpHeaders, java.io.InputStream entityStream) throws java.io.IOException, WebApplicationException; }
isReadable()
isReadable()
方法决定了读者是否能够读取数据流并创建正确类型的实体表示。如果读取器可以创建正确类型的实体,则方法返回为true
。表 51.2 “用于确定读取器是否可生成实体的参数” 描述
isReadable()
方法的参数。表 51.2. 用于确定读取器是否可生成实体的参数 参数 类型 描述 type
class<T>
指定用于存储实体的对象的实际 Java 类。
genericType
类型
指定用于存储实体的对象的 Java 类型。例如,如果消息正文被转换为方法参数,则该值将是
Method.getGenericParameterTypes()
方法返回的方法参数类型。annotations
annotation[]
指定用于存储该实体创建的对象的声明上的注解列表。例如,如果消息正文被转换为方法参数,这将是
Method.getParameterAnnotations()
方法返回的注解。mediaType
MediatType
指定 HTTP 实体的 MIME 类型。
readFrom()
readFrom()
方法读取 HTTP 实体,并将其涉及所需的 Java 对象。如果读取成功,则方法返回包含该实体的创建的 Java 对象。如果在读取输入流时发生错误,方法应该会抛出 IOException 异常。如果发生需要 HTTP 错误响应的错误,应当抛出带 HTTP 响应的 WebApplicationException。表 51.3 “用于读取实体的参数” 描述
readFrom()
方法的参数。表 51.3. 用于读取实体的参数 参数 类型 描述 type
class<T>
指定用于存储实体的对象的实际 Java 类。
genericType
类型
指定用于存储实体的对象的 Java 类型。例如,如果消息正文被转换为方法参数,则该值将是
Method.getGenericParameterTypes()
方法返回的方法参数类型。annotations
annotation[]
指定用于存储该实体创建的对象的声明上的注解列表。例如,如果消息正文被转换为方法参数,这将是
Method.getParameterAnnotations()
方法返回的注解。mediaType
MediatType
指定 HTTP 实体的 MIME 类型。
httpHeaders
MultivaluedMap<String, String>
指定与实体关联的 HTTP 消息标头。
entityStream
InputStream
指定包含 HTTP 实体的输入流。
重要这个方法不应该关闭输入流。
在将 MessageBodyReader 实施用作实体读取器之前,必须先将它与 javax.ws.rs.ext.Provider
注解进行解码。@Provider
注释提醒提供额外功能的运行时。这种实现还必须与运行时注册,如 “注册读者和写器”一节 所述。
默认情况下,自定义实体提供商处理所有 MIME 类型。您可以使用 javax.ws.rs.Consumes
注解限制自定义实体读取器处理的 MIME 类型。@Consumes
注释指定自定义实体提供程序所读取的以逗号分隔的 MIME 类型列表。如果实体不是指定的 MIME 类型,则实体提供商不会选择为可能的 reader。
例 51.2 “XML 源实体读取器” 显示实体读取者消耗 XML 实体并将其存储在 Source 对象中。
例 51.2. XML 源实体读取器
import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import javax.ws.rs.Consumes; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyReader; import javax.ws.rs.ext.Provider; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Source; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamSource; import org.w3c.dom.Document; import org.apache.cxf.jaxrs.ext.xml.XMLSource; @Provider @Consumes({"application/xml", "application/*+xml", "text/xml", "text/html" }) public class SourceProvider implements MessageBodyReader<Object> { public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mt) { return Source.class.isAssignableFrom(type) || XMLSource.class.isAssignableFrom(type); } public Object readFrom(Class<Object> source, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream is) throws IOException { if (DOMSource.class.isAssignableFrom(source)) { Document doc = null; DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder; try { builder = factory.newDocumentBuilder(); doc = builder.parse(is); } catch (Exception e) { IOException ioex = new IOException("Problem creating a Source object"); ioex.setStackTrace(e.getStackTrace()); throw ioex; } return new DOMSource(doc); } else if (StreamSource.class.isAssignableFrom(source) || Source.class.isAssignableFrom(source)) { return new StreamSource(is); } else if (XMLSource.class.isAssignableFrom(source)) { return new XMLSource(is); } throw new IOException("Unrecognized source"); } }
自定义写入器
自定义实体作者负责将 Java 类型映射到 HTTP 实体。它们实施 javax.ws.rs.ext.MessageBodyWriter 接口。
接口在 例 51.3 “消息写入程序接口” 中显示,有三个需要实现的方法:
例 51.3. 消息写入程序接口
package javax.ws.rs.ext; public interface MessageBodyWriter<T> { public boolean isWriteable(java.lang.Class<?> type, java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] annotations, javax.ws.rs.core.MediaType mediaType); public long getSize(T t, java.lang.Class<?> type, java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] annotations, javax.ws.rs.core.MediaType mediaType); public void writeTo(T t, java.lang.Class<?> type, java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] annotations, javax.ws.rs.core.MediaType mediaType, javax.ws.rs.core.MultivaluedMap<String, Object> httpHeaders, java.io.OutputStream entityStream) throws java.io.IOException, WebApplicationException; }
isWriteable()
isWriteable()
方法决定了实体作者是否可以将 Java 类型映射到正确的实体类型。如果写入器可以进行映射,方法会返回true
。表 51.4 “用于读取实体的参数” 描述
isWritable()
方法的参数。表 51.4. 用于读取实体的参数 参数 类型 描述 type
class<T>
指定正在写入对象的 Java 类。
genericType
类型
通过反映资源方法返回类型或通过返回的实例检查,指定要写入的 Java 类型。
GenericEntity
类(在 第 48.4 节 “使用通用类型信息返回实体” 所述)提供了对控制这个值的支持。annotations
annotation[]
指定返回实体的方法上的注解列表。
mediaType
MediatType
指定 HTTP 实体的 MIME 类型。
getSize()
getSize()
方法在writeTo()
之前调用。它返回所写入实体的长度,以字节为单位。如果返回正值,则该值将写入到 HTTP 消息的Content-Length
标头中。表 51.5 “用于读取实体的参数” 描述
getSize()
方法的参数。表 51.5. 用于读取实体的参数 参数 类型 描述 t
generic
指定正在写入的实例。
type
class<T>
指定正在写入对象的 Java 类。
genericType
类型
通过反映资源方法返回类型或通过返回的实例检查,指定要写入的 Java 类型。
GenericEntity
类(在 第 48.4 节 “使用通用类型信息返回实体” 所述)提供了对控制这个值的支持。annotations
annotation[]
指定返回实体的方法上的注解列表。
mediaType
MediatType
指定 HTTP 实体的 MIME 类型。
writeTo()
writeTo()
方法将 Java 对象转换为所需的实体类型,并将实体写入到输出流。如果在将实体写入输出流时发生错误,则方法应抛出 IOException 异常。如果发生需要 HTTP 错误响应的错误,应当抛出带 HTTP 响应的 WebApplicationException。表 51.6 “用于读取实体的参数” 描述
writeTo()
方法的参数。表 51.6. 用于读取实体的参数 参数 类型 描述 t
generic
指定正在写入的实例。
type
class<T>
指定正在写入对象的 Java 类。
genericType
类型
通过反映资源方法返回类型或通过返回的实例检查,指定要写入的 Java 类型。
GenericEntity
类(在 第 48.4 节 “使用通用类型信息返回实体” 所述)提供了对控制这个值的支持。annotations
annotation[]
指定返回实体的方法上的注解列表。
mediaType
MediatType
指定 HTTP 实体的 MIME 类型。
httpHeaders
MultivaluedMap<String, Object>
指定与实体关联的 HTTP 响应标头。
entityStream
OutputStream
指定将实体写入的输出流。
在消息BodyWriter 实施可用作实体写器之前,它必须通过 javax.ws.rs.ext.Provider
注释来解码。@Provider
注释提醒提供额外功能的运行时。这种实现还必须与运行时注册,如 “注册读者和写器”一节 所述。
默认情况下,自定义实体提供商处理所有 MIME 类型。您可以使用 javax.ws.rs.Produces
注释限制自定义实体写入器的 MIME 类型。@Produces
注释指定自定义实体提供程序生成的以逗号分隔的 MIME 类型列表。如果实体不是指定的 MIME 类型,则实体提供商不会选择作为可能的写入器。
例 51.4 “XML 源实体作者” 显示取源对象并生成 XML 实体的实体作者。
例 51.4. XML 源实体作者
import java.io.IOException; import java.io.OutputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.Provider; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.apache.cxf.jaxrs.ext.xml.XMLSource; @Provider @Produces({"application/xml", "application/*+xml", "text/xml" }) public class SourceProvider implements MessageBodyWriter<Source> { public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mt) { return Source.class.isAssignableFrom(type); } public void writeTo(Source source, Class<?> clazz, Type genericType, Annotation[] annotations, MediaType mediatype, MultivaluedMap<String, Object> httpHeaders, OutputStream os) throws IOException { StreamResult result = new StreamResult(os); TransformerFactory tf = TransformerFactory.newInstance(); try { Transformer t = tf.newTransformer(); t.transform(source, result); } catch (TransformerException te) { te.printStackTrace(); throw new WebApplicationException(te); } } public long getSize(Source source, Class<?> type, Type genericType, Annotation[] annotations, MediaType mt) { return -1; } }
注册读者和写器
在 JAX-RS 应用可以使用任何自定义实体提供程序之前,必须将自定义提供程序注册到运行时。提供程序使用应用配置文件中的 jaxrs:providers
元素或使用 JAXRSServeronnectionFactoryyBean
类通过运行时注册。
jaxrs:providers
元素是 jaxrs:server
元素的子级,含有 bean
元素的列表。每个 bean
元素定义一个实体提供程序。
例 51.5 “使用运行时注册实体供应商” 显示配置为使用一组自定义实体提供程序的 JAX-RS 服务器。
例 51.5. 使用运行时注册实体供应商
<beans ...> <jaxrs:server id="customerService" address="/"> ... <jaxrs:providers> <bean id="isProvider" class="com.bar.providers.InputStreamProvider"/> <bean id="longProvider" class="com.bar.providers.LongProvider"/> </jaxrs:providers> </jaxrs:server> </beans>
JAXRSServerFactoryBean
类是 Apache CXF 扩展,提供对配置 API 的访问。它有一个 setProvider()
方法,用于将实例化的实体提供程序添加到应用程序中。例 51.6 “以编程方式注册实体供应商” 显示以编程方式注册实体提供程序的代码。
例 51.6. 以编程方式注册实体供应商
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean; ... JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean(); ... SourceProvider provider = new SourceProvider(); sf.setProvider(provider); ...