通过JAXB将链接插入RESTEasy XML结果

我想通过RESTeasy / JAXB将链接插入XML。 我试图使用我的代码的文档 ,但这没有用,所以我只是在文档中编写给定的例子:它仍然不起作用,我不知道为什么。

背景:

要在我的JBoss RESTEasy API中实现HATEOAS原则,我必须在我的JAXB XML结果中插入链接,以便客户端可以浏览API。

我现在正试图了解如何做到这一点,但我不确定文档是否充满错误,或者我只是无法理解示例和解释:

不清楚的东西

据我了解,你必须使用@AddLinks来声明结果应该插入链接。 然后我必须使用@LinkResource 再次 (!?) 冗余地执行此操作,并且“ 有时 ”指定URI构建过程应该来自哪个类(例如@LinkResource(value = car.class) )。 然后我必须在实体类中添加一个RESTServiceDiscovery ,用@XmlElementRef注释它……但在示例中,在声明(!?)之后, RESTServiceDiscovery根本没有被使用。

码:

我真的很困惑如何使用所有这些,但当然我自己尝试了很多代码,让它工作。
以下代码就像文档示例:

BookController.java

 import java.util.ArrayList; import java.util.Collection; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import org.jboss.resteasy.links.AddLinks; import org.jboss.resteasy.links.LinkResource; import com.gasx.extsys.datamodel.vo.kplan.Book; @Path("/") @Consumes({ "application/xml", "application/json" }) @Produces({ "application/xml", "application/json" }) public class BookController { @AddLinks @LinkResource(value = Book.class) @GET @Path("books") public Collection getBooks() { ArrayList res = new ArrayList(); res.add(new Book("Robert", "WhySOIsGreat")); res.add(new Book("Robert", "JavaUltimateGuide")); res.add(new Book("Not Robert", "ThisIsSparta!")); return res; }; @AddLinks @LinkResource @GET @Path("book/{id}") public Book getBook(@PathParam("id") String id) { return new Book("Robert", "WhyIloveJAVA"); }; } 

Book.java

 import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlID; import javax.xml.bind.annotation.XmlRootElement; import org.jboss.resteasy.links.RESTServiceDiscovery; @XmlRootElement @XmlAccessorType(XmlAccessType.NONE) public class Book { @XmlAttribute private String author = "startAuthor"; @XmlID @XmlAttribute private String title = "startTitle"; @XmlElementRef private RESTServiceDiscovery rest; public Book() { } public Book(String author, String title) { this.author = author; this.title = title; } } 

现在在booksbook/1上调用GET会抛出此错误:

 2014-09-25 11:30:36,188 WARN [http-/0.0.0.0:8080-1] (org.jboss.resteasy.core.SynchronousDispatcher:135) # Failed executing GET /book/1: org.jboss.resteasy.plugins.providers.jaxb.JAXBMarshalException: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExceptions XmlElementRef points to a non-existent class. 

我不确定这是如何工作的,所以我尝试使用Book.java中的以下代码手动添加URI:

 import java.net.URI; public Book(String author, String title) { this.author = author; this.title = title; URI uri = URI.create("books/" + title); rest = new RESTServiceDiscovery(); rest.addLink(uri, "self"); } 

但这仍然会引发同样的错误。

我对链接注入不太熟悉,但添加链接的一种简单方法是将javax.ws.rs.core.Link嵌入到JAXB实体类中。 它带有一个内置的XmlAdapter, Link.JaxbAdapter ,它允许Link类型由JAXB编组Link.JaxbAdapter 。 例如,您有一个BookStore类,其中包含一系列Books 。 它还将具有Link ,您可以从中控制导航案例。

 @XmlRootElement(name = "bookstore") public class BookStore { private List links; private Collection books; @XmlElementRef public Collection getBooks() { return books; } public void setBooks(Collection books) { this.books = books; } @XmlElement(name = "link") @XmlJavaTypeAdapter(Link.JaxbAdapter.class) public List getLinks() { return links; } public void setLinks(List links) { this.links = links; } @XmlTransient public URI getNext() { if (links == null) { return null; } for (Link link : links) { if ("next".equals(link.getRel())) { return link.getUri(); } } return null; } @XmlTransient public URI getPrevious() { if (links == null) { return null; } for (Link link : links) { if ("previous".equals(link.getRel())) { return link.getUri(); } } return null; } } 

