JBoss.orgCommunity Documentation

第 24 章 通过Jackson提供对JSON的支持

24.1. 在 WildFly 外使用 Jackson 1.9. x
24.2. 在WildFly 8中使用Jackson 1.9.x
24.3. 在WildFly之外使用Jackson 2
24.4. 在WildFly 9 以及更高版本使用Jackson 2
24.5. 附加 RESTEasy 特性
24.6. 对JSONP的支持
24.7. Jackson JSON 装饰器
24.8. 对JSON过滤器的支持
24.9. 多态类型反序列化

RESTEasy 支持与 Jackson 项目的集成。更多关于 Jackson 2的信息,请看 http://wiki.fasterxml.com/JacksonHome 。除了类似于 JAXB 的 api 之外,它还有一个基于 JavaBean 的模型,这个模型在 http://wiki.fasterxml.com/JacksonDataBinding 中有描述,它允许您轻松地将 Java 对象序列化到 JSON 和从 JSON 反序列化 Java 对象。RESTEasy 与 JavaBean 模型集成。虽然 Jackson 确实带来了自己的 JAX-RS 集成,RESTEasy 将其扩展了一点,如下面所述。

NOTE. resteasy-jackson-provider 模块基于过时的 Jackson 1.9.x,目前已被弃用,并将在3.1.0.Final 之后的发行版中删除。resteasy-jackson2-provider 模块基于 Jackson 2。

如果你正在 WildFly 之外部署 RESTEasy,那么将 RESTEasy Jackson provder 添加到 WAR pom.xml 构建中:

<dependency>
   <groupId>org.jboss.resteasy</groupId>
   <artifactId>resteasy-jackson-provider</artifactId>
   <version>${version.resteasy}</version>
</dependency>

如果您正在使用 WildFly 8部署 RESTEasy,那么除了确保您已经使用最新和最好的 RESTEasy 更新您的安装之外,您不需要做任何事情。有关详细信息,请参阅本文档的Installation/Configuration部分。

如果你正在 WildFly 之外部署 RESTEasy,那么将 RESTEasy Jackson provder 添加到 WAR pom.xml 构建中:

<dependency>
   <groupId>org.jboss.resteasy</groupId>
   <artifactId>resteasy-jackson2-provider</artifactId>
   <version>${version.resteasy}</version>
</dependency>

如果您正在使用 WildFly 9或更高版本部署 RESTEasy,那么除了确保更新了最新和最好的 RESTEasy 之外,您不需要做任何事情。有关详细信息,请参阅本文档的Installation/Configuration部分。

RESTEasy 添加到集成中的第一个额外部分是支持"application/*+json"。Jackson 只接受"application/json"和"text/json"作为有效的媒体类型。这允许您创建基于 json 的媒体类型,并且仍然允许 Jackson 为您序列化对象。例如:

@Path("/customers")
public class MyService {

    @GET
    @Produces("application/vnd.customer+json")
    public Customer[] getCustomers() {}
}

如果您正在使用 Jackson,可以通过添加提供程序 org.jboss.resteasy.plugins.providers.jackson.JacksonJsonpInterceptor 来转换它为 JSONP 。(如果您正在使用 Jackson2提供程序,可以使用 Jackson2JsonpInterceptor)来部署。如果响应的媒体类型是 json,并且给出了回调查询参数,那么响应将是一个 javascript 代码片段,其中包含由 callback 参数定义的方法的方法调用。例如:

GET /resources/stuff?callback=processStuffResponse

会产生这样的返回:

processStuffResponse(<nomal JSON body>)

这支持 jQuery 的默认行为。要在 WildFly 中启用 JacksonJsonpInterceptor,需要使用 jboss-deployment-structure.xml 从 org.jboss.resteasy.resteasy-jackson-provider 模块导入注解:

<jboss-deployment-structure>
  <deployment>
    <dependencies>
      <module name="org.jboss.resteasy.resteasy-jackson-provider" annotations="true"/>
    </dependencies>
  </deployment>
</jboss-deployment-structure>

可以通过设置 callbackQueryParameter 属性更改回调参数的名称。

JacksonJsonpInterceptor 可以将响应封装到 try-catch 块中:

try{processStuffResponse(<normal JSON body>)}catch(e){}

可以通过将 resteasy.jsonp.silent 属性设置为 true 来启用此特性

