JAX-RS:从声明抽象返回类型的方法返回具体类实例

6

我有一些使用JAX-RS和Jackson的REST服务。我的客户希望将抽象类作为服务的返回类型,但我不知道如何使JAX-RS客户端返回具体子类的实例。这对XML和JSON表示都适用吗?如果是,将感激示例和/或链接。


也许可以使用jaxb中的@XmlSeeAlso? - nachokk
我写了一个测试程序。@XmlSeeAlso 起作用了。JSON 呢? - Aleksandr Kravets
1
如果您在教程中使用Jersey,则说明JAXB可以与JSON和XML一起使用。 - nachokk
不是,我在使用CXF和Jackson。 - Aleksandr Kravets
请尝试提供您的测试示例(例如junit),我可以查看是否有清晰的示例。 - рüффп
显示剩余3条评论
3个回答

2
您可以尝试添加JsonTypeInfo和JsonSubTypes注释。
@JsonTypeInfo(use = Id.CLASS,
              include = JsonTypeInfo.As.PROPERTY,
              property = "type")
@JsonSubTypes({
    @Type(value = MySubClass.class)
    })
public abstract class MyAbsClass {
....
}
public class MySubClass extends MyAbsClass {
....
}

它应该将类型信息添加到JSON输出中。

1

有几种方法可以实现这一点。 最简单的方法是使用@JsonTypeInfo就像Garry提到的那样。您只需使用@JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY)对您的抽象基类进行注释,然后就可以开始使用了。

另一种方法是确保服务器端和客户端的所有映射器都启用了(很可能是非最终版本的)默认类型,以便在未注释的类上进行类型信息的序列化和反序列化。如果您对开箱即用的序列化功能不满意,可以通过提供自己的TypeResolverBuilder来增强这两种方式。有关这些方法的更详细说明,请参见本文}或{{link4:Jackson的多态类型处理维基页面
尽管使用@JsonTypeInfo的方法很简单,但是让CXF服务器/客户端实际工作可能是一项繁琐的任务。因此,这里提供一个完整的示例:
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider;
import com.fasterxml.jackson.jaxrs.xml.JacksonJaxbXMLProvider;
import org.apache.cxf.endpoint.Server;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.client.JAXRSClientFactoryBean;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import java.util.Arrays;
import java.util.Collections;
import java.util.Random;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static javax.ws.rs.core.MediaType.APPLICATION_XML;

public class FullCxfJaxrsJacksonExample {
    public static void main(String[] args) {
        String serverAddress = "http://localhost:9000/";
        Server server = null;

        try {
            // make server that supports JSON and XML
            JAXRSServerFactoryBean serverFactory = new JAXRSServerFactoryBean();
            serverFactory.setResourceClasses(ShapeServiceRandom.class);
            serverFactory.setAddress(serverAddress);
            serverFactory.setProviders(Arrays.asList(new JacksonJaxbJsonProvider(), new JacksonJaxbXMLProvider()));
            server = serverFactory.create();

            // make and use a client
            JAXRSClientFactoryBean clientFactory = new JAXRSClientFactoryBean();
            clientFactory.setAddress(serverAddress);
            clientFactory.setServiceClass(ShapeService.class);
            clientFactory.setProvider(new JacksonJaxbJsonProvider());
            clientFactory.setHeaders(Collections.singletonMap("Accept", "application/json"));
            // for an XML client instead of a JSON client, use the following provider/headers instead:
            //clientFactory.setProvider(new JacksonJaxbXMLProvider());
            //clientFactory.setHeaders(Collections.singletonMap("Accept", "application/xml"));

            ShapeService shapeServiceClient = clientFactory.create(ShapeService.class);
            for (int i = 0; i < 10; i++) {
                System.out.format("created shape: %s\n", shapeServiceClient.create());
            }

        } finally {
            if (server != null) {
                server.destroy();
            }
        }
        System.exit(0);
    }

    // Put JsonTypeInfo on the abstract base class so class info gets marshalled.
    // You'll want CLASS instead of MINIMAL_CLASS if your base class and subclasses are in different packages.
    @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS, include = JsonTypeInfo.As.PROPERTY)
    public abstract static class Shape {
    }

    public static class Circle extends Shape {
        public double radius;

        @Override
        public String toString() {
            return "Circle{radius=" + radius + '}';
        }
    }

    public static class Polygon extends Shape {
        public int numSides;

        @Override
        public String toString() {
            return "Polygon{numSides=" + numSides + '}';
        }
    }

    // service definition with abstract return type
    @Path("/shape")
    public interface ShapeService {
        @GET
        @Path("/create")
        @Produces({APPLICATION_JSON, APPLICATION_XML})
        Shape create();
    }

    // service implementation that returns different concrete subclasses
    public static class ShapeServiceRandom implements ShapeService {
        Random random = new Random();

        public Shape create() {
            int num = random.nextInt(8);
            if (num > 3) {
                Polygon polygon = new Polygon();
                polygon.numSides = num;
                return polygon;
            } else {
                Circle circle = new Circle();
                circle.radius = num + 0.5;
                return circle;
            }
        }
    }
}

这个例子已经成功地使用了JDK 1.8.0_45和以下依赖项进行测试:

    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-json-provider</artifactId>
        <version>2.5.4</version>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.jaxrs</groupId>
        <artifactId>jackson-jaxrs-xml-provider</artifactId>
        <version>2.5.4</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-transports-http-jetty</artifactId>
        <version>3.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-rs-client</artifactId>
        <version>3.1.1</version>
    </dependency>

0
如果您只想返回一个任意的具体实例,使用基于Jackson的提供程序,这个方法“就能工作”,而且这也是在JSON与DropWizard一起使用的默认方式。唯一可能出现的问题是,如果使用纯Jersey和基于JAXB的提供程序,则可能将序列化限制为由抽象类型公开的API。
如果您还需要能够反序列化(在客户端)到具体类型中,则需要使用@JsonTypeInfo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接