﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-神奇好望角 The Magical Cape of Good Hope-随笔分类-SOA</title><link>http://www.blogjava.net/shinzey/category/44814.html</link><description>庸人不必自扰，智者何需千虑？</description><language>zh-cn</language><lastBuildDate>Thu, 09 Feb 2012 11:33:13 GMT</lastBuildDate><pubDate>Thu, 09 Feb 2012 11:33:13 GMT</pubDate><ttl>60</ttl><item><title>JAX-RS 从傻逼到牛叉 7：注入参数的自动类型转换</title><link>http://www.blogjava.net/shinzey/archive/2012/01/10/368030.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Tue, 10 Jan 2012 05:17:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2012/01/10/368030.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/368030.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2012/01/10/368030.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/368030.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/368030.html</trackback:ping><description><![CDATA[<div class="article">
    <p>前面介绍了各种请求参数的注入，这些参数在 HTTP 请求中都是以纯文本的方式存在的。在处理参数的时候，往往需要把这些文本参数转换为 Java 对象。JAX-RS 提供了一些内置的规则里自动完成这种转换。</p>
    <h1>转换规则一览</h1>
    <p>JAX-RS 提供了四条自动类型转换规则，下面我们逐条考察。</p>
    <h2>原始类型</h2>
    <p>这个早就见识过了，无需多说。举例回顾一下：</p>
    <pre class="brush: java; highlight: 3">
@GET
@Path("{id}")
public Movie getXxx(@PathParam("id") int id) {/*...*/}
    </pre>
    <h2>提供接受单个 <code>String</code> 参数的构造器的类型</h2>
    <p>这个也不难理解，JAX-RS 会自动调用该构造器创建一个对象：</p>
    <pre class="brush: java; highlight: [2, 8]">
public class Style {
    public Style(String name) {/* ... */}
    // ...
}

@GET
@Path("{name}")
public Movie getXxx(@PathParam("name") Style style) {
    // JAX-RS 已自动调用 xxx = new Style(name)
    // ...
}
    </pre>
    <h2>提供静态工厂方法 <code>valueOf(String)</code> 的类型</h2>
    <p>也好理解。特别需要注意的是，所有的枚举类型都在此列，因为编译器会自动给枚举类型加上一个这样的工厂方法。例如：</p>
    <pre class="brush: java; highlight: [1, 5]">
public enum Style {/*...*/}

@GET
@Path("{name}")
public Movie getXxx(@PathParam("name") Style style) {
    // JAX-RS 已自动调用 style = Style.valueOf(name)
    // ...
}
    </pre>
    <h2>类型参数满足前两个条件的 <code>List&lt;T&gt;</code>、<code>Set&lt;T&gt;</code> 和 <code>SortedSet&lt;T&gt;</code></h2>
    <p>这条规则适用于多值参数，例如查询参数：</p>
    <pre class="brush: java; highlight: 3">
@GET
@Path("xxx")
public Movie getXxx(@QueryParam("style") Set&lt;Style&gt; styles) {
    // JAX-RS 已自动转换每个 Style 对象并组装到 Set 中
    // ...
}
    </pre>
    <h1>转换失败的处理</h1>
    <p>如果转换失败，JAX-RS 会根据情况自动抛出一个包装了初始异常，但是带不同 HTTP 错误码的 <code>WebApplicationException</code>：对矩阵参数（<code>@MatrixParam</code>）、查询参数 （<code>@QueryParam</code>）或路径参数（<code>@PathParam</code>）来说为 <code>HTTP 404 找不到</code>，而对头部参数（<code>@HeaderParam</code>）或 Cookie 参数（<code>@CookieParam</code>）为 <code>HTTP 400 错误请求</code>。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/368030.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2012-01-10 13:17 <a href="http://www.blogjava.net/shinzey/archive/2012/01/10/368030.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAX-RS 从傻逼到牛叉 6：参数注入</title><link>http://www.blogjava.net/shinzey/archive/2011/12/29/367499.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Thu, 29 Dec 2011 08:34:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/12/29/367499.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/367499.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/12/29/367499.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/367499.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/367499.html</trackback:ping><description><![CDATA[<div class="article">
    <p>在<a href="http://www.blogjava.net/shinzey/archive/2011/10/09/360199.html">《JAX-RS 从傻逼到牛叉 3：路径匹配》</a>中，我们已经见过如何使用 <code>@PathParam</code>、<code>@QueryParam</code> 和 <code>@MatrixParam</code> 分别注入 URI 中的路径参数、矩阵参数和查询参数，以及如何编程访问这些参数。本文介绍表单参数、HTTP 头部参数和 Cookie 参数的注入。</p>
    <h2>表单参数</h2>
    <p>HTTP 请求也可以使用提交表单的方式。这时请求方法一般是 POST，当然春哥也无法阻止你用 GET。在前面我们虽然介绍过处理 POST 请求的例子，但那只是利用了 JAX-RS 对 JAXB 的支持，并没有涉及到对具体请求参数的注入。JAX-RS 提供了 <code>@FormParam</code> 注解来注入 POST 请求的参数，例如：</p>
    <pre class="brush: java; highlight: 2">
