草庐IT

java - 通过 JAXB 将链接插入到 RESTEasy XML 结果中

coder 2024-03-27 原文

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

背景:

为了在我的 JBoss RESTEasy API 中实现 HATEOAS 原则,我必须将链接插入到我的 JAXB XML 结果中,以便客户可以在 API 中导航。

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

不清楚的东西:

据我了解,您必须使用 @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<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");
    }

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

最佳答案

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

@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 引用的 rel类。

@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>

至于你的Unclear Stuff

Annotate the JAX-RS method with @AddLinks to indicate that you want Atom links injected in your response entity.

这表明该方法将利用链接注入(inject)。

Annotate the JAX-RS methods you want Atom links for with @LinkResource, so that RESTEasy knows which links to create for which resources.

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

Add RESTServiceDiscovery fields to the resource classes where you want Atom links injected.

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

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

关于java - 通过 JAXB 将链接插入到 RESTEasy XML 结果中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26014656/

有关java - 通过 JAXB 将链接插入到 RESTEasy XML 结果中的更多相关文章

  1. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  2. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  3. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  4. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

    我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

  5. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  6. ruby-on-rails - Enumerator.new 如何处理已通过的 block ? - 2

    我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m

  7. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  8. 通过 MacPorts 的 RubyGems 是个好主意吗? - 2

    从MB升级到新的MBP后,Apple的迁移助手没有移动我的gem。我这次是通过macports安装ruby​​gems,希望在下次升级时避免这种情况。有什么我应该注意的陷阱吗? 最佳答案 如果你想把你的gems安装在你的主目录中(在传输过程中应该复制过来,作为一个附带的好处,会让你以你自己的身份运行geminstall,而不是root),将gemhome:键设置为您在~/.gemrc中的主目录中的路径. 关于通过MacPorts的RubyGems是个好主意吗?,我们在StackOverf

  9. ruby-on-rails - Ruby url 到 html 链接转换 - 2

    我正在使用Rails构建一个简单的聊天应用程序。当用户输入url时,我希望将其输出为html链接(即“url”)。我想知道在Ruby中是否有任何库或众所周知的方法可以做到这一点。如果没有,我有一些不错的正则表达式示例代码可以使用... 最佳答案 查看auto_linkRails提供的辅助方法。这会将所有URL和电子邮件地址变成可点击的链接(htmlanchor标记)。这是文档中的代码示例。auto_link("Gotohttp://www.rubyonrails.organdsayhellotodavid@loudthinking.

  10. ruby - 通过 RVM 安装 Ruby 1.9.2 永远行不通! - 2

    当我执行>rvminstall1.9.2时一切顺利。然后我做>rvmuse1.9.2也很顺利。但是当涉及到ruby​​-v时..sam@sjones:~$rvminstall1.9.2/home/sam/.rvm/rubies/ruby-1.9.2-p136,thismaytakeawhiledependingonyourcpu(s)...ruby-1.9.2-p136-#fetchingruby-1.9.2-p136-#downloadingruby-1.9.2-p136,thismaytakeawhiledependingonyourconnection...%Total%Rece

随机推荐