第 51 章 实体支持

摘要

Apache CXF 运行时支持开箱即用 MIME 类型和 Java 对象之间的有限映射。开发人员可以通过实施自定义读取器和写入器来扩展映射。自定义读取器和写入器在启动时使用运行时注册。

概述

该运行时依赖于 JAX-RS MessageBodyReader 和 MessageBodyWriter 实现,以在 HTTP 消息及其 Java 表示之间序列化和反序列化数据。读取器和写入器可以限制其能够处理的 MIME 类型。

运行时为多个常用映射提供读取器和写入器。如果应用程序需要更高级的映射,开发人员可以提供消息BodyReader 接口和/或 MessageBodyWriter 接口的自定义实现。在应用程序启动时,自定义读取器和写入器会在运行时注册。

原生支持的类型

表 51.1 “原生支持的实体映射” 列出 Apache CXF 提供的实体映射。

表 51.1. 原生支持的实体映射

Java 类型MIME 类型

原语类型

text/plain

java.lang.Number

text/plain

byte[]

*/*

java.lang.String

*/*

java.io.InputStream

*/*

java.io.Reader

*/*

java.io.File

*/*

javax.activation.DataSource

*/*

javax.xml.transform.Source

text/xml,application/xml,application/\*+xml

javax.xml.bind.JAXBElement

text/xml,application/xml,application/\*+xml

JAXB 注解的对象

text/xml,application/xml,application/\*+xml

javax.ws.rs.core.MultivaluedMap<String, String>

application/x-www-form-urlencoded [a]

javax.ws.rs.core.StreamingOutput

*/* [b]

[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);
...