Book类只是一个常规的根元素JAXB类

 @XmlRootElement public class Book { @XmlAttribute private String author; @XmlAttribute private String title; public Book() {} public Book(String title, String author) { this.title = title; this.author = author; } } 

BookResource类中,我们基本上可以根据您想要表示的链接所需的逻辑按需添加链接。 在下面的示例中,有一个内存数据库(此类仅用作有状态单例类)的书籍,我为其添加了五本书,并增加了ID。 当请求进入时,将向返回的BookStore添加一个或两个链接。 根据请求的ID,我们将添加“下一个”和/或前一个“链接。链接将具有我们从BookStore类引用的rel

 @Path("/books") public class BookResource { private final Map booksDB = Collections.synchronizedMap(new LinkedHashMap()); private final AtomicInteger idCounter = new AtomicInteger(); public BookResource() { Book book = new Book("Book One", "Author One"); booksDB.put(idCounter.incrementAndGet(), book); book = new Book("Book Two", "Author Two"); booksDB.put(idCounter.incrementAndGet(), book); book = new Book("Book Three", "Author Three"); booksDB.put(idCounter.incrementAndGet(), book); book = new Book("Book Four", "Author Four"); booksDB.put(idCounter.incrementAndGet(), book); book = new Book("Book Five", "Author Five"); booksDB.put(idCounter.incrementAndGet(), book); } @GET @Formatted @Path("/{id}") @Produces(MediaType.APPLICATION_XML) public BookStore getBook(@Context UriInfo uriInfo, @PathParam("id") int id) { List links = new ArrayList<>(); Collection books = new ArrayList<>(); UriBuilder uriBuilder = uriInfo.getBaseUriBuilder(); uriBuilder.path("books"); uriBuilder.path("{id}"); Book book = booksDB.get(id); if (book == null) { throw new WebApplicationException(Response.Status.NOT_FOUND); } synchronized(booksDB) { if (id + 1 <= booksDB.size()) { int next = id + 1; URI nextUri = uriBuilder.clone().build(next); Link link = Link.fromUri(nextUri).rel("next").type(MediaType.APPLICATION_XML).build(); links.add(link); } if (id - 1 > 0) { int previous = id - 1; URI nextUri = uriBuilder.clone().build(previous); Link link = Link.fromUri(nextUri).rel("previous").type(MediaType.APPLICATION_XML).build(); links.add(link); } } books.add(book); BookStore bookStore = new BookStore(); bookStore.setLinks(links); bookStore.setBooks(books); return bookStore; } } 

在测试用例中,我们请求第三本书,我们可以看到内存数据库中有“下一页”和“上一页”书籍的链接。 我们还在BookStore上调用getNext()来检索db中的下一本书,结果将带有两个不同的链接。

 public class BookResourceTest { private static Client client; @BeforeClass public static void setUpClass() { client = ClientBuilder.newClient(); } @AfterClass public static void tearDownClass() { client.close(); } @Test public void testBookResourceLinks() throws Exception { String BASE_URL = "http://localhost:8080/jaxrs-stackoverflow-book/rest/books/3"; WebTarget target = client.target(BASE_URL); String xmlResult = target.request().accept(MediaType.APPLICATION_XML).get(String.class); System.out.println(xmlResult); Unmarshaller unmarshaller = JAXBContext.newInstance(BookStore.class).createUnmarshaller(); BookStore bookStore = (BookStore)unmarshaller.unmarshal(new StringReader(xmlResult)); URI next = bookStore.getNext(); WebTarget nextTarget = client.target(next); String xmlNextResult = nextTarget.request().accept(MediaType.APPLICATION_XML).get(String.class); System.out.println(xmlNextResult); } } 

结果:

             

仅供参考,我正在使用Resteasy 3.0.8和Wildfly 8.1