@POST
public Response createMovie(@FormParam("title") String title) {
    // 此处省略若干行
}
    </pre>
    <p>这儿省略了 <code>@Consumes</code> 注解，JAX-RS 会自动默认为 <code>@Consumes(MediaType.APPLICATION_FORM_URLENCODED)</code>，也就是 <code>application/x-www-form-urlencoded</code> 格式的请求。如果请求格式为 <code>multipart/form-data</code>，就必须显示指明：</p>
    <pre class="brush: java; highlight: [2, 3]">
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response createMovie(@FormParam("title") String title) {
    // 此处省略若干行
}
    </pre>
    <p>JAX-RS 还支持文件的上传和下载，以后再介绍。</p>
    <h2>HTTP 头部参数</h2>
    <p>注入 HTTP 头部参数简单得不能再简单了：</p>
    <pre class="brush: java; highlight: 4">
@GET
@Path("xxx")
@Produces(MediaType.TEXT_PLAIN)
public String xxx(@HeaderParam("User-Agent") String userAgent) {
    // 此处省略若干行
}
    </pre>
    <p>如果有很多头部参数，为了避免臃肿的参数列表，可以注入一个头部对象，然后编程访问头部参数：</p>
    <pre class="brush: java; highlight: 4">
@GET
@Path("xxx")
@Produces(MediaType.TEXT_PLAIN)
public String xxx(@Context HttpHeaders headers) {
    // 此处省略若干行
}
    </pre>
    <h2>Cookie 参数</h2>
    <p>注入 Cookie 参数同样的简单：</p>
    <pre class="brush: java; highlight: 4">
@GET
@Path("xxx")
@Produces(MediaType.TEXT_PLAIN)
public String xxx(@CookieParam("userName") String userName) {
    // 此处省略若干行
}
    </pre>
    <p>如果希望编程访问，则可以像编程访问那样注入一个 <code>HttpHeaders</code> 对象，然后通过它的 <code>getCookies()</code> 方法来获取所有的 Cookie。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/367499.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-12-29 16:34 <a href="http://www.blogjava.net/shinzey/archive/2011/12/29/367499.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAX-RS 从傻逼到牛叉 5：资源的动态定位</title><link>http://www.blogjava.net/shinzey/archive/2011/12/21/366802.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Wed, 21 Dec 2011 08:00:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/12/21/366802.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/366802.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/12/21/366802.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/366802.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/366802.html</trackback:ping><description><![CDATA[<div class="article">
    <p>目前我们的电影服务只提供了对电影信息的访问服务，现在我们要再增加两项级服务，分别用来访问导演和演员信息。加上原先的电信信息服务，我们把 URI 统一放到 <code>/ms/rest/service/</code> 的子路径下。最先想到的方法就是为这三个 URI 分别写 JAX-RS 服务：</p>
    <pre class="brush: java">
@Singleton
@Path("service/movie")
public class MovieService {
    // 此处省略若干行
}

@Singleton
@Path("service/director")
public class DirectorService {
    // 此处省略若干行
}

@Singleton
@Path("service/director")
public class ActorService {
    // 此处省略若干行
}
    </pre>
    <p>这种写法的缺点就是让三个本来有点关系（父级 URI 相同）的服务被放到了毫不相干的三个类里面，不一个个类地查看注解难以看出这点关系。为此，JAX-RS 提供了动态资源绑定的功能，让我们能够对这种情况做一些整理。</p>
    <p>首先，我们引入一个服务定位器来处理集中管理这三个子级服务：</p>
    <pre class="brush: java; highlight: [20, 21, 22, 23, 24, 25, 26, 27]">
@Singleton
@Path("service")
public class ServiceLocator {
    @Inject
    private MovieService movieService;
    @Inject
    private DirectorService directorService;
    @Inject
    private ActorService actorService;
    private Map&lt;String, Object&gt; serviceMap;

    @PostConstruct
    private initServiceMap() {
        serviceMap = new HashMap&lt;&gt;();
        serviceMap.put("movie", movieService);
        serviceMap.put("director", directorService);
        serviceMap.put("actor", actorService);
    }

    @Path("{name}")
    public Object locateService(@PathParam("name") String name) {
        Object service = serviceMap.get(name);
        if (service == null) {
            throw new WebApplicationException(Status.SERVICE_UNAVAILABLE);
        }
        return service;
    }
}
    </pre>
    <p>该类中的 <code>locateService</code> 方法根据服务的名称返回相应的服务实例，注意该方法只有一个 <code>@Path</code> 注解，因为它并不清楚请求的具体内容；返回对象的类型为 <code>Object</code>，表明动态资源定位不要求服务类实现相同的接口，只需要它们的方法带有相应的 JAX-RS 注解，就能够被 JAX-RS 自动发现和处理（专业术语称为 introspect，内省），以 <code>MovieService</code> 为例：</p>
