JAX-RS: возврат конкретного экземпляра класса из метода с объявленным абстрактным типом возврата
У меня есть служба отдыха с использованием JAX-RS и Jackson. Мой клиент хочет иметь абстрактный класс в качестве типа возврата службы, и я понятия не имею, как заставить клиента JAX-RS возвращать экземпляры конкретных подклассов. Возможно ли это для представления XML и JSON? Если это так, оцените образцы и/или ссылки.
3 ответов
есть несколько способов добиться этого. Самый простой способ-использовать @JsonTypeInfo
, как Гарри упомянул. Все, что вам нужно сделать, это комментировать свой абстрактный базовый класс с помощью @JsonTypeInfo(use = Id.CLASS, include = As.PROPERTY)
и вы должны быть хорошо идти.
другой способ-убедиться, что все картографы на стороне сервера и на стороне клиента имеют (вероятно, NON_FINAL) ввод по умолчанию включено, поэтому информация о типе (de)сериализуется в необъявленных классах. Оба пути могут быть увеличены путем обеспечивать ваше собственный TypeResolverBuilder если вы не удовлетворены возможностями сериализации из коробки. Более подробные объяснения этих подходов можно найти в этой статье или Джексона полиморфного типа обработки вики-страницы.
хотя @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>
вы можете попробовать добавить аннотации 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.
Если все, что вы хотите, это вернуть произвольный конкретный экземпляр, это "просто работает" при использовании поставщика на основе Джексона. Это значение по умолчанию для JSON с DropWizard
. Единственная проблема, которая может возникнуть, - это использование простого Джерси и поставщика на основе JAXB, который может ограничить сериализацию API, предоставляемым абстрактным типом.
Если Вам также нужно десериализовать (на стороне клиента) в конкретный тип, то используйте это.