更新:使用自动发现

所以我尝试了参考指南示例,我无法重现您的问题。 不确定你的完整环境,但这是我正在使用的

  • Wildfly 8.1
  • Resteasy 3.0.8
  • Maven的

这是代码

申请类

 @ApplicationPath("/rest") public class BookApplication extends Application { @Override public Set> getClasses() { Set> classes = new HashSet<>(); classes.add(Bookstore.class); return classes; } } 

资源类

 @Path("/books") @Produces({"application/xml", "application/json"}) public class Bookstore { @AddLinks @LinkResource(value = Book.class) @GET @Formatted public Collection getBooks() { List books = new ArrayList<>(); books.add(new Book("Book", "Author")); return books; } } 

预订课程

 @XmlRootElement @XmlAccessorType(XmlAccessType.NONE) public class Book { @XmlAttribute private String author; @XmlID @XmlAttribute private String title; @XmlElementRef private RESTServiceDiscovery rest; public Book() {} public Book(String title, String author) { this.title = title; this.author = author; } } 

pom.xml(也许你缺少一些依赖项 – 注意下面的resteasy-client和resteasy-servlet-initializer仅用于测试)

   4.0.0 com.underdogdevs.web jaxrs-stackoverflow-user 1.0-SNAPSHOT war jaxrs-stackoverflow-user  ${project.build.directory}/endorsed UTF-8    org.jboss.resteasy resteasy-jackson2-provider   org.jboss.resteasy resteasy-jaxb-provider   org.jboss.resteasy resteasy-jaxrs   org.jboss.resteasy jaxrs-api   org.jboss.resteasy resteasy-links   org.jboss.resteasy resteasy-client test   org.jboss.resteasy resteasy-servlet-initializer test   junit junit test   javax javaee-web-api 7.0 provided      org.wildfly.bom jboss-javaee-7.0-with-resteasy 8.1.0.Final pom import   org.wildfly.bom jboss-javaee-7.0-with-tools 8.1.0.Final pom import     ${project.artifactId}   org.apache.maven.plugins maven-compiler-plugin 3.1  1.7 1.7  ${endorsed.dir}     org.apache.maven.plugins maven-war-plugin 2.3  false    org.apache.maven.plugins maven-dependency-plugin 2.6   validate  copy   ${endorsed.dir} true   javax javaee-endorsed-api 7.0 jar          

在浏览器中正常工作

在此处输入图像描述

适用于客户端api

 public class BookTest { private static Client client; @BeforeClass public static void setUpClass() { client = ClientBuilder.newClient(); } @AfterClass public static void tearDownClass() { client.close(); } @Test public void testBookLink() { String BASE_URL = "http://localhost:8080/jaxrs-stackoverflow-user/rest/books"; WebTarget target = client.target(BASE_URL); String result = target.request() .accept(MediaType.APPLICATION_XML).get(String.class); System.out.println(result); } } 

结果

 Running jaxrs.book.test.BookTest       

至于你的不清楚的东西

使用@AddLinks注释JAX-RS方法,以指示您希望在响应实体中注入Atom链接。

这是为了表明该方法将使用链接注入。

使用@LinkResource注释您希望Atom链接的JAX-RS方法,以便RESTEasy知道要为哪些资源创建哪些链接。

这允许您自定义注入的链接和实体。 8.2.4。 指定哪些J​​AX-RS方法与哪些资源的关系更深入。

将RESTServiceDiscovery字段添加到要注入Atom链接的资源类中。

“注入”意味着框架将为您实例化它,因此您永远不必自己明确地执行它(正如您尝试的那样)。 也许对dependency injection和控制反转(IoC)做一些研究

祝好运。 希望这一切都有帮助。

我只想补充一点,如果你想在JSON中添加你的ATOM链接,你需要禁用/排除所有的jackson提供者。 即在WildFly 8.x中创建一个META-INF / jboss-deployment-structure.xml,其中包含以下内容:

            

这样,jettison提供程序将正确创建包含ATOM链接的JSON表示

希望这可以帮助 ;)