Note. 由于 JSONP 可以用于 Cross Site Scripting Inclusion (XSSI) attacks ,因此 Jackson2JsonpInterceptor 默认是禁用的。要实现这一目标,必须采取两个步骤:

  1. 如上所述, Jackson2JsonpInterceptor 需要包含在deployment中,例如 META-INF/services/javax.ws.rs.ext.Providers 文件需要下面该行
    org.jboss.resteasy.plugins.providers.jackson.Jackson2JsonpInterceptor
    
    必须包括在classpath中
  2. 同时, "resteasy.jsonp.enable" 参数也必须设置为 "true"。 [参见 第 3.4 节 “配置” 获取更多配置信息]

如果您正在使用 Jackson 2 提供程序,RESTEasy 提供了一个与 JAXB 提供程序中的注解类似的漂亮印刷注解:

org.jboss.resteasy.annotations.providers.jackson.Formatted

下面是一个例子:

@GET
@Produces("application/json")
@Path("/formatted/{id}")
@Formatted
public Product getFormattedProduct()
{
    return new Product(333, "robot");
}

如上所示的示例,@Formatted 注解将启用基础 Jackson 选项"SerializationFeature.INDENT_OUTPUT"。

在 Jackson2中,有一个新特性 JsonFilter ,允许使用 @JsonFilter 注解类并进行动态过滤。这里有一个例子,它定义了从"nameFilter"到 serilize 到 json 格式时过滤实例和过滤 bean 属性的映射:

@JsonFilter(value="nameFilter")
public class Jackson2Product {
    protected String name;
    protected int id;
    public Jackson2Product() {
    }
    public Jackson2Product(final int id, final String name) {
        this.id = id;
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
}

@JsonFilter 注解了resource类,以筛选出一些不在 json 响应中序列化的属性。为了映射过滤器 id 和实例,我们需要创建另一个 jackson 类来添加 id 和过滤器实例映射:


public class ObjectFilterModifier extends ObjectWriterModifier {
	public ObjectFilterModifier() {
	}
	@Override
	public ObjectWriter modify(EndpointConfigBase<?> endpoint,
			MultivaluedMap<String, Object> httpHeaders, Object valueToWrite,
			ObjectWriter w, JsonGenerator jg) throws IOException {

		FilterProvider filterProvider = new SimpleFilterProvider().addFilter(
				"nameFilter",
				SimpleBeanPropertyFilter.filterOutAllExcept("name"));
		return w.with(filterProvider);

	}
}

在这里,modify() 方法将负责在写入之前筛选除"name"属性之外的所有属性。为了使这个工作,我们需要让 RESTEasy 知道这个映射信息。使用 Jackson 的 ObjectWriterInjector 可以很容易地在 WriterInterceptor 中设置这个参数:


@Provider
public class JsonFilterWriteInterceptor implements WriterInterceptor{

	private ObjectFilterModifier modifier = new ObjectFilterModifier();
	@Override
	public void aroundWriteTo(WriterInterceptorContext context)
			throws IOException, WebApplicationException {
		//set a threadlocal modifier
	    ObjectWriterInjector.set(modifier);
		context.proceed();
	}

}

另外,Jackson 的文档建议在 servlet 过滤器中也这样做; 但这可能会导致 RESTEasy 上的问题,因为 ObjectFilterModifier 最终使用 ThreadLocal 对象存储,而且不能保证服务于 servlet 过滤器的同一个线程也会运行资源端点的执行。因此,对于 servlet 过滤器场景,RESTEasy 提供了自己的注入器,它依赖于当前线程上下文类加载器来传递指定的修饰符:


public class ObjectWriterModifierFilter implements Filter {
	private static ObjectFilterModifier modifier = new ObjectFilterModifier();

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
	}

	@Override
	public void doFilter(ServletRequest request, ServletResponse response,
			FilterChain chain) throws IOException, ServletException {
		ResteasyObjectWriterInjector.set(Thread.currentThread().getContextClassLoader(), modifier);
		chain.doFilter(request, response);
	}

	@Override
	public void destroy() {
	}

}

由于存在大量针对特定类型的多态反序列化的 CVEs (详见 FasterXML Jackson 文档) ,从 Jackson 2.10开始,用户有一种只允许指定类反序列化的意思。RESTEasy 默认启用此特性,并允许使用 MicroProfile Config 控制允许的类/包的白名单内容。