<pre class="brush: java">
@Singleton
public class MovieService {
    @GET
    @Path("{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public Movie find(@PathParam("id") int id) {
        Movie movie = movieDao.get(id);
        if (movie != null) {
            return movie;
        } else {
            throw new WebApplicationException(Status.NOT_FOUND);
        }
    }

    // 此处省略若干行
}
</pre>
    <p>这样，每个请求实际上都由两个类先后处理。例如，处理请求 <code>GET /ms/rest/service/movie/1</code> 的时候，先由 <code>ServiceLocator</code> 返回相配的服务实例 <code>movieService</code>，然后再由该实例的 <code>find</code> 方法返回结果。比起最开始那三个简单的类，虽然多了一层调用，但换来了更加清晰的结构。</p>
    <p>动态资源定位是一个非常灵活强大的功能，用好的话，完全可以把 URI 层次整理成一个类似于文件目录结构的抽象文件系统。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/366802.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-12-21 16:00 <a href="http://www.blogjava.net/shinzey/archive/2011/12/21/366802.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAX-RS 从傻逼到牛叉 4：路径优先级规则</title><link>http://www.blogjava.net/shinzey/archive/2011/12/07/365769.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Wed, 07 Dec 2011 07:10:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/12/07/365769.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/365769.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/12/07/365769.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/365769.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/365769.html</trackback:ping><description><![CDATA[<div class="article">
    <p>笼子大了什么鸟都有。同样的道理，不论多么细心地设计 URI 结构，在系统复杂到一定程度后，仍然难以避免路径冲突。为此，JAX-RS 使用一些规则来定义路径匹配的优先级。</p>
    <p>如果某个请求路径可以对上多个 URI 匹配模式，那么 JAX-RS 就把可能匹配上的 URI 模式先拼接完整，按照下列规则依次进行比较，直到找出最适合的匹配模式：</p>
    <ol>
        <li>首先，字面字符数量更多的 URI 模式优先。“字面字符”就是写死的路径段，不包含路径分隔符 <code>/</code> 和模板参数。例如 <code>/ms/rest/movie/{id : \\d+}</code> 包含 11 个字面字符。</li>
        <li>其次，模板参数个数最多的 URI 模式优先。例如 <code>/ms/rest/movie/{id : \\d+}</code> 带一个模板参数。</li>
        <li>最后，含正则表达式的模板参数个数最多的 URI 模式优先。例如 <code>/ms/rest/movie/{id : \\d+}</code> 带一个含正则表达式的模板参数。</li>
    </ol>
    <p>现在看一个例子。回顾一下，<code>/ms/rest/movie/{id : \\d+}</code> 已经用来根据 ID 获取电影信息。为了制造麻烦，现在引入 <code>/ms/rest/movie/{title}</code> 来根据电影标题获取电影信息。先请你猜一猜 <code>/ms/rest/movie/300</code> 代表啥？ID 为 300 的神秘电影，还是我们可爱的<a href="http://www.imdb.com/title/tt0416449/">勇士</a>？只能跟着规则一条一条地看：</p>
    <ol>
        <li>首先，两个 URI 匹配模式的字面字符都是 11，下一步。</li>
        <li>其次，两个 URI 匹配模式都带一个模板参数，下一步。</li>
        <li>最后，只有 <code>/ms/rest/movie/{id : \\d+}</code> 带了一个含正则表达式的模板参数，胜利！所以返回 ID 为 300 的片片。</li>
    </ol>
    <p>传说这三条规则能够覆盖 90% 以上的情景。不过我们马上就能造出一个打破规则的东西：<code>/ms/rest/movie/{title : [ \\w]+}</code>。经过测试，<code>/ms/rest/movie/300</code> 会匹配上 <code>/ms/rest/movie/{id : \\d+}</code>。如何解释？JAX-RS 规范文档 3.7.2 定义了完整的匹配规则，对于这两个简单的 URI 匹配模式，似乎一直进行到底都无法比较出优先级。莫非有另外的潜规则？或者是 JAX-RS 的实现（参考实现为 Jersey）自行规定？但无论如何，搞出这种怪物本身就是一个设计错误，所以也不必去深究原因。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/365769.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-12-07 15:10 <a href="http://www.blogjava.net/shinzey/archive/2011/12/07/365769.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAX-RS 从傻逼到牛叉 3：路径匹配</title><link>http://www.blogjava.net/shinzey/archive/2011/10/09/360199.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Sun, 09 Oct 2011 04:43:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/10/09/360199.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/360199.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/10/09/360199.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/360199.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/360199.html</trackback:ping><description><![CDATA[<div class="article">
    <p>JAX-RS 的核心功能是处理向 URI 发送的请求，所以它提供了一些匹配模式以便简化对 URI 的解析。楼主在本系列的上一篇文章中已经使用了最简单的路径参数，本文将介绍一些稍微高级点的咚咚。</p>
    <h2>模板参数</h2>
    <p>前面已经见过用 <code>@Path("{id}")</code> 和 <code>@PathParam("id")</code> 来匹配路径参数 <code>id</code>。这种匹配方式可以被嵌入到 <code>@Path</code> 注解中的任何地方，从而匹配多个参数，例如下面的代码用来查找 ID 在某一范围内的电影：</p>
    <pre class="brush: java; highlight: [2, 4]">
        @GET
        @Path("{min}~{max}")
        @Produces(MediaType.APPLICATION_JSON)
        public List&lt;Movie&gt; findMovies(@PathParam("min") int min, @PathParam("max") int max) {
    </pre>
    <p>于是，<code>GET /ms/rest/movie/<b>5~16</b></code> 就将返回 ID 为 5 到 16 的电影。此处的 <code>min</code> 和 <code>max</code> 已被自动转换为 <code>int</code> 类型。JAX-RS 支持多种类型的自动转换，详见 <code>@PathParam</code> 的文档。</p>
    <p>根据 HTTP 规范，参数可能会编码。默认情况下，JAX-RS 会自动解码。如果希望得到未解码的参数，只需在参数上再加个 <code>@Encoded</code> 注解。该注解适用于大多数 JAX-RS 注入类型，但并不常用。</p>
    <p>模板参数虽然灵活，也可能会带来歧义。例如想用 <code>{firstName}-{lastName}</code> 匹配一个人的姓名，但恰好某人的名（<code>lastName</code>）含有“-”字符，像 O-live K 这种，匹配后就会变成姓 live-K，名 O。这种场景很难避免，一种简单的解决方法就是对参数值进行两次编码，然后在服务端代码解码一次，因为 JAX-RS 默认会进行一次解码，或者加上 <code>@Encoded</code> 注解，自己进行两次解码。</p>
    <p>另外，在一个复杂系统中，多个 <code>@Path</code> 可能会造成路径混淆，例如 <code>{a}-{b}</code> 和 <code>{a}-z</code> 都能匹配路径 <code>a-z</code>。虽然 JAX-RS 定义了一些规则来指定匹配的优先级，但这些规则本身就比较复杂，并且也不能完全消除混淆。楼主认为，设计一个 REST 系统的核心就是对 URI 的设计，应当小心处理 URI 的结构，合理分类，尽量保证匹配的唯一性，而不要过度使用晦涩的优先级规则。楼主将在下一篇文章介绍优先级规则。</p>
    <h2>正则表达式</h2>
    <p>模板参数可以用一个正则表达式进行验证，写法是在模板参数的标识符后面加一个冒号，然后跟上正则表达式字符串。例如在根据 ID 查询电影信息的代码中，模板参数 <code>{id}</code> 只能是整数，于是代码可以改进为：</p>
    <pre class="brush: java; highlight: 2">
        @GET
        @Path("{id : \\d+}")
        @Produces(MediaType.APPLICATION_JSON)
        public List&lt;Movie&gt; findMovies(@PathParam("min") int min, @PathParam("max") int max) {
    </pre>
    <p>冒号左右的空格将被忽略。用正则表达式验证数据很有局限性，可惜 JAX-RS 目前并不能直接集成 Bean 验证框架，因此复杂的验证只能靠自己写代码。</p>
    <h2>查询参数</h2>
    <p>查询参数很常见，就是在 URI 的末尾跟上一个问号和一系列由“&”分隔的键值对，例如查询 ID 为 5 到 16 的电影也可以设计为 <code> /ms/rest/movie<b>?min=5&max=16</b></code>。JAX-RS 提供了 <code>QueryParam</code> 来注入查询参数：</p>
    <pre class="brush: java; highlight: [3, 4]">
        @GET
        @Produces(MediaType.APPLICATION_JSON)
        public List&lt;Movie&gt; findMovies(@DefaultValue("0") @QueryParam("min") int min,
                @DefaultValue("0") @QueryParam("max") int max) {
    </pre>
    <p>查询参数是可选的。如果 URI 没有设定某个查询参数，JAX-RS 就会根据情况为其生成 0、空字符串之类的默认值。如果要手动设定默认值，需要像上面的代码一样用   <code>@DefaultValue</code> 注解来指定。另外还可以加上 <code>Encoded</code> 注解来得到编码的原始参数。</p>
    <p>有的查询参数是一对多的键值对，例如 <code> /xyz<b>?a=def&a=pqr</b></code>，这种情况只需将注入的参数类型改为 <code>List</code> 即可。</p>
    <h2>矩阵参数</h2>
    <p>矩阵参数应该属于 URI 规范中的非主流类型，但它实际上比查询参数更灵活，因为它可以嵌入到 URI 路径中的任何一段末尾（用分号隔开），用来标识该段的某些属性。例如 <code>GET /ms/rest/movie<b>;year=2011</b>/title<b>;initial=A</b></code> 表示在 2011 年出品的电影中查找首字母为 A 的标题。<code>year</code> 是电影的属性，而 <code>initial</code> 是标题的属性，这比把它们都作为查询参数放在末尾更直观可读。匹配 URI 的时候，矩阵参数将被忽略，因此前面的 URI 匹配为 <code>/ms/rest/movie/title</b></code>。矩阵参数可以用 <code>@MatrixParam</code> 来注入：</p>
    <pre class="brush: java; highlight: [4, 5]">
        @GET
        @Path("title")
        @Produces(MediaType.APPLICATION_JSON)
        public List&lt;String&gt; findTitles(@MatrixParam("year") int year,
                @MatrixParam("initial") String initial) {
    </pre>
    <p>如果 URI 的多个段中含有相同名称的矩阵参数，例如 <code>/abc<b>;name=XXX</b>/xyz<b>;name=OOO</b></code>，这种直接注入就失效了，只能用下面要讲的编程式访问来取得。</p>
    <h2>编程式访问</h2>
    <p>如果简单的注入不能达到目的，就需要通过注入 <code>PathSegment</code> 或 <code>UriInfo</code> 对象来直接编程访问 URI 的信息。</p>
    <p>一个 <code>PathSegment</code> 对象代表 URI 中的一个路径段，可以从它得到矩阵参数。它可以通过 <code>@PathParam</code> 来注入，这要求该路径段必须整个被定义为一个模板参数。例如下面的代码也可以用来处理 <code>GET /ms/rest/movie/{id}</code>：</p>
    <pre class="brush: java; highlight: [2, 4]">
        @GET
        @Path("{id}")
        @Produces(MediaType.APPLICATION_JSON)
        public Movie findMovie(@PathParam("id") PathSegment ps) {
    </pre>
    <p><code>@PathParam</code> 也可以注入多个段，如果想把 <code>/a/<b>b/c</b>/d</code> 匹配到 <code>/a/<b>{segments}</b>/d</code>，直接注入一个字符串显然不行，因为 <code>b/c</code> 是两个路径段。唯一的选择是把注入的类型改为 <code>List&lt;PathSegment&gt;</code>。楼主严重不推荐用一个模板参数匹配多个路径段，因为这很容易干扰其他匹配的设计，最后搞成一团乱麻。URI 路径段应当尽量设计得简单明晰，再辅以矩阵参数或查询参数就能应付大多数场景。不论对服务端还是客户端开发人员来说，简洁的 URI 既便于管理，又便于使用。网上有不少关于 URI 设计指南的文章，此处不再赘述。</p>
    <p>如果想完全手动解析路径，则可以用 <code>@Context</code> 注入一个 <code>UriInfo</code> 对象，通过此对象可以得到 URI 的全部信息，详见 API 文档。例如：</p>
    <pre class="brush: java; highlight: [2, 4]">
        @GET
        @Path("{id}/{segments}")
        @Produces(MediaType.PLAIN_TEXT)
        public String getInfo(@PathParam("id") int id, @Context UriInfo uriInfo) {
    </pre>
    <p><code>UriInfo</code> 主要用在某些特殊场合下起辅助作用，设计良好的 URI 用普通的注入就能完成大部分匹配。</p>
    <hr>
    <p>工欲善其事必先利其器，为此 JAX-RS 提供了这些利器来解析 URI。至于如何用这些器来做出一个好系统，则还是依赖于 URI 本身的设计。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/360199.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-10-09 12:43 <a href="http://www.blogjava.net/shinzey/archive/2011/10/09/360199.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAX-RS 从傻逼到牛叉 2：开发一个简单的服务</title><link>http://www.blogjava.net/shinzey/archive/2011/09/20/359085.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Tue, 20 Sep 2011 09:22:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/09/20/359085.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/359085.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/09/20/359085.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/359085.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/359085.html</trackback:ping><description><![CDATA[<div class="article">
    <p>JAX-RS 使用注解进行配置，所以用它开发 REST 风格的服务非常简单。楼主在本文用一个小例子来说明 JAX-RS 的基本用法。</p>
    <hr>
    <p>假设楼主要开发一个小电影服务，客户端可以通过请求 URI 对电影进行 CRUD 操作。为简明起见，这儿不使用数据库，只在内存中模拟。先用一个非常简单的 <code>Movie</code> 类，在后续的文章中根据情况逐步扩充：</p>
    <pre class="brush: java">
        public class Movie {
            private int id;
            private String title;
            // 此处省略若干行
        }
    </pre>
    <p>嗯，就是一个很普通的 JavaBean，实际项目中可以根据需要加上 <code>@Entity</code> 等注解。接下来看看如何编写 JAX-RS 服务。</p>
    <hr>
    <p>一个 JAX-RS 服务就是一个使用了 JAX-RS 注解来将 HTTP 请求绑定到方法的 Java 类，一共支持两种类型：单请求对象或单例对象。单请求对象意味着每来一个请求，就创建一个服务对象，在请求结束时销毁。单例对象则意味着只有一个服务对象处理所有的请求，从而可以在多个请求间维持服务状态。JAX-RS 服务可通过继承 <code>javax.ws.rs.core.Application</code> 来定义，其中的 <code>getClasses</code> 方法返回单请求对象的类型，<code>getSingletons</code> 方法返回单例对象的类型。这两个方法是可选的。在 Java EE 6 环境中，如果这两个方法都返回 <code>null</code> 或者空集合，那么应用程序中的所有 JAX-RS 都将被部署。这时可以用 CDI 的 <code>@javax.inject.Singleton</code> 或者 EJB 的 <code>@javax.ejb.Singleton</code> 注解来指定单例对象。</p>
    <p>如果电影服务的上下文根路径为 http://localhost/ms，而楼主希望将服务部署到 http://localhost/ms/rest 下面，只需要写一个类：</p>
    <pre class="brush: java; highlight: 1">
        @ApplicationPath("rest")
        public class RestApplication extends Application {
        }
    </pre>
    <p><code>@ApplicationPath</code> 注解指定所有服务的相对基址，如果为空字符串，则直接使用上下文根路径。另一种配置方式是在 web.xml 文件中进行声明，那是为了使 JAX-RS 能在 Servlet 容器（例如 Tomcat）中运行，此处略过。这项配置必不可少，否则无法部署服务。</p>
    <hr>
    <p>很好很强大，现在开始编写电影服务类 <code>MovieService</code>，先看看声明和初始化：</p>
    <pre class="brush: java; highlight: [1, 2, 7]">
        @Singleton
        @Path("movie")
        public class MovieService {
            private AtomicInteger ai;
            private ConcurrentMap&lt;Integer, Movie&gt; movieMap;

            @PostConstruct
            private void init() {
                ai = new AtomicInteger();
                movieMap = new ConcurrentHashMap&lt;&gt;();
                int id = ai.getAndIncrement();
                movieMap.put(id, new Movie().setId(id).setTitle("Avatar"));
            }
    </pre>
    <p>因为楼主只需要一个“内存数据库”，所以用单例对象即可，此处使用 CDI 的 <code>@javax.inject.Singleton</code> 来声明单例。<code>@Path</code> 声明了一个服务，它指示 <code>MovieService</code> 负责处理发送到 http://localhost/ms/rest/<b>movie</b> 的请求。路径的拼接方式非常直观。<code>init</code> 方法带有 <code>@PostConstruct</code> 注解，因此将在 <code>MovieService</code> 构造完成后立即调用，它向 <code>movieMap</code> 中存入了一个 ID 为 0 的 <code>Movie</code> 对象。为简化代码，<code>Movie</code> 的设置方法都返回 <code>this</code>，有点伪造构建者模式的味道。</p>
    <hr>
    <p>接下来看看如何处理 HTTP 请求。</p>
    <h2>GET</h2>
    <p>GET 请求用于获取一个或多个资源。在本例中用来获取一部电影的信息：</p>
    <pre class="brush: java; highlight: [1, 2, 3, 4, 9]">
        @GET
        @Path("{id}")
        @Produces(MediaType.APPLICATION_JSON)
        public Movie find(@PathParam("id") int id) {
            Movie movie = movieMap.get(id);
            if (movie != null) {
                return movie;
            } else {
                throw new WebApplicationException(Response.Status.NOT_FOUND);
            }
        }
    </pre>
    <p>该方法标注了 <code>@GET</code>，表示用来处理向 http://localhost/ms/rest/movie/{id} 发送的 GET 请求。<code>@Path</code> 再次用来绑定路径，注意其参数 <code>{id}</code>，它带有花括号，对应 URI 的最后一段，也正好和方法参数 <code>id</code> 的 <code>@PathParam</code> 的值相对应。这种参数还有很多高级用法，以后再介绍。<code>@Produces</code> 注解指定输出格式为 JSON。JAX-RS 内置了很多格式，详见 <code>MediaType</code> 的文档。如果找到了相应 ID 的对象，则将其返回，JAX-RS 会自动加上响应码 200 OK；否则抛出异常，错误码为 404 Not Found。</p>
    <p>例如，通过浏览器访问 http://localhost/ms/rest/movie/0，得到的结果为 {"@id":"0","@title":"Avatar"}。</p>
    <h2>POST</h2>
    <p>POST 请求用于创建一个资源。在本例中用来创建一部电影：</p>
    <pre class="brush: java; highlight: [1, 2, 6]">
        @POST
        @Consumes(MediaType.APPLICATION_JSON)
        public Response create(Movie movie) {
            int id = ai.getAndIncrement();
            movieMap.put(id, movie.setId(id));
            return Response.created(URI.create(String.valueOf(id))).build();
        }
    </pre>
    <p>由于没有 <code>@Path</code> 注解，所以 POST 请求的目标就直接是 http://localhost/ms/rest/movie。<code>Consumes</code> 和 <code>@Produces</code> 相反，表示接受的数据类型，此处 JAX-RS 会自动把 JSON 数据转换为 <code>Movie</code> 对象。返回的响应码为 201 Created，并且带有所创建资源的 URI。</p>
    <p>例如，向 http://localhost/ms/rest/movie 发送 POST 请求，正文为 {"@title": "007"}，则可以从 FireBug 的网络监控中看到返回的响应码，以及头部中 Location 的值为 http://localhost:8080/rest/service/movie/1。多次发送该 POST 请求，将会创建多个资源，以保证 POST 不是幂等的。</p>
    <h2>PUT</h2>
    <p>PUT 请求用于创建或更新一个资源。与 POST 不同，PUT 请求要指定某个特定资源的地址。在本例中用来更新一部电影的信息：</p>
    <pre class="brush: java; highlight: [1, 7]">
        @PUT
        @Path("{id}")
        @Consumes(MediaType.APPLICATION_JSON)
        public Response update(@PathParam("id") int id, Movie movie) {
            movie.setId(id);
            if (movieMap.replace(id, movie) != null) {
                return Response.ok().build();
            } else {
                throw new WebApplicationException(Response.Status.NOT_FOUND);
            }
        }
    </pre>
    <p>更新成功就返回 200 OK，否则返回 404 Not Found。这儿先把 <code>movie</code> 对象的 ID 强制改为 URI 所指定的，以免出现不一致。也可以根据需求，将不一致作为异常处理，给客户端返回一个错误码。</p>
    <p>顺便啰嗦一句，反正代码在自己手中，楼主也可以把 PUT 搞成非幂等的，例如将 PUT 当成 POST 来处理，就像以前把 GET 和 POST 一视同仁那样。不过咱既然在搞 JAX-RS，就还是要沾染一点 REST 风格，严格遵守 HTTP 才是。</p>
    <h2>DELETE</h2>
    <p>DELETE 请求用于删除一个资源。在本例中用来删除一部电影：</p>
    <pre class="brush: java; highlight: 1">
        @DELETE
        @Path("{id}")
        public Response delete(@PathParam("id") int id) {
            if (movieMap.remove(id) != null) {
                return Response.ok().build();
            } else {
                throw new WebApplicationException(Response.Status.NOT_FOUND);
            }
        }
    </pre>
    <p>没什么特别的，该说的前面都说了。</p>
    <p>HEAD 和 OPTIONS 请求就忽略吧，用得不太多，也同样挺简单的。</p>
    <hr>
    <p>JAX-RS 服务的部署和部署常规 Web 程序一样，打包成 war 文件就可以了。最后赞一下 NetBeans 可以为 REST 风格的服务自动生成测试页面，很好用，虽然在 Firefox 下页面显示不正常（对此我已经提了一个 <a href="http://netbeans.org/bugzilla/show_bug.cgi?id=202464" target="_blank">bug</a>），但 IE 是可以的。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/359085.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-09-20 17:22 <a href="http://www.blogjava.net/shinzey/archive/2011/09/20/359085.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JAX-RS 从傻逼到牛叉 1：REST 基础知识</title><link>http://www.blogjava.net/shinzey/archive/2011/09/16/358799.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Fri, 16 Sep 2011 07:31:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/09/16/358799.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/358799.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/09/16/358799.html#Feedback</comments><slash:comments>3</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/358799.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/358799.html</trackback:ping><description><![CDATA[<div class="article">
    <p>JAX-RS（JSR 311 - Java&trade; API for RESTful Web Services，用于 REST 风格的 Web 服务的 Java&trade; API）是 Java EE 6 规范的一部分，其目标在于简化和标准化用 Java 开发 REST 风格的 Web 服务。虽然 Java EE 6 刚出炉的时候，楼主也从头到尾看过这份规范，但苦于没有实际的项目练手，看过又忘了，现在最多算达到大成傻逼的境界。这次边看边写，期望完成后至少能破入小成牛逼。先从 REST 本身开始。</p>
    <hr>
    <p>REST（REpresentational State Transfer，代表性状态传输）自称是一种风格而非标准，这在楼主看来有炒作的嫌疑。如果仅仅是一种风格，那么不同的框架如何兼容？所以才有 JAX-RS 的诞生。REST 最大的贡献是带来了 HTTP 协议的复兴。为什么叫复兴呢？本来 HTTP 的功能挺丰富的，可惜长期以来只被用作传输数据，大好青年被埋没了。楼主记得刚开始学 Servlet 的时候，一向是把 <code>doGet</code> 和 <code>doPost</code> 两个方法一视同仁的，因为书上这么教，很多 Web 框架也这么搞，以至于弄了很久才搞清楚 <code>GET</code> 和 <code>POST</code> 是两种不同的请求。现在 REST 拍砖说道，HTTP 早就定义好了一堆操作，以前一直被混淆使用，现在应该重新赋予它们本来的意义了，而且如果充分发挥 HTTP 的功能，完全能够胜任分布式应用的开发（传说中的 SOA）。</p>
    <hr>
    <p>SOA 的理念在于将系统设计为一系列可重用的、解耦的、分布式的服务。这也不是新鲜玩意儿了，早期有 CORBA，稍晚有 SOAP 等等。REST 作为后起之秀，能够快速崛起，也必有其非同寻常的特色。下面一一列举。</p>
    <h2>可寻址性（Addressability）</h2>
    <p>系统中的每个资源都可以通过唯一标识符来访问。小插一句，“标识”的正确读音是 biāozhì。REST 使用 URI（统一资源标识符）管理资源的地址。URI 的概念不解释。一个 URI 可以指向一个或者多个资源。</p>
    <h2>统一的受限接口（The Uniform, Constrained Interface）</h2>
    <p>实际上强调了 HTTP 操作的原意。REST 主要使用了 GET、PUT、DELETE、POST、HEAD 和 OPTIONS 这 6 种操作。此处有两个曾经被忽略的 HTTP 概念：幂等（idempotent）和安全（safe）。幂等应该是 HTTP 从数学借来的一个术语（原始的数学意义楼主也不懂），意味着若干次请求的副作用与单次请求相同，或者根本没有副作用。GET、PUT、DELETE、HEAD 和 OPTIONS 都是幂等的：GET、HEAD 和 OPTIONS 都是读操作，容易理解；PUT 用于创建或更新已知 URI 的资源，多次创建或更新同一个资源显然和一次的效果相同；DELETE 删除资源，亦然。安全表示操作不会影响服务器的状态。GET、HEAD 和 OPTIONS 是安全的。POST 既不幂等又不安全，因为和 PUT 不同，POST 创建资源的时候并不知道资源的 URI，所以多个 POST 请求将会创建多个资源。</p>
    <h2>面向表象（Representation-Oriented）</h2>
    <p>表象这个词有点拗口，传闻在一个 REST 风格的系统中，服务端和客户端之间传输的咚咚就是表象……表象可以是纯文本、XML、JSON……或者自编的山寨格式。唉，不就是数据么？只不过可以用任意的格式来传输，因为 HTTP 正文里面啥都能放。<code>Content-Type</code> 头用来声明格式，一般是 MIME（多用途因特网邮件扩展），像 <code>text/plain</code>、<code>text/html</code>、<code>application/pdf</code> 这些。MIME 可以带属性，例如 <code>text/html; charset=UTF-8</code>。</p>
    <h2>无态通信（Communicate Statelessly）</h2>
    <p>REST 服务器只管理资源，而不会像 Web 服务器一样记录客户的会话状态，这些应该由客户端来管理，如此就能增强 REST 服务器的伸缩性。此处的客户端可以是客户端程序、浏览器，甚至一个 Web 应用。总之，REST 只负责库管啦！</p>
    <h2>HATEOAS</h2>
    <p>猛词砸来了！HATEOAS = Hypermedia As The Engine Of Application State，超媒体作为应用状态的引擎，怎么看起来比 SaaS（Software as a Service，软件作为服务）还要吓人呢？超文本不过是一只纸老虎，超媒体也瞒不过楼主的天眼：超媒体就是是由文字、图像、图形、视频、音频……链成一体的大杂烩！很简单的一个例子，有些坑爹的电影网站经常发布一些内嵌了广告的电影，播放的时候会弹出广告窗口，里面很多链接，你去点两下就中招了：这个电影文件就算是超媒体。</p>
    <p>其实这个词最关键的地方是“状态引擎”。例如楼主在去网购，先选了几个东西，接下来可以干啥呢？可以继续选、可以把购物车清空、可以结账……楼主可以从现状“转换”到其他某些状态，而控制状态转换的那个咚咚就被冠名为状态引擎。多么聪明的词汇啊！楼主发现凡是高手都是造词砖家呀！用超媒体来控制状态转换，就是 HATEOAS：你是要继续看电影还是看广告？看哪个广告？自己慢慢考虑……</p>
    <hr>
    <p>REST 相比 CORBA、SOAP、WS-* 之流确实独树一帜，但也难逃玩弄概念的嫌疑。记得大学里讲数据库的老师说过：“你们现在学了这么多理论，其实以后在工作中未必管用。在大街上随便找一个软件培训学校出来的小伙子，他哪儿懂什么第二第三范式啊？但却能把数据库玩儿得飞转！”</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/358799.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2011-09-16 15:31 <a href="http://www.blogjava.net/shinzey/archive/2011/09/16/358799.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>