﻿<?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-随笔分类-Java EE</title><link>http://www.blogjava.net/shinzey/category/44807.html</link><description>庸人不必自扰，智者何需千虑？</description><language>zh-cn</language><lastBuildDate>Tue, 14 Feb 2012 07:52:41 GMT</lastBuildDate><pubDate>Tue, 14 Feb 2012 07:52:41 GMT</pubDate><ttl>60</ttl><item><title>Java EE 7 新功能前瞻</title><link>http://www.blogjava.net/shinzey/archive/2012/02/13/369874.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Mon, 13 Feb 2012 14:23:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2012/02/13/369874.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/369874.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2012/02/13/369874.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/369874.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/369874.html</trackback:ping><description><![CDATA[<div class="article">
    <p>21 世纪初，Spring 框架的诞生和崛起让沉重而腐朽的 J2EE 遭到了当头棒喝，随后大批开发人员转投 Spring 阵营，呼吸间就让 J2EE 阵营大伤元气。然而这种命悬一线的危机并没有造成毁灭性的打击，尤其是对于 Java 这种提倡开放的平台而言，取长补短，互相促进才是正道。于是，JCP 委员会痛定思痛，在 2006 年推出 Java EE 5 规范，主要是对 EJB 的开发进行了极大幅度的简化。2008 年发布的 Java EE 6 引入了 CDI、BV、JAX-RS 等一系列新功能，并且以配置文件（profile）的方式让 Java EE 向轻量级迈进了一步。特别有趣的是，Spring 框架也开始提供对某些 Java EE 注解的支持，是否标志着两大阵营开始合流？Java EE 7 预定于今年下半年发布，目标是支持云计算。最近几年来，云计算一直被炒作，却从来没有一个准确的定义和规范，希望 Java EE 7 能够在 Java 界扭转这种尴尬的局面。</p>
    <p>下面开始详细列举 Java EE 7 的新功能前瞻，数据来源于《Java Magazine 2012-01/02》中的《Cloud/Java EE: Looking Ahead to Java EE 7》一文。Java EE 7 是以“日期驱动”的方式开发的，也就是说，在计划日期到达前没有完成的功能都将被推迟到 Java EE 8。</p>
    <h1>Java EE 7（JSR-342）</h1>
    <ul>
        <li>主题：让应用程序能够在私有或公共云上容易地运行。</li>
        <li>该平台将定义一个应用程序元数据描述符，以描述 PaaS 执行环境（例如多租户、资源共享、服务质量，以及应用程序间的依赖）。</li>
        <li>支持 HTML5、WebSocket、JSON 等新标准，并为它们一一提供 API。</li>
        <li>消除受管 Bean、EJB、Servlet、JSF、CDI 和 JAX-RS 之间不一致的地方。</li>
        <li>可能在 Web 配置文件中包含 JAX-RS 2.0 和 JMS 2.0 API 修订版。</li>
        <li>更新一些现有的技术，可能引入用于 Java EE 的并发工具（JSR-236）和 JCache（JSR-107）。</li>
    </ul>
    <h1>Java Persistence 2.1（JSR-338）</h1>
    <ul>
        <li>支持多租户。</li>
        <li>支持存储过程和厂商函数。</li>
        <li>用规则（Criteria）进行更新和删除。</li>
        <li>支持数据库大纲（Scheme）的生成。</li>
        <li>持久化上下文的同步。</li>
        <li>侦听器中的 CDI 注入。</li>
    </ul>
    <h1>JAX-RS 2.0: The Java API for RESTful Web Services（JSR-339）</h1>
    <ul>
        <li>客户端 API——底层使用构建者模式，可能提供上层封装。</li>
        <li>超媒体——轻松创建和处理关联了资源的链接。</li>
        <li>使用 Bean 验证框架来验证表单或查询参数。</li>
        <li>与 <code>@Inject</code> 更紧密集成。</li>
        <li>服务端的异步请求处理。</li>
        <li>使用“qs”进行服务端的内容协商。</li>
    </ul>
    <h1>Java Servlet 3.1（JSR-340）</h1>
    <ul>
        <li>为 Web 应用程序优化 PaaS 模型。</li>
        <li>用于安全、会话和资源的多租户。</li>
        <li>基于 NIO2 的异步 I/O。</li>
        <li>简化的异步 Servlet。</li>
        <li>利用 Java EE 并发工具。</li>
        <li>支持 WebSocket。</li>
    </ul>
    <h1>Expression Language 3.0（JSR-341）</h1>
    <ul>
        <li>将 <code>ELContext</code> 分离为解析和求值上下文。</li>
        <li>可定制的 EL 强迫规则。</li>
        <li>在 EL 表达式中直接引用静态方法和成员。</li>
        <li>添加运算符，例如等于、字符串连接和取大小。</li>
        <li>与 CDI 集成，例如在表达式求值前/中/后生成事件。</li>
    </ul>
    <h1>Java Message Service 2.0（JSR-343）</h1>
    <ul>
        <li>简化开发——改变 JMS 编程模型，让应用程序开发变得更加简单容易。</li>
        <li>清除/澄清现有规范中的模糊之处。</li>
        <li>与 CDI 集成。</li>
        <li>澄清 JMS 和其他 Java EE 规范之间的关系。</li>
        <li>新的强制性 API允许任何 JMS 提供者能与任何 Java EE 容器集成。</li>
        <li>来自平台的多租户和其他云相关的功能。</li>
    </ul>
    <h1>JavaServer Faces 2.2（JSR-344）</h1>
    <ul>
        <li>简化开发——使配置选项动态化，使复合组件中的 <code>cc:interface</code> 可选，Facelet 标记库的速记 URL，与 CDI 集成，JSF 组件的 OSGi 支持。</li>
        <li>支持 Portlet 2.0 桥（JSR-329）的实现。</li>
        <li>支持 HTML5 的功能，例如 HTML5 表单、元数据、头部和区段内容模型。</li>
        <li>流管理，页面导航事件的侦听器，以及 <code>fileUpload</code> 和 <code>BackButton</code> 等新组件。</li>
    </ul>
    <h1>Enterprise JavaBeans 3.2（JSR-345）</h1>
    <ul>
        <li>增强 EJB 架构以支持 PaaS，例如多租户。</li>
        <li>对在 EJB 外使用容器管理的事务进行工厂化。</li>
        <li>更进一步使用注解。</li>
        <li>与平台中的其他规范对齐和集成。</li>
    </ul>
    <h1>Contexts and Dependency Injection 1.1（JSR-346）</h1>
    <ul>
        <li>拦截器的全局排序和管理内建上下文的装饰器 API。</li>
        <li>可在 Java EE 容器外启动的嵌入式模式。</li>
        <li>声明式地控制归档中的哪些包和 Bean 将被扫描。</li>
        <li>注入日志之类的静态成员。</li>
        <li>将 Servlet 事件作为 CDI 事件发送。</li>
    </ul>
    <h1>Bean Validation 1.1（JSR-349）</h1>
    <ul>
        <li>与其他 Java EE 规范集成。</li>
        <li>JAX-RS：在 HTTP 调用中验证参数和返回值。</li>
        <li>JAXB：将约束条件转换到 XML 模式描述符中。</li>
        <li>方法级别的验证。</li>
        <li>在组集合上应用约束条件。</li>
        <li>扩展模型以支持“与”和“或”风格的组合。</li>
    </ul>
    <h1>JCache: Java Temporary Caching API（JSR-107）</h1>
    <ul>
        <li>在内存中暂存 Java 对象的 API 和语义，包括对象的创建、共享访问、缓存池、失效，以及跨 JVM 的一致性。</li>
    </ul>
    <h1>Java State Management（JSR-350）</h1>
    <ul>
        <li>应用程序和 Java EE 容器可使用该 API 将状态管理的任务交给具有不同 QoS 特征的第三方提供者。</li>
        <li>基于 Java SE 的调用者可通过查询状态提供者来访问状态数据。</li>
        <li>可添加具有不同 QoS 的提供者，API 调用者能够按自己的规则进行查询。</li>
    </ul>
    <h1>Batch Applications for the Java Platform（JSR-352）</h1>
    <ul>
        <li>用于批处理应用程序的编程模型，以及用于调度和执行工作的运行时。</li>
        <li>为标准编程模型定义批处理工作、批处理工作步骤、批处理应用程序、批处理执行器和批处理工作管理器。</li>
    </ul>
    <h1>Concurrency Utilities for Java EE（JSR-236）</h1>
    <ul>
        <li>提供一个整洁、简单且独立的 API，使其能用于任何 Java EE 容器中。</li>
    </ul>
    <h1>Java API for JSON Processing（JSR-353）</h1>
    <ul>
        <li>处理 JSON 的 Java API。</li>
    </ul>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/369874.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-02-13 22:23 <a href="http://www.blogjava.net/shinzey/archive/2012/02/13/369874.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><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>JPA 应用技巧 3：映射多对多的关联表</title><link>http://www.blogjava.net/shinzey/archive/2011/09/27/359547.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Tue, 27 Sep 2011 03:04:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/09/27/359547.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/359547.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/09/27/359547.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/359547.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/359547.html</trackback:ping><description><![CDATA[<div class="article">
    <p>实体间的多对多的关联需要一张关联表。如果直接使用 <code>ManyToMany</code> 来映射，JPA 就会隐式地帮我们自动管理关联表，代码写出来和其他类型的关联差别不大。例如，某州炒房团需要一个炒房跟踪系统，那么该系统中的炒房客和房子就是多对多的关系：</p>
    <pre class="brush: java; highlight: [5, 6, 7, 14, 15]">
        public class Speculator implements Serializable {
            @Id
            private Integer id;
            @ManyToMany
            @JoinTable(joinColumns = @JoinColumn(name = "speculator_id"),
                    inverseJoinColumns = @JoinColumn(name = "house_id"))
            private List&lt;House&gt; houses;
            // 此处省略若干行
        }

        public class House implements Serializable {
            @Id
            private Integer id;
            @ManyToMany(mappedBy = "houses")
            private List&lt;Speculator&gt; speculators;
            // 此处省略若干行
        }
    </pre>
    <p>如果炒房客 <code>s</code> 要卖掉房子 <code>h</code>（严格点说是卖掉房子的产权部分），那么系统执行的代码差不多就是 <code>s.getHouses().remove(h)</code>。看似简单，然而底层的操作却性能低下：JPA 会先从数据库中取出炒房客的所有房产（<code>s.getHouses()</code>），然后再删除指定的那套房子；从数据库层面上看，这将先从关联表（<code>speculator_house</code>）中找到该炒房客的所有房子的外键，然后从 <code>house</code> 表载入这些 <code>House</code> 对象，最后才从 <code>speculator_house</code> 删除关联。在 ORM 出现前，这种操作只需要改关联表，根本不用关心其他房子。这种简单的多对多映射写法将关联表隐藏起来，虽然简化了代码，却也可能带来性能隐患。</p>
    <p>很自然地可以想到，如果把关联表也映射成实体类，就能解决这个问题。<code>speculator_house</code> 包含两个外键，可用作联合主键。如果把它映射为 <code>SpeculatorHouse</code> 类，则该类与 <code>Speculator</code> 和 <code>House</code> 都是多对一的关系。关联表实体类的代码如下（<code>EmbeddedId</code> 的映射技巧见<a href="http://www.blogjava.net/shinzey/archive/2011/09/13/358519.html">《JPA 应用技巧 2：主键外键合体映射》</a>）：</p>
    <pre class="brush: java; highlight: [13, 14, 15, 16, 17, 18]">
        @Embeddable
        public class SpeculatorHouseId implements Serializable {
            private Integer speculatorId;
            private Integer houseId;
            // 此处省略若干行
        }

        @Entity
        @Table(name = "speculator_house")
        public class SpeculatorHouse implements Serializable {
            @EmbeddedId
            private SpeculatorHouseId id;
            @MapsId("speculatorId")
            @ManyToOne
            private Speculator speculator;
            @MapsId("houseId")
            @ManyToOne
            private House house;
            // 此处省略若干行
        }
    </pre>
    <p><code>Speculator</code> 和 <code>House</code> 也要增加相应的关联信息：</p>
    <pre class="brush: java; highlight: [8, 9, 18, 19]">
        public class Speculator implements Serializable {
            @Id
            private Integer id;
            @ManyToMany
            @JoinTable(joinColumns = @JoinColumn(name = "speculator_id"),
                    inverseJoinColumns = @JoinColumn(name = "house_id"))
            private List&lt;House&gt; houses;
            @OneToMany(mappedBy = "speculator")
            private List&lt;SpeculatorHouse&gt; speculatorHouses;
            // 此处省略若干行
        }

        public class House implements Serializable {
            @Id
            private Integer id;
            @ManyToMany(mappedBy = "houses")
            private List&lt;Speculator&gt; speculators;
            @OneToMany(mappedBy = "house")
            private List&lt;SpeculatorHouse&gt; speculatorHouses;
            // 此处省略若干行
        }
    </pre>
    <p>这样既保留了多对多关系，又映射了关联表，然后就可以根据实际情况选择隐式或显示的关联表管理。例如，要得到一个炒房客的全部房子，就使用隐式管理：<code>s.getHouses()</code>；而要删除炒房客和某套房子的关联，则用显示管理：<code>delete from SpeculatorHouse sh where sh.speculator = :s and sh.house = :h</code>。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/359547.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-27 11:04 <a href="http://www.blogjava.net/shinzey/archive/2011/09/27/359547.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><item><title>JPA 应用技巧 2：主键外键合体映射</title><link>http://www.blogjava.net/shinzey/archive/2011/09/13/358519.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Tue, 13 Sep 2011 03:27:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/09/13/358519.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/358519.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/09/13/358519.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/358519.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/358519.html</trackback:ping><description><![CDATA[<div class="article">
    <p>考虑两个具有一对一关联的实体类，受控方除了有一个自己的主键，还有一个引用主控方的外键。如果主控方和受控方是同生同灭的关系，换句话说，双方的一对一关联一旦确立就不可更改，就可以考虑让双方共享相同的主键，简化受控方的表结构。下面就让楼主通过实例来说明如何用 JPA 2.0 来实现这种映射。</p>
    <p>盯着眼前的电脑，楼主想到了一个也许不太贴切的例子：员工和公司配的电脑的关系。员工的主键就是工号。虽然电脑可能被换掉，但电脑实体完全可以用工号做主键，只是把电脑配置的详细信息改掉而已。此处的难点就在与如何将电脑的主键字段同时映射一个员工，请看楼主是怎么一步步推导出来的。</p>
    <p>一开始是最想当然的写法：</p>
    <pre class="brush: java">
        public class Computer implements Serializable {
            @Id
            @OneToOne
            private Employee employee;
            // 此处省略若干行
        }
    </pre>
    <p>然而根据规范，只有这些类型可以作为主键：Java 原始类型（例如 <code>int</code>）、原始包装类型（例如 <code>Integer</code>）、<code>String</code>、<code>java.util.Date</code>、<code>java.sql.Date</code>、 <code>java.math.BigDecimal</code> 和 <code>java.math.BigInteger</code>。所以直接拿 <code>Employee</code> 做主键是不行的。顺便提一下，也许某些 JPA 实现自己做了扩展，使得可以直接拿实体类做主键，这已经超出了 JPA 规范的范畴，此处不讨论。</p>
    <p>直接映射是行不通了，那有什么间接的方式吗？这时楼主想起了一个特殊的注解：<code>EmbeddedId</code>。该注解的本意是用于联合主键，不过变通一下，是否可以将 <code>Employee</code> 包装进一个嵌入式主键，然后再将这个嵌入式主键作为 <code>Computer</code> 的主键以达到目的？带着这种想法，楼主有了下面的代码：</p>
    <pre class="brush: java">
        @Embeddable
        public class ComputerId implements Serializable {
            @OneToOne
            private Employee employee;
            // 此处省略若干行
        }
    </pre>
    <pre class="brush: java">
        public class Computer implements Serializable {
            @EmbeddedId
            private ComputerId id;
            // 此处省略若干行
        }
    </pre>
    <p>现在又出现了新的问题：JPA 不支持定义在嵌入式主键类中的关联映射。好在天无绝人之路，<code>EmbeddedId</code> 的文档中直接指出，可以使用 <code>MapsId</code> 注解来间接指定嵌入式主键类中的关联映射，而且还附带了一个例子！于是最终的成品就出炉了：</p>
    <pre class="brush: java; highlight: 3">
        @Embeddable
        public class ComputerId implements Serializable {
            private String employeeId;
            // 此处省略若干行
        }
    </pre>
    <pre class="brush: java; highlight: [2, 3, 4, 5, 6]">
        public class Computer implements Serializable {
            @EmbeddedId
            private Employee employee;
            @MapsId("employeeId")
            @OneToOne
            private Employee employee;
            // 此处省略若干行
        }
    </pre>
    <p>唯一的遗憾是，逻辑上的主控方 <code>Employee</code> 现在不得不成为受控方了，好在可以定义级联操作来达到差不多的效果：</p>
    <pre class="brush: java; highlight: [4, 5]">
        public class Employee implements Serializable {
            @Id
            private String id;
            @OneToOne(mappedBy = "employee", cascade = CascadeType.ALL)
            private Computer computer;
            // 此处省略若干行
        }
    </pre>
    <p>虽然做到了，但确实挺绕的。希望未来版本的 JPA 能直接支持将实体类作为主键，楼主个人觉得不是一个技术问题。</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/358519.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-13 11:27 <a href="http://www.blogjava.net/shinzey/archive/2011/09/13/358519.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>让 JSF 支持 multipart/form-data 编码类型的 POST 请求</title><link>http://www.blogjava.net/shinzey/archive/2011/09/09/358357.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Fri, 09 Sep 2011 03:23:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/09/09/358357.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/358357.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/09/09/358357.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/358357.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/358357.html</trackback:ping><description><![CDATA[<div class="article">
    <p>JSF 都 2.0 了，尼玛居然还是无法识别 <code>multipart/form-data</code>（至少参考实现 Mojarra 如此），绑定的属性一个都读不出来，坑爹啊！！！既然官方不支持，咱就自己搞一个补丁，让它不从也得从。</p>
    <p>说到底，JSF 的属性绑定功能不外乎是利用 <code>FacesServlet</code> 帮我们把参数进行转换和校验，然后拼装成受管 Bean。而 <code>FacesServlet</code> 必定是通过 <code>HttpServletRequest</code> 的相关方法来读取请求参数，因此只需要在 <code>FacesServlet</code> 之前增加一个过滤器，把文本类型的 <code>Part</code> 参数转换为普通参数就可以了。至于文件类型的 <code>Part</code>，则可以使用一些第三方工具来绑定，例如使用 PrimeFaces 将文件绑定到 <code>File</code> 对象。下图是这种思路的流程：</p>
    <p class="center"><img src="http://www.blogjava.net/images/blogjava_net/shinzey/44806/o_MultipartFilter.png" alt="multipart/form-data 的处理流程" title="multipart/form-data 的处理流程"></p>
    <p>第二步的过滤器就是给 JSF 打的“补丁”：</p>
    <pre class="brush: java; highlight: [14, 15, 16, 17, 18, 19, 20, 21, 22, 23]">
        /**
         * 该过滤器帮助 {@link FacesServlet} 识别 {@code multipart/form-data} 格式的 POST 请求。
         */
        @WebFilter("*.xhtml")
        public class MultipartFilter implements Filter {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain) throws IOException, ServletException {
                String contentType = request.getContentType();
                // 判断请求的格式是否为 multipart/form-data。
                if (contentType != null && contentType.startsWith("multipart/form-data")) {
                    MultipartRequest req = new MultipartRequest((HttpServletRequest) request);
                    for (Part part : req.getParts()) {
                        // 如果该 Part 的内容类型不为 null, 那它是一个文件，忽略。
                        if (part.getContentType() == null) {
                            req.addParameter(part.getName(), decode(part));
                        }
                    }
                    chain.doFilter(req, response);
                } else {
                    chain.doFilter(request, response);
                }
            }

            @Override
            public void destroy() {
            }

            /**
             * 将 {@link Part} 对象解码为字符串。
             */
            private String decode(Part part) throws IOException {
                try (InputStreamReader in = new InputStreamReader(
                        part.getInputStream(), StandardCharsets.UTF_8)) {
                    char[] buffer = new char[64];
                    int nread = 0;
                    StringBuilder sb = new StringBuilder();
                    while ((nread = in.read(buffer)) != -1) {
                        sb.append(buffer, 0, nread);
                    }
                    return sb.toString();
                }
            }

            /**
             * {@link HttpServletRequest} 中的请求参数映射是只读的，所以自己封装一个。
             */
            private static class MultipartRequest extends HttpServletRequestWrapper {
                private Map&lt;String, String[]&gt; parameters;

                public MultipartRequest(HttpServletRequest request) {
                    super(request);
                    parameters = new HashMap&lt;&gt;();
                }

                private void addParameter(String name, String value) {
                    String[] oldValues = parameters.get(name);
                    if (oldValues == null) {
                        parameters.put(name, new String[] {value});
                    } else {
                        int size = oldValues.length;
                        String[] values = new String[size + 1];
                        System.arraycopy(oldValues, 0, values, 0, size);
                        values[size] = value;
                        parameters.put(name, values);
                    }
                }

                @Override
                public String getParameter(String name) {
                    String[] values = getParameterValues(name);
                    return values == null ? null : values[0];
                }

                @Override
                public Map&lt;String, String[]&gt; getParameterMap() {
                    return parameters;
                }

                @Override
                public Enumeration&lt;String&gt; getParameterNames() {
                    final Iterator&lt;String&gt; it = parameters.keySet().iterator();
                    return new Enumeration&lt;String&gt;() {
                        @Override
                        public boolean hasMoreElements() {
                            return it.hasNext();
                        }

                        @Override
                        public String nextElement() {
                            return it.next();
                        }
                    };
                }

                @Override
                public String[] getParameterValues(String name) {
                    return parameters.get(name);
                }
            }
        }
    </pre>
    <p>这儿喷一下，为什么 <code>HttpServletRequest</code> 里面的请求参数映射是只读的，非得要通过继承 <code>HttpServletRequestWrapper</code> 这种蛋疼的弯路来黑？</p>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/358357.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-09 11:23 <a href="http://www.blogjava.net/shinzey/archive/2011/09/09/358357.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JPA 应用技巧 1：实体类和实体 DAO 模板</title><link>http://www.blogjava.net/shinzey/archive/2011/09/07/358223.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Wed, 07 Sep 2011 09:40:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2011/09/07/358223.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/358223.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2011/09/07/358223.html#Feedback</comments><slash:comments>8</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/358223.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/358223.html</trackback:ping><description><![CDATA[<div class="article">
    <p>最近闲来无事（楼主确实太懒了），重翻旧账，捣鼓了下 JPA 2.0，通过不断地写代码和谷歌，又有了一些旧瓶装新酒的发现和吐槽。楼主将在这一系列文章中慢慢道来。本次开篇带来的是两个模板类：用作实体类基础框架的 <code>AbstractEntity</code>， 以及实现了对实体的基本 CRUD 操作的 <code>BasicEntityDao</code>。</p>
    <p>一个实体类必须实现 <code>java.io.Serializable</code> 接口，必须有一个 ID 字段作为主键，且最好覆盖 <code>equals</code> 和 <code>hashCode</code> 方法。因为实体类和数据表有对应关系，所以往往根据 ID 来实现 <code>equals</code> 和 <code>hashCode</code>。这很自然地可以引出一个模板类，所有的实体类都可以从它继承：</p>
    <pre class="brush: java; highlight: [6, 7, 8, 9]">
        /**
         * 该类可作为实体类的模板，其 {@link #equals(Object)} 和 {@link hashCode()} 方法基于主键实现。
         * 子类只需要实现 {@link #getId()} 方法。
         */
        public abstract class AbstractEntity implements Serializable {
            /**
             * 返回主键。
             */
            public abstract Object getId();

            @Override
            public boolean equals(Object obj) {
                if (this == obj) {
                    return true;
                }
                if (obj == null || getClass() != obj.getClass()) {
                    return false;
                }
                return getId() == null ? false
                        : getId().equals(((AbstractEntity) obj).getId());
            }

            @Override
            public int hashCode() {
                return Objects.hashCode(getId());
            }
        }
    </pre>
    <p>针对主键的类型，<code>AbstractEntity</code> 可以进一步扩展。例如，可以扩展出一个 <code>UuidEntity</code>，它使用随机生成的 UUID 作为主键：</p>
    <pre class="brush: java; highlight: [11, 12, 13, 14, 15]">
        @MappedSuperclass
        public class UuidEntity extends AbstractEntity {
            @Id
            private String id;

            @Override
            public String getId() {
                return id;
            }

            @PrePersist
            private void generateId() {
                // 仅在持久化前生成 ID，提升一点性能。
                id = UUID.randomUUID().toString();
            }
        }
    </pre>
    <p>继续发挥想象，让它支持乐观锁：</p>
    <pre class="brush: java; highlight: [3, 4]">
        @MappedSuperclass
        public class VersionedUuidEntity extends UuidEntity {
            @Version
            private int version;
        }
    </pre>
    <p>这儿顺便插嘴吐槽下主键的类型。用整数还是 UUID 好呢？这个问题在网上也是争论纷纷。在楼主看来，两者各有优劣：整数主键性能高，可读性也好，但会对数据迁移，例如合并两个数据库，造成不小的麻烦，因为可能出现一大堆重复的主键；UUID 性能差些，看起来晃眼，虽然据说有些数据库针对性地做了优化，想来也不大可能优于整数，不过好处就是理论上出现重复主键的概率比中彩票还小（福彩除外）。说这么一大堆，其实还是蛮纠结啊……楼主一般倾向于用 UUID，只要服务器的配置够劲，想来不会出现明显的性能问题。</p>
    <p>接下来说说 <code>BasicEntityDao</code>，它提供了基本的 CRUD 实现，可以用来为会话 Bean 做模板：</p>
    <pre class="brush: java; highlight: [17, 18, 19, 20]">
        /**
         * 提供了对实体进行基本 CRUD 操作的实现，可作为会话 Bean 的模板。
         */
        public abstract class BasicEntityDao&lt;T&gt; {
            private Class&lt;T&gt; entityClass;
            private String entityClassName;
            private String findAllQuery;
            private String countQuery;

            protected BasicEntityDao(Class&lt;T&gt; entityClass) {
                this.entityClass = Objects.requireNonNull(entityClass);
                entityClassName = entityClass.getSimpleName();
                findAllQuery = "select e from " + entityClassName + " e";
                countQuery = "select count(e) from " + entityClassName + " e";
            }

            /**
             * 返回用于数据库操作的 {@link EntityManager} 实例。
             */
            protected abstract EntityManager getEntityManager();

            public void persist(T entity) {
                getEntityManager().persist(entity);
            }

            public T find(Object id) {
                return getEntityManager().find(entityClass, id);
            }

            public List&lt;T&gt; findAll() {
                return getEntityManager().createQuery(findAllQuery, entityClass).getResultList();
            }

            public List&lt;T&gt; findRange(int first, int max) {
                return getEntityManager().createQuery(findAllQuery, entityClass)
                        .setFirstResult(first).setMaxResults(max).getResultList();
            }

            public long count() {
                return (Long) getEntityManager().createQuery(countQuery).getSingleResult();
            }

            public T merge(T entity) {
                return getEntityManager().merge(entity);
            }

            public void remove(T entity) {
                getEntityManager().remove(merge(entity));
            }
        }
    </pre>
    <p>子类只需要提供 <code>getEntityManager()</code> 的实现即可。假设楼主要做一个养鸡场管理系统，对鸡圈进行操作的会话 Bean 就可以简单地写成：</p>
    <pre class="brush: java; highlight: [6, 7, 8, 10, 11, 12, 13]">
        @Stateless
        public class CoopDao extends BasicEntityDao&lt;Coop&gt; {
            @Persistence
            private EntityManager em;

            public CoopDao() {
                super(Coop.class);
            }

            @Override
            protected EntityManager getEntityManager() {
                return em;
            }

            // 更多方法……
        }
    </pre>
