通过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; } }
现在在books
或book/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。 指定哪些JAX-RS方法与哪些资源的关系更深入。
将RESTServiceDiscovery字段添加到要注入Atom链接的资源类中。
“注入”意味着框架将为您实例化它,因此您永远不必自己明确地执行它(正如您尝试的那样)。 也许对dependency injection和控制反转(IoC)做一些研究
祝好运。 希望这一切都有帮助。
我只想补充一点,如果你想在JSON中添加你的ATOM链接,你需要禁用/排除所有的jackson提供者。 即在WildFly 8.x中创建一个META-INF / jboss-deployment-structure.xml,其中包含以下内容:
这样,jettison提供程序将正确创建包含ATOM链接的JSON表示
希望这可以帮助 ;)