【问题标题】:Inserting links into RESTEasy XML results via JAXB通过 JAXB 将链接插入 RESTEasy XML 结果
【发布时间】:2014-09-24 10:31:08
【问题描述】:

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

背景:

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

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

不清楚的东西

据我了解,您必须使用@AddLinks 声明结果应插入链接。然后我必须再次重复(!?)用@LinkResource和“sometimes”指定URI构建过程应该来自哪个类(例如@LinkResource(value = car.class)) .然后我必须在实体类中添加一个RESTServiceDiscovery,用@XmlElementRef 对其进行注释...但在示例中RESTServiceDiscovery 在声明(!?) 后根本不使用。

代码:

我真的很困惑如何使用所有这些,但当然我自己尝试了很多代码来让它工作。
下面的代码和docu例子一样:

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<Book> getBooks() {
        ArrayList<Book> res = new ArrayList<Book>();
        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");
    }

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

【问题讨论】:

    标签: java jakarta-ee jaxb jax-rs resteasy


    【解决方案1】:

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

    @XmlRootElement(name = "bookstore")
    public class BookStore {
    
        private List<Link> links;
        private Collection<Book> books;
    
        @XmlElementRef
        public Collection<Book> getBooks() {
            return books;
        }
    
        public void setBooks(Collection<Book> books) {
            this.books = books;
        }
    
        @XmlElement(name = "link")
        @XmlJavaTypeAdapter(Link.JaxbAdapter.class) 
        public List<Link> getLinks() {
            return links;
        }
    
        public void setLinks(List<Link> 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 类中引用的 rels。

    @Path("/books")
    public class BookResource {
    
        private final Map<Integer, Book> booksDB 
                = Collections.synchronizedMap(new LinkedHashMap<Integer, Book>());
        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<Link> links = new ArrayList<>();
            Collection<Book> 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() 以检索数据库中的下一本书,结果将带有两个不同的链接。

    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);
        }
    }
    

    结果:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <bookstore>
        <book author="Author Three" title="Book Three"/>
        <link href="http://localhost:8080/jaxrs-stackoverflow-book/rest/books/4" rel="next" type="application/xml"/>
        <link href="http://localhost:8080/jaxrs-stackoverflow-book/rest/books/2" rel="previous" type="application/xml"/>
    </bookstore>
    
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <bookstore>
        <book author="Author Four" title="Book Four"/>
        <link href="http://localhost:8080/jaxrs-stackoverflow-book/rest/books/5" rel="next" type="application/xml"/>
        <link href="http://localhost:8080/jaxrs-stackoverflow-book/rest/books/3" rel="previous" type="application/xml"/>
    </bookstore>
    

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


    更新:使用自动发现

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

    • 野蝇 8.1
    • Resteasy 3.0.8
    • 马文

    这是代码

    应用类

    @ApplicationPath("/rest")
    public class BookApplication extends Application {
        @Override
        public Set<Class<?>> getClasses() {
           Set<Class<?>> 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<Book> getBooks() {
            List<Book> 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 只是为了测试)

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                                 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.underdogdevs.web</groupId>
        <artifactId>jaxrs-stackoverflow-user</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>war</packaging>
    
        <name>jaxrs-stackoverflow-user</name>
    
        <properties>
            <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>resteasy-jackson2-provider</artifactId>
            </dependency>
            <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>resteasy-jaxb-provider</artifactId>
            </dependency>
            <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>resteasy-jaxrs</artifactId>
            </dependency>
            <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>jaxrs-api</artifactId>
            </dependency>
            <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>resteasy-links</artifactId>
            </dependency>
            <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>resteasy-client</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.jboss.resteasy</groupId>
                <artifactId>resteasy-servlet-initializer</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>javax</groupId>
                <artifactId>javaee-web-api</artifactId>
                <version>7.0</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.wildfly.bom</groupId>
                    <artifactId>jboss-javaee-7.0-with-resteasy</artifactId>
                    <version>8.1.0.Final</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>org.wildfly.bom</groupId>
                    <artifactId>jboss-javaee-7.0-with-tools</artifactId>
                    <version>8.1.0.Final</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <build>
            <finalName>${project.artifactId}</finalName>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.1</version>
                    <configuration>
                        <source>1.7</source>
                        <target>1.7</target>
                        <compilerArguments>
                            <endorseddirs>${endorsed.dir}</endorseddirs>
                        </compilerArguments>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>2.3</version>
                    <configuration>
                        <failOnMissingWebXml>false</failOnMissingWebXml>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-dependency-plugin</artifactId>
                    <version>2.6</version>
                    <executions>
                        <execution>
                            <phase>validate</phase>
                            <goals>
                                <goal>copy</goal>
                            </goals>
                            <configuration>
                                <outputDirectory>${endorsed.dir}</outputDirectory>
                                <silent>true</silent>
                                <artifactItems>
                                    <artifactItem>
                                        <groupId>javax</groupId>
                                        <artifactId>javaee-endorsed-api</artifactId>
                                        <version>7.0</version>
                                        <type>jar</type>
                                    </artifactItem>
                                </artifactItems>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    在浏览器中运行良好

    与客户端 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
    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <collection xmlns:atom="http://www.w3.org/2005/Atom">
        <book author="Author" title="Book">
            <atom:link rel="list" href="http://localhost:8080/jaxrs-stackoverflow-user/rest/books"/>
        </book>
    </collection>
    

    至于你的不清楚的东西

    使用 @AddLinks 注释 JAX-RS 方法以表明您希望将 Atom 链接注入到响应实体中。

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

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

    这允许您自定义注入哪些链接以及注入哪些实体。 8.2.4. Specifying which JAX-RS methods are tied to which resources 更深入。

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

    "injected" 表示框架将为您实例化它,因此您不必自己显式地执行它(就像您尝试做的那样)。也许对依赖注入和控制反转(IoC)做一些研究

    祝你好运。希望这一切对您有所帮助。

    【讨论】:

    • 哇!这个答案超出了我的预期!太疯狂了!非常非常感谢@peeskillet!对我有很大帮助!因为我现在正在拜访一位朋友,所以我没有时间编写您的示例,但我会在下周完成。 :)
    • @Robert 如果您无法启动它,请告诉我。对于帖子中的第一个项目,我使用了相同的 maven 依赖项和相同的 Application 类
    【解决方案2】:

    我只想补充一点,如果您想以 JSON 格式添加 ATOM 链接,您需要禁用/排除所有 jackson 提供程序。即在 WildFly 8.x 中使用以下内容创建 META-INF/jboss-deployment-structure.xml:

    <jboss-deployment-structure>
      <deployment>
        <exclusions>
          <module name="org.jboss.resteasy.resteasy-jackson-provider" />
          <module name="org.jboss.resteasy.resteasy-jackson2-provider" />
        </exclusions>
        <dependencies>
          <module name="org.jboss.resteasy.resteasy-jettison-provider" />
        </dependencies>
      </deployment>
    </jboss-deployment-structure>
    

    这样,抛弃提供者将正确创建包含 ATOM 链接的 JSON 表示

    希望这会有所帮助;)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-14
      • 2011-10-16
      • 2014-11-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-14
      • 2013-05-25
      相关资源
      最近更新 更多