</div><img src ="http://www.blogjava.net/shinzey/aggbug/358223.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-07 17:40 <a href="http://www.blogjava.net/shinzey/archive/2011/09/07/358223.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>JSF 2.0 中定义受管 Bean 的三种途径的比较</title><link>http://www.blogjava.net/shinzey/archive/2010/05/15/321050.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Sat, 15 May 2010 11:10:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2010/05/15/321050.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/321050.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2010/05/15/321050.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/321050.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/321050.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: JSF 2.0 大量采用标注，从而使 <code>web/WEB-INF/faces-config.xml</code> 不再必需。本文介绍并比较了三种途径来定义可从页面上的 EL 表达式中引用的受管 Bean。&nbsp;&nbsp;<a href='http://www.blogjava.net/shinzey/archive/2010/05/15/321050.html'>阅读全文</a><img src ="http://www.blogjava.net/shinzey/aggbug/321050.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2010-05-15 19:10 <a href="http://www.blogjava.net/shinzey/archive/2010/05/15/321050.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在 Servlet 3.0 中处理 multipart/form-data 请求的两个辅助方法</title><link>http://www.blogjava.net/shinzey/archive/2010/04/24/319255.html</link><dc:creator>蜀山兆孨龘</dc:creator><author>蜀山兆孨龘</author><pubDate>Sat, 24 Apr 2010 03:59:00 GMT</pubDate><guid>http://www.blogjava.net/shinzey/archive/2010/04/24/319255.html</guid><wfw:comment>http://www.blogjava.net/shinzey/comments/319255.html</wfw:comment><comments>http://www.blogjava.net/shinzey/archive/2010/04/24/319255.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/shinzey/comments/commentRss/319255.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/shinzey/services/trackbacks/319255.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Servlet 3.0 引入了 <code>javax.servlet.http.Part</code> 接口，从而提供了对 <code>multipart/form-data</code> 类型的 HTTP 请求的直接支持，我们从此就可以摆脱诸如 Apache Commons FileUpload 之类的第三方依赖。然而，该支持太过单纯，所以还要多做点事情，以便能更有效地进行工作。我将在本文中介绍两个辅助方法。&nbsp;&nbsp;<a href='http://www.blogjava.net/shinzey/archive/2010/04/24/319255.html'>阅读全文</a><img src ="http://www.blogjava.net/shinzey/aggbug/319255.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/shinzey/" target="_blank">蜀山兆孨龘</a> 2010-04-24 11:59 <a href="http://www.blogjava.net/shinzey/archive/2010/04/24/